[nexus] feat(19-03): dual-section Installed tab and read-only SkillCard for native skills
- SkillCard: add isReadOnly and source props; hide update/rollback/install when isReadOnly - SkillCard: show Native badge when isReadOnly or source=native - SkillBrowser: remove agentSkillsDir state and text input from install dialog - SkillBrowser: add selectedAgentId state for per-agent installed skills view - SkillBrowser: add agentInstalledSkills query using skillGroupsApi.listAgentSkills - SkillBrowser: split installed skills into managedSkills/nativeSkills sections - SkillBrowser: render Managed/Native section headings when nativeSkills.length > 0 - SkillBrowser: uninstall dialog now carries agentId and calls skillRegistryApi.uninstall - SkillBrowser: install dialog sends agentId directly (no agentSkillsDir text input)
This commit is contained in:
parent
6b205b9f21
commit
da3a43e349
2 changed files with 176 additions and 76 deletions
|
|
@ -20,6 +20,8 @@ export interface SkillCardProps {
|
||||||
onRollback?: () => void;
|
onRollback?: () => void;
|
||||||
onUninstall?: () => void;
|
onUninstall?: () => void;
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
|
isReadOnly?: boolean;
|
||||||
|
source?: "managed" | "native";
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -32,13 +34,15 @@ export function SkillCard({
|
||||||
onRollback,
|
onRollback,
|
||||||
onUninstall,
|
onUninstall,
|
||||||
isLoading = false,
|
isLoading = false,
|
||||||
|
isReadOnly = false,
|
||||||
|
source,
|
||||||
className,
|
className,
|
||||||
}: SkillCardProps) {
|
}: SkillCardProps) {
|
||||||
return (
|
return (
|
||||||
<Card className={cn("flex flex-col", className)}>
|
<Card className={cn("flex flex-col", className)}>
|
||||||
<CardContent className="p-4 flex flex-col gap-2">
|
<CardContent className="p-4 flex flex-col gap-2">
|
||||||
|
|
||||||
{/* Row 1: name link (primary visual anchor) + update badge */}
|
{/* Row 1: name link (primary visual anchor) + update badge + native badge */}
|
||||||
<div className="flex items-start justify-between gap-2">
|
<div className="flex items-start justify-between gap-2">
|
||||||
<Link
|
<Link
|
||||||
to={`/skills/detail/${encodeURIComponent(skill.id)}`}
|
to={`/skills/detail/${encodeURIComponent(skill.id)}`}
|
||||||
|
|
@ -46,15 +50,26 @@ export function SkillCard({
|
||||||
>
|
>
|
||||||
{skill.name}
|
{skill.name}
|
||||||
</Link>
|
</Link>
|
||||||
{hasUpdate && (
|
<div className="flex shrink-0 gap-1">
|
||||||
<Badge
|
{(isReadOnly || source === "native") && (
|
||||||
variant="outline"
|
<Badge
|
||||||
className="text-xs text-amber-600 border-amber-500 shrink-0"
|
variant="secondary"
|
||||||
aria-label="Update available"
|
className="text-xs text-muted-foreground"
|
||||||
>
|
aria-label="Native skill"
|
||||||
Update
|
>
|
||||||
</Badge>
|
Native
|
||||||
)}
|
</Badge>
|
||||||
|
)}
|
||||||
|
{hasUpdate && !isReadOnly && (
|
||||||
|
<Badge
|
||||||
|
variant="outline"
|
||||||
|
className="text-xs text-amber-600 border-amber-500"
|
||||||
|
aria-label="Update available"
|
||||||
|
>
|
||||||
|
Update
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Row 2: description (2-line clamp) */}
|
{/* Row 2: description (2-line clamp) */}
|
||||||
|
|
@ -72,7 +87,7 @@ export function SkillCard({
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<div className="ml-auto flex gap-1">
|
<div className="ml-auto flex gap-1">
|
||||||
{isInstalled && onRollback && (
|
{!isReadOnly && isInstalled && onRollback && (
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
|
|
@ -87,7 +102,7 @@ export function SkillCard({
|
||||||
<TooltipContent>Rollback</TooltipContent>
|
<TooltipContent>Rollback</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
{!isInstalled && (
|
{!isReadOnly && !isInstalled && (
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
|
|
@ -98,7 +113,7 @@ export function SkillCard({
|
||||||
{isLoading ? "Installing\u2026" : "Install skill"}
|
{isLoading ? "Installing\u2026" : "Install skill"}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{isInstalled && hasUpdate && (
|
{!isReadOnly && isInstalled && hasUpdate && (
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import { useCompany } from "@/context/CompanyContext";
|
||||||
import { useBreadcrumbs } from "@/context/BreadcrumbContext";
|
import { useBreadcrumbs } from "@/context/BreadcrumbContext";
|
||||||
import { useToast } from "@/context/ToastContext";
|
import { useToast } from "@/context/ToastContext";
|
||||||
import { skillRegistryApi } from "@/api/skillRegistry";
|
import { skillRegistryApi } from "@/api/skillRegistry";
|
||||||
|
import { skillGroupsApi } from "@/api/skillGroups";
|
||||||
import { agentsApi } from "@/api/agents";
|
import { agentsApi } from "@/api/agents";
|
||||||
import { queryKeys } from "@/lib/queryKeys";
|
import { queryKeys } from "@/lib/queryKeys";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
|
@ -55,10 +56,12 @@ export function SkillBrowser() {
|
||||||
const [categoryFilter, setCategoryFilter] = useState<string | null>(null);
|
const [categoryFilter, setCategoryFilter] = useState<string | null>(null);
|
||||||
const [sortBy, setSortBy] = useState<SortBy>("rating");
|
const [sortBy, setSortBy] = useState<SortBy>("rating");
|
||||||
|
|
||||||
|
// Installed tab: selected agent for per-agent skill view
|
||||||
|
const [selectedAgentId, setSelectedAgentId] = useState<string | null>(null);
|
||||||
|
|
||||||
// Dialog state
|
// Dialog state
|
||||||
const [installDialog, setInstallDialog] = useState<{ skillId: string; isUpdate?: boolean } | null>(null);
|
const [installDialog, setInstallDialog] = useState<{ skillId: string; isUpdate?: boolean } | null>(null);
|
||||||
const [agentSkillsDir, setAgentSkillsDir] = useState("");
|
const [uninstallDialog, setUninstallDialog] = useState<{ skillId: string; agentId: string } | null>(null);
|
||||||
const [uninstallDialog, setUninstallDialog] = useState<{ skillId: string } | null>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setBreadcrumbs([
|
setBreadcrumbs([
|
||||||
|
|
@ -79,6 +82,13 @@ export function SkillBrowser() {
|
||||||
enabled: !!selectedCompany?.id,
|
enabled: !!selectedCompany?.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Per-agent installed skills (for Installed tab)
|
||||||
|
const { data: agentInstalledSkills = [] } = useQuery({
|
||||||
|
queryKey: ["agentInstalledSkills", selectedAgentId],
|
||||||
|
queryFn: () => skillGroupsApi.listAgentSkills(selectedAgentId!),
|
||||||
|
enabled: tab === "installed" && !!selectedAgentId,
|
||||||
|
});
|
||||||
|
|
||||||
// Mutations
|
// Mutations
|
||||||
const fetchMutation = useMutation({
|
const fetchMutation = useMutation({
|
||||||
mutationFn: () => skillRegistryApi.fetch(),
|
mutationFn: () => skillRegistryApi.fetch(),
|
||||||
|
|
@ -92,10 +102,13 @@ export function SkillBrowser() {
|
||||||
});
|
});
|
||||||
|
|
||||||
const installMutation = useMutation({
|
const installMutation = useMutation({
|
||||||
mutationFn: (params: { skillId: string; agentSkillsDir: string }) =>
|
mutationFn: (params: { skillId: string; agentId: string }) =>
|
||||||
skillRegistryApi.install(params.skillId, params.agentSkillsDir),
|
skillRegistryApi.install(params.skillId, params.agentId),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries({ queryKey: queryKeys.skillRegistry.list });
|
queryClient.invalidateQueries({ queryKey: queryKeys.skillRegistry.list });
|
||||||
|
if (selectedAgentId) {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["agentInstalledSkills", selectedAgentId] });
|
||||||
|
}
|
||||||
pushToast({ title: "Skill installed", tone: "success" });
|
pushToast({ title: "Skill installed", tone: "success" });
|
||||||
},
|
},
|
||||||
onError: (err: Error) => {
|
onError: (err: Error) => {
|
||||||
|
|
@ -104,10 +117,13 @@ export function SkillBrowser() {
|
||||||
});
|
});
|
||||||
|
|
||||||
const updateMutation = useMutation({
|
const updateMutation = useMutation({
|
||||||
mutationFn: (params: { skillId: string; agentSkillsDir: string }) =>
|
mutationFn: (params: { skillId: string; agentId: string }) =>
|
||||||
skillRegistryApi.install(params.skillId, params.agentSkillsDir),
|
skillRegistryApi.install(params.skillId, params.agentId),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries({ queryKey: queryKeys.skillRegistry.list });
|
queryClient.invalidateQueries({ queryKey: queryKeys.skillRegistry.list });
|
||||||
|
if (selectedAgentId) {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["agentInstalledSkills", selectedAgentId] });
|
||||||
|
}
|
||||||
pushToast({ title: "Skill updated", tone: "success" });
|
pushToast({ title: "Skill updated", tone: "success" });
|
||||||
},
|
},
|
||||||
onError: (err: Error) => {
|
onError: (err: Error) => {
|
||||||
|
|
@ -116,8 +132,8 @@ export function SkillBrowser() {
|
||||||
});
|
});
|
||||||
|
|
||||||
const rollbackMutation = useMutation({
|
const rollbackMutation = useMutation({
|
||||||
mutationFn: (params: { skillId: string; versionId: string; agentSkillsDir: string }) =>
|
mutationFn: (params: { skillId: string; versionId: string; agentId: string }) =>
|
||||||
skillRegistryApi.rollback(params.skillId, params.versionId, params.agentSkillsDir),
|
skillRegistryApi.rollback(params.skillId, params.versionId, params.agentId),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries({ queryKey: queryKeys.skillRegistry.list });
|
queryClient.invalidateQueries({ queryKey: queryKeys.skillRegistry.list });
|
||||||
pushToast({ title: "Rolled back to previous version", tone: "success" });
|
pushToast({ title: "Rolled back to previous version", tone: "success" });
|
||||||
|
|
@ -128,9 +144,13 @@ export function SkillBrowser() {
|
||||||
});
|
});
|
||||||
|
|
||||||
const removeMutation = useMutation({
|
const removeMutation = useMutation({
|
||||||
mutationFn: (skillId: string) => skillRegistryApi.remove(skillId),
|
mutationFn: (params: { skillId: string; agentId: string }) =>
|
||||||
|
skillRegistryApi.uninstall(params.skillId, params.agentId),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries({ queryKey: queryKeys.skillRegistry.list });
|
queryClient.invalidateQueries({ queryKey: queryKeys.skillRegistry.list });
|
||||||
|
if (selectedAgentId) {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["agentInstalledSkills", selectedAgentId] });
|
||||||
|
}
|
||||||
pushToast({ title: "Skill uninstalled", tone: "success" });
|
pushToast({ title: "Skill uninstalled", tone: "success" });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -181,11 +201,20 @@ export function SkillBrowser() {
|
||||||
if (key === "category") setCategoryFilter(null);
|
if (key === "category") setCategoryFilter(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Installed tab grouping
|
// Installed tab: split into managed/native
|
||||||
const installedGroups = useMemo(() => {
|
const managedSkills = useMemo(
|
||||||
const installed = skills.filter((s) => s.activeVersionId && !s.removedAt);
|
() => agentInstalledSkills.filter((s) => s.source === "managed"),
|
||||||
if (installed.length === 0) return [];
|
[agentInstalledSkills],
|
||||||
return [{ agentId: "all", agentName: "All Agents", skills: installed }];
|
);
|
||||||
|
const nativeSkills = useMemo(
|
||||||
|
() => agentInstalledSkills.filter((s) => s.source === "native"),
|
||||||
|
[agentInstalledSkills],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Helper: get SkillListItem by skillId (for rendering in Installed tab)
|
||||||
|
const skillById = useMemo(() => {
|
||||||
|
const map = new Map(skills.map((s) => [s.id, s]));
|
||||||
|
return map;
|
||||||
}, [skills]);
|
}, [skills]);
|
||||||
|
|
||||||
// Trending tab sections
|
// Trending tab sections
|
||||||
|
|
@ -224,14 +253,12 @@ export function SkillBrowser() {
|
||||||
|
|
||||||
const handleInstallForAgent = (agentId: string) => {
|
const handleInstallForAgent = (agentId: string) => {
|
||||||
if (!installDialog) return;
|
if (!installDialog) return;
|
||||||
const dir = agentSkillsDir.trim() || `/agents/${agentId}/.claude/skills`;
|
|
||||||
if (installDialog.isUpdate) {
|
if (installDialog.isUpdate) {
|
||||||
updateMutation.mutate({ skillId: installDialog.skillId, agentSkillsDir: dir });
|
updateMutation.mutate({ skillId: installDialog.skillId, agentId });
|
||||||
} else {
|
} else {
|
||||||
installMutation.mutate({ skillId: installDialog.skillId, agentSkillsDir: dir });
|
installMutation.mutate({ skillId: installDialog.skillId, agentId });
|
||||||
}
|
}
|
||||||
setInstallDialog(null);
|
setInstallDialog(null);
|
||||||
setAgentSkillsDir("");
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const tabItems = [
|
const tabItems = [
|
||||||
|
|
@ -345,7 +372,10 @@ export function SkillBrowser() {
|
||||||
onInstall={() => setInstallDialog({ skillId: skill.id })}
|
onInstall={() => setInstallDialog({ skillId: skill.id })}
|
||||||
onUpdate={() => setInstallDialog({ skillId: skill.id, isUpdate: true })}
|
onUpdate={() => setInstallDialog({ skillId: skill.id, isUpdate: true })}
|
||||||
onRollback={() => handleRollback(skill.id)}
|
onRollback={() => handleRollback(skill.id)}
|
||||||
onUninstall={() => setUninstallDialog({ skillId: skill.id })}
|
onUninstall={() => {
|
||||||
|
// Browse tab uninstall: no specific agentId context — open install dialog to select agent first
|
||||||
|
void skill.id;
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -364,34 +394,106 @@ export function SkillBrowser() {
|
||||||
|
|
||||||
{/* Installed tab */}
|
{/* Installed tab */}
|
||||||
<TabsContent value="installed" className="space-y-6">
|
<TabsContent value="installed" className="space-y-6">
|
||||||
{installedGroups.length === 0 && (
|
{/* Agent selector for installed tab */}
|
||||||
|
{agents.length === 0 && (
|
||||||
<EmptyState
|
<EmptyState
|
||||||
icon={Download}
|
icon={Download}
|
||||||
message="No skills installed"
|
message="No agents found in this workspace."
|
||||||
action="Browse skills"
|
|
||||||
onAction={() => setTab("browse")}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{installedGroups.map((group) => (
|
{agents.length > 0 && !selectedAgentId && (
|
||||||
<div key={group.agentId}>
|
<div className="space-y-2">
|
||||||
<div className="flex items-center gap-2 px-4 py-2 bg-muted/50 rounded-t-md">
|
<p className="text-sm text-muted-foreground">Select an agent to view installed skills:</p>
|
||||||
<Identity name={group.agentName} size="sm" />
|
<div className="flex flex-wrap gap-2">
|
||||||
<span className="text-xs text-muted-foreground ml-1">{group.skills.length}</span>
|
{agents.map((agent) => (
|
||||||
</div>
|
<Button
|
||||||
<div className={cn("grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 pt-3")}>
|
key={agent.id}
|
||||||
{group.skills.map((skill) => (
|
variant="outline"
|
||||||
<SkillCard
|
size="sm"
|
||||||
key={skill.id}
|
onClick={() => setSelectedAgentId(agent.id)}
|
||||||
skill={skill}
|
>
|
||||||
isInstalled
|
<Identity name={agent.name} size="sm" />
|
||||||
hasUpdate={false}
|
</Button>
|
||||||
onRollback={() => handleRollback(skill.id)}
|
|
||||||
onUninstall={() => setUninstallDialog({ skillId: skill.id })}
|
|
||||||
/>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
)}
|
||||||
|
{selectedAgentId && (
|
||||||
|
<div className="space-y-4">
|
||||||
|
{/* Back to agent selector */}
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setSelectedAgentId(null)}
|
||||||
|
className="text-xs"
|
||||||
|
>
|
||||||
|
← All agents
|
||||||
|
</Button>
|
||||||
|
<span className="text-sm font-medium">
|
||||||
|
{agents.find((a) => a.id === selectedAgentId)?.name ?? selectedAgentId}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{agentInstalledSkills.length === 0 && (
|
||||||
|
<EmptyState
|
||||||
|
icon={Download}
|
||||||
|
message="No skills installed for this agent"
|
||||||
|
action="Browse skills"
|
||||||
|
onAction={() => setTab("browse")}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Managed skills section */}
|
||||||
|
{managedSkills.length > 0 && (
|
||||||
|
<>
|
||||||
|
{nativeSkills.length > 0 && (
|
||||||
|
<h3 className="text-sm font-medium text-muted-foreground mt-4 mb-2">Managed</h3>
|
||||||
|
)}
|
||||||
|
<div className={cn("grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4")}>
|
||||||
|
{managedSkills.map((entry) => {
|
||||||
|
const skill = skillById.get(entry.skillId);
|
||||||
|
if (!skill) return null;
|
||||||
|
return (
|
||||||
|
<SkillCard
|
||||||
|
key={entry.skillId}
|
||||||
|
skill={skill}
|
||||||
|
isInstalled
|
||||||
|
hasUpdate={false}
|
||||||
|
source="managed"
|
||||||
|
onRollback={() => handleRollback(entry.skillId)}
|
||||||
|
onUninstall={() => setUninstallDialog({ skillId: entry.skillId, agentId: selectedAgentId })}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Native skills section */}
|
||||||
|
{nativeSkills.length > 0 && (
|
||||||
|
<>
|
||||||
|
<h3 className="text-sm font-medium text-muted-foreground mt-4 mb-2">Native</h3>
|
||||||
|
<div className={cn("grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4")}>
|
||||||
|
{nativeSkills.map((entry) => {
|
||||||
|
const skill = skillById.get(entry.skillId);
|
||||||
|
if (!skill) return null;
|
||||||
|
return (
|
||||||
|
<SkillCard
|
||||||
|
key={entry.skillId}
|
||||||
|
skill={skill}
|
||||||
|
isInstalled
|
||||||
|
hasUpdate={false}
|
||||||
|
isReadOnly={true}
|
||||||
|
source="native"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
{/* Trending tab */}
|
{/* Trending tab */}
|
||||||
|
|
@ -418,7 +520,7 @@ export function SkillBrowser() {
|
||||||
onInstall={() => setInstallDialog({ skillId: skill.id })}
|
onInstall={() => setInstallDialog({ skillId: skill.id })}
|
||||||
onUpdate={() => setInstallDialog({ skillId: skill.id, isUpdate: true })}
|
onUpdate={() => setInstallDialog({ skillId: skill.id, isUpdate: true })}
|
||||||
onRollback={() => handleRollback(skill.id)}
|
onRollback={() => handleRollback(skill.id)}
|
||||||
onUninstall={() => setUninstallDialog({ skillId: skill.id })}
|
onUninstall={() => void skill.id}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -437,7 +539,7 @@ export function SkillBrowser() {
|
||||||
onInstall={() => setInstallDialog({ skillId: skill.id })}
|
onInstall={() => setInstallDialog({ skillId: skill.id })}
|
||||||
onUpdate={() => setInstallDialog({ skillId: skill.id, isUpdate: true })}
|
onUpdate={() => setInstallDialog({ skillId: skill.id, isUpdate: true })}
|
||||||
onRollback={() => handleRollback(skill.id)}
|
onRollback={() => handleRollback(skill.id)}
|
||||||
onUninstall={() => setUninstallDialog({ skillId: skill.id })}
|
onUninstall={() => void skill.id}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -456,7 +558,7 @@ export function SkillBrowser() {
|
||||||
onInstall={() => setInstallDialog({ skillId: skill.id })}
|
onInstall={() => setInstallDialog({ skillId: skill.id })}
|
||||||
onUpdate={() => setInstallDialog({ skillId: skill.id, isUpdate: true })}
|
onUpdate={() => setInstallDialog({ skillId: skill.id, isUpdate: true })}
|
||||||
onRollback={() => handleRollback(skill.id)}
|
onRollback={() => handleRollback(skill.id)}
|
||||||
onUninstall={() => setUninstallDialog({ skillId: skill.id })}
|
onUninstall={() => void skill.id}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -471,10 +573,7 @@ export function SkillBrowser() {
|
||||||
<Dialog
|
<Dialog
|
||||||
open={!!installDialog}
|
open={!!installDialog}
|
||||||
onOpenChange={(open) => {
|
onOpenChange={(open) => {
|
||||||
if (!open) {
|
if (!open) setInstallDialog(null);
|
||||||
setInstallDialog(null);
|
|
||||||
setAgentSkillsDir("");
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
|
|
@ -485,17 +584,6 @@ export function SkillBrowser() {
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="space-y-1">
|
|
||||||
<label className="text-xs text-muted-foreground" htmlFor="skills-dir-input">
|
|
||||||
Agent skills directory (leave blank to use default)
|
|
||||||
</label>
|
|
||||||
<Input
|
|
||||||
id="skills-dir-input"
|
|
||||||
placeholder="/path/to/agent/.claude/skills"
|
|
||||||
value={agentSkillsDir}
|
|
||||||
onChange={(e) => setAgentSkillsDir(e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
{agents.length === 0 && (
|
{agents.length === 0 && (
|
||||||
<p className="text-sm text-muted-foreground">No agents found in this workspace.</p>
|
<p className="text-sm text-muted-foreground">No agents found in this workspace.</p>
|
||||||
|
|
@ -516,10 +604,7 @@ export function SkillBrowser() {
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() => {
|
onClick={() => setInstallDialog(null)}
|
||||||
setInstallDialog(null);
|
|
||||||
setAgentSkillsDir("");
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -548,7 +633,7 @@ export function SkillBrowser() {
|
||||||
disabled={removeMutation.isPending}
|
disabled={removeMutation.isPending}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (uninstallDialog) {
|
if (uninstallDialog) {
|
||||||
removeMutation.mutate(uninstallDialog.skillId);
|
removeMutation.mutate({ skillId: uninstallDialog.skillId, agentId: uninstallDialog.agentId });
|
||||||
setUninstallDialog(null);
|
setUninstallDialog(null);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue