Files
stack/apps/web/src/components/chat/AgentSelector.tsx
Jason Woltje a70f149886
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
feat(ms22-p2): add agent selector UI in WebUI (#685)
Co-authored-by: Jason Woltje <jason@diversecanvas.com>
Co-committed-by: Jason Woltje <jason@diversecanvas.com>
2026-03-05 03:29:02 +00:00

129 lines
3.4 KiB
TypeScript

"use client";
import React from "react";
interface AgentSelectorProps {
selectedAgent?: string | null;
onChange?: (agent: string | null) => void;
disabled?: boolean;
}
const AGENT_CONFIG = {
jarvis: {
displayName: "Jarvis",
role: "Orchestrator",
color: "#3498db",
},
builder: {
displayName: "Builder",
role: "Coding Agent",
color: "#3b82f6",
},
medic: {
displayName: "Medic",
role: "Health Monitor",
color: "#10b981",
},
} as const;
function JarvisIcon({ className }: { className?: string }): React.ReactElement {
return (
<svg
className={`w-3 h-3 ${className ?? ""}`}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="1.5"
>
<circle cx="12" cy="12" r="3" />
<path d="M12 2v4M12 22v-4" />
<path d="M2 12h4M22 12h-4" />
</svg>
);
}
function BuilderIcon({ className }: { className?: string }): React.ReactElement {
return (
<svg
className={`w-3 h-3 ${className ?? ""}`}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="1.5"
>
<path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z" />
</svg>
);
}
function MedicIcon({ className }: { className?: string }): React.ReactElement {
return (
<svg
className={`w-3 h-3 ${className ?? ""}`}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="1.5"
>
<path d="M22 12h-4l-3 9L9 3l-3 9H2" />
</svg>
);
}
const AGENT_ICONS: Record<string, React.FC<{ className?: string }>> = {
jarvis: JarvisIcon,
builder: BuilderIcon,
medic: MedicIcon,
};
export function AgentSelector({
selectedAgent,
onChange,
disabled,
}: AgentSelectorProps): React.ReactElement {
return (
<div className="flex items-center gap-2">
<span className="text-xs font-medium" style={{ color: "rgb(var(--text-muted))" }}>
Agent
</span>
<div className="flex flex-wrap gap-1">
{Object.entries(AGENT_CONFIG).map(([name, config]) => {
const Icon = AGENT_ICONS[name];
const isSelected = selectedAgent === name;
return (
<button
key={name}
type="button"
onClick={() => onChange?.(isSelected ? null : name)}
disabled={disabled}
className={`flex items-center gap-1.5 px-2 py-1.5 rounded-lg border transition-all text-xs ${
isSelected ? "border-primary bg-primary/10 shadow-sm" : "hover:bg-muted/50"
} ${disabled ? "opacity-50 cursor-not-allowed" : ""}`}
style={{
borderColor: isSelected
? "rgb(var(--accent-primary))"
: "rgb(var(--border-default))",
color: isSelected ? "rgb(var(--accent-primary))" : "rgb(var(--text-primary))",
}}
title={`${config.displayName}${config.role}`}
>
<span
className="rounded-full"
style={{
backgroundColor: config.color,
width: "8px",
height: "8px",
}}
/>
{Icon && <Icon />}
<span className="font-medium">{config.displayName}</span>
</button>
);
})}
</div>
</div>
);
}