feat(20-02): add adapter labels and unsupported install guard to SkillBrowser
- Import getUIAdapter and resolveAdapterSkillConfig/listAdapterSkillConfigs - Show adapter type label in parentheses next to agent name in Installed tab selector - Show adapter type label in install dialog agent list - Guard handleInstallForAgent: show unsupportedMessage if adapter does not support install - Dismissible error alert in install dialog for unsupported adapter attempts - Clear unsupportedMessage on dialog open/close - Compute COMPATIBLE_ADAPTER_LABELS at module level for Task 2
This commit is contained in:
parent
1c44dabf51
commit
f79e0aa628
1 changed files with 49 additions and 2 deletions
|
|
@ -38,6 +38,13 @@ import { PageTabBar } from "@/components/PageTabBar";
|
||||||
import { PageSkeleton } from "@/components/PageSkeleton";
|
import { PageSkeleton } from "@/components/PageSkeleton";
|
||||||
import { Identity } from "@/components/Identity";
|
import { Identity } from "@/components/Identity";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
import { getUIAdapter } from "@/adapters";
|
||||||
|
import { resolveAdapterSkillConfig, listAdapterSkillConfigs } from "@paperclipai/adapter-utils";
|
||||||
|
|
||||||
|
// Compute compatible adapter labels once at module level (used by Browse/Trending SkillCards)
|
||||||
|
const COMPATIBLE_ADAPTER_LABELS = listAdapterSkillConfigs()
|
||||||
|
.filter((c) => c.supportsInstall)
|
||||||
|
.map((c) => getUIAdapter(c.adapterType).label);
|
||||||
|
|
||||||
type SortBy = "rating" | "name" | "recent";
|
type SortBy = "rating" | "name" | "recent";
|
||||||
|
|
||||||
|
|
@ -62,6 +69,7 @@ export function SkillBrowser() {
|
||||||
// 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 [uninstallDialog, setUninstallDialog] = useState<{ skillId: string; agentId: string } | null>(null);
|
const [uninstallDialog, setUninstallDialog] = useState<{ skillId: string; agentId: string } | null>(null);
|
||||||
|
const [unsupportedMessage, setUnsupportedMessage] = useState<string | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setBreadcrumbs([
|
setBreadcrumbs([
|
||||||
|
|
@ -253,6 +261,16 @@ export function SkillBrowser() {
|
||||||
|
|
||||||
const handleInstallForAgent = (agentId: string) => {
|
const handleInstallForAgent = (agentId: string) => {
|
||||||
if (!installDialog) return;
|
if (!installDialog) return;
|
||||||
|
const agent = agents.find((a) => a.id === agentId);
|
||||||
|
if (agent) {
|
||||||
|
const cfg = resolveAdapterSkillConfig(agent.adapterType ?? "process");
|
||||||
|
if (!cfg.supportsInstall) {
|
||||||
|
setUnsupportedMessage(
|
||||||
|
cfg.unsupportedReason ?? "This adapter does not support skill installation."
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (installDialog.isUpdate) {
|
if (installDialog.isUpdate) {
|
||||||
updateMutation.mutate({ skillId: installDialog.skillId, agentId });
|
updateMutation.mutate({ skillId: installDialog.skillId, agentId });
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -413,6 +431,9 @@ export function SkillBrowser() {
|
||||||
onClick={() => setSelectedAgentId(agent.id)}
|
onClick={() => setSelectedAgentId(agent.id)}
|
||||||
>
|
>
|
||||||
<Identity name={agent.name} size="sm" />
|
<Identity name={agent.name} size="sm" />
|
||||||
|
<span className="text-muted-foreground ml-1 text-xs">
|
||||||
|
({getUIAdapter(agent.adapterType ?? "process").label})
|
||||||
|
</span>
|
||||||
</Button>
|
</Button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -432,6 +453,14 @@ export function SkillBrowser() {
|
||||||
</Button>
|
</Button>
|
||||||
<span className="text-sm font-medium">
|
<span className="text-sm font-medium">
|
||||||
{agents.find((a) => a.id === selectedAgentId)?.name ?? selectedAgentId}
|
{agents.find((a) => a.id === selectedAgentId)?.name ?? selectedAgentId}
|
||||||
|
{(() => {
|
||||||
|
const a = agents.find((ag) => ag.id === selectedAgentId);
|
||||||
|
return a ? (
|
||||||
|
<span className="text-muted-foreground ml-1 text-xs font-normal">
|
||||||
|
({getUIAdapter(a.adapterType ?? "process").label})
|
||||||
|
</span>
|
||||||
|
) : null;
|
||||||
|
})()}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -573,7 +602,10 @@ export function SkillBrowser() {
|
||||||
<Dialog
|
<Dialog
|
||||||
open={!!installDialog}
|
open={!!installDialog}
|
||||||
onOpenChange={(open) => {
|
onOpenChange={(open) => {
|
||||||
if (!open) setInstallDialog(null);
|
if (!open) {
|
||||||
|
setInstallDialog(null);
|
||||||
|
setUnsupportedMessage(null);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
|
|
@ -584,6 +616,15 @@ export function SkillBrowser() {
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
|
{unsupportedMessage && (
|
||||||
|
<div className="rounded-md border border-destructive/50 bg-destructive/10 p-3 text-sm">
|
||||||
|
<p className="font-medium text-destructive">Cannot install on this agent</p>
|
||||||
|
<p className="text-muted-foreground mt-1">{unsupportedMessage}</p>
|
||||||
|
<Button variant="ghost" size="sm" className="mt-2" onClick={() => setUnsupportedMessage(null)}>
|
||||||
|
Dismiss
|
||||||
|
</Button>
|
||||||
|
</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>
|
||||||
|
|
@ -597,6 +638,9 @@ export function SkillBrowser() {
|
||||||
onClick={() => handleInstallForAgent(agent.id)}
|
onClick={() => handleInstallForAgent(agent.id)}
|
||||||
>
|
>
|
||||||
<Identity name={agent.name} size="sm" />
|
<Identity name={agent.name} size="sm" />
|
||||||
|
<span className="text-muted-foreground ml-1 text-xs">
|
||||||
|
({getUIAdapter(agent.adapterType ?? "process").label})
|
||||||
|
</span>
|
||||||
</Button>
|
</Button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -604,7 +648,10 @@ export function SkillBrowser() {
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() => setInstallDialog(null)}
|
onClick={() => {
|
||||||
|
setInstallDialog(null);
|
||||||
|
setUnsupportedMessage(null);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue