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:
Mikkel Georgsen 2026-04-01 12:03:11 +02:00 committed by Nexus Dev
parent cef656264e
commit 8a392a568b

View file

@ -38,6 +38,13 @@ import { PageTabBar } from "@/components/PageTabBar";
import { PageSkeleton } from "@/components/PageSkeleton";
import { Identity } from "@/components/Identity";
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";
@ -62,6 +69,7 @@ export function SkillBrowser() {
// Dialog state
const [installDialog, setInstallDialog] = useState<{ skillId: string; isUpdate?: boolean } | null>(null);
const [uninstallDialog, setUninstallDialog] = useState<{ skillId: string; agentId: string } | null>(null);
const [unsupportedMessage, setUnsupportedMessage] = useState<string | null>(null);
useEffect(() => {
setBreadcrumbs([
@ -253,6 +261,16 @@ export function SkillBrowser() {
const handleInstallForAgent = (agentId: string) => {
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) {
updateMutation.mutate({ skillId: installDialog.skillId, agentId });
} else {
@ -413,6 +431,9 @@ export function SkillBrowser() {
onClick={() => setSelectedAgentId(agent.id)}
>
<Identity name={agent.name} size="sm" />
<span className="text-muted-foreground ml-1 text-xs">
({getUIAdapter(agent.adapterType ?? "process").label})
</span>
</Button>
))}
</div>
@ -432,6 +453,14 @@ export function SkillBrowser() {
</Button>
<span className="text-sm font-medium">
{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>
</div>
@ -573,7 +602,10 @@ export function SkillBrowser() {
<Dialog
open={!!installDialog}
onOpenChange={(open) => {
if (!open) setInstallDialog(null);
if (!open) {
setInstallDialog(null);
setUnsupportedMessage(null);
}
}}
>
<DialogContent>
@ -584,6 +616,15 @@ export function SkillBrowser() {
</DialogDescription>
</DialogHeader>
<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">
{agents.length === 0 && (
<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)}
>
<Identity name={agent.name} size="sm" />
<span className="text-muted-foreground ml-1 text-xs">
({getUIAdapter(agent.adapterType ?? "process").label})
</span>
</Button>
))}
</div>
@ -604,7 +648,10 @@ export function SkillBrowser() {
<DialogFooter>
<Button
variant="outline"
onClick={() => setInstallDialog(null)}
onClick={() => {
setInstallDialog(null);
setUnsupportedMessage(null);
}}
>
Cancel
</Button>