All checks were successful
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <jason@diversecanvas.com> Co-committed-by: Jason Woltje <jason@diversecanvas.com>
129 lines
3.4 KiB
TypeScript
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>
|
|
);
|
|
}
|