"use client"; import { useEffect, useState } from "react"; import { useStore, type AgentInfo } from "@/lib/store"; import { useT, type DictKey } from "@/lib/i18n"; const PROTOCOL_KEY: Record = { stdin: { key: "protocol.stdin", tone: "ok" }, argv: { key: "protocol.argv", tone: "ok" }, "argv-message": { key: "protocol.argvMessage", tone: "ok" }, acp: { key: "protocol.acp", tone: "warn" }, "pi-rpc": { key: "protocol.piRpc", tone: "warn" }, }; const VENDOR_HINT: Record = { Anthropic: { gradient: "from-[#c96442] to-[#e9b94a]", install: "npm i -g @anthropic-ai/claude-code · claude /login", }, OpenAI: { gradient: "from-[#10a37f] to-[#1f7a3a]", install: "npm i -g @openai/codex", }, Cursor: { gradient: "from-[#5b6cf2] to-[#a1a8f5]", install: "curl https://cursor.com/install -fsS | bash", }, Google: { gradient: "from-[#4285f4] to-[#34a853]", install: "npm i -g @google/gemini-cli", }, GitHub: { gradient: "from-[#24292e] to-[#444c56]", install: "gh extension install github/gh-copilot", }, Open: { gradient: "from-[#6e7448] to-[#b26200]", install: "npm i -g @opencode-ai/cli", }, Alibaba: { gradient: "from-[#ff7a00] to-[#ed6f5c]", install: "npm i -g @alibabacloud/qwen-code", }, Aider: { gradient: "from-[#6c3aa6] to-[#9c2a25]", install: "pip install aider-install && aider-install", }, DeepSeek: { gradient: "from-[#2563eb] to-[#7c3aed]", install: "npm install -g deepseek-tui · deepseek-tui auth", }, CodeWhale: { gradient: "from-[#2563eb] to-[#7c3aed]", install: "cargo install codewhale · codewhale auth", }, Cognition: { gradient: "from-[#0f172a] to-[#475569]", install: "curl -fsSL https://cli.devin.ai/install.sh | bash", }, Mature: { gradient: "from-[#7c2d12] to-[#b45309]", install: "npm i -g @mature/hermes-cli · hermes login", }, Moonshot: { gradient: "from-[#0ea5e9] to-[#1e3a8a]", install: "npm i -g @moonshot-ai/kimi-cli · kimi login", }, Inflection: { gradient: "from-[#a855f7] to-[#ec4899]", install: "curl -fsSL https://pi.ai/install.sh | bash · pi login", }, AWS: { gradient: "from-[#ff9900] to-[#232f3e]", install: "brew install kiro · kiro-cli login", }, Kilo: { gradient: "from-[#16a34a] to-[#0d9488]", install: "npm i -g kilo · kilo login", }, Mistral: { gradient: "from-[#fb923c] to-[#ef4444]", install: "npm i -g @mistralai/vibe-cli · vibe login", }, Qoder: { gradient: "from-[#0891b2] to-[#7c3aed]", install: "brew tap qoder/cli && brew install qodercli · qodercli login", }, }; type Props = { onClose: () => void }; export function WelcomeModal({ onClose }: Props) { const setSelectedAgent = useStore((s) => s.setSelectedAgent); const setWelcomeAck = useStore((s) => s.setWelcomeAck); const setAgents = useStore((s) => s.setAgents); const setAgentModel = useStore((s) => s.setAgentModel); const agents = useStore((s) => s.agents); const selected = useStore((s) => s.selectedAgent); const agentModels = useStore((s) => s.agentModels); const t = useT(); const [loading, setLoading] = useState(false); const [err, setErr] = useState(null); const load = async () => { setLoading(true); setErr(null); try { const res = await fetch("/api/agents", { cache: "no-store" }); if (!res.ok) throw new Error(`HTTP ${res.status}`); const data = (await res.json()) as { agents: AgentInfo[] }; setAgents(data.agents); const installed = data.agents.filter((a) => a.available); if (!installed.find((a) => a.id === selected) && installed.length) { setSelectedAgent(installed[0].id); } } catch (e) { setErr(e instanceof Error ? e.message : "detection failed"); } finally { setLoading(false); } }; useEffect(() => { load(); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const installed = agents.filter((a) => a.available); const missing = agents.filter((a) => !a.available); const selectedAgent = installed.find((a) => a.id === selected); const selectedModelId = selected ? agentModels[selected] ?? "default" : "default"; const canEnter = !!selectedAgent && !selectedAgent.unsupported; const confirm = () => { if (!canEnter) return; setWelcomeAck(true); onClose(); }; return (
{ if (e.target === e.currentTarget && canEnter) confirm(); }} >
{/* Header */}
{t("welcome.eyebrow")}

{t("welcome.titlePart1")} {t("welcome.titleAccent")}

{t("welcome.description")}

{/* Agent grid */}
{err && (
{t("welcome.detectionFailed")}: {err}
)} {installed.length > 0 && ( <>
{t("welcome.installed", { n: installed.length })}
{installed.map((a) => ( setSelectedAgent(a.id)} /> ))}
)} {missing.length > 0 && ( <>
{t("welcome.notInstalled", { n: missing.length })}
{missing.map((a) => ( ))}
)} {!loading && installed.length === 0 && (
🪞

{t("welcome.noAgentsTitle")}

{t("welcome.noAgentsBody")}

)} {selectedAgent && ( setAgentModel(selectedAgent.id, id)} /> )}
{/* Community links — brand names only, no translation needed. */} {/* Footer */}
{selectedAgent ? ( <> {t("welcome.current")}: {selectedAgent.label} {selectedModelId !== "default" && ( · {selectedModelId} )} {selectedAgent.unsupported && ( {t("welcome.unsupportedHint")} )} ) : ( <>{t("welcome.pickInstalled")} )}
); } function AgentCard({ agent, selected, onClick, }: { agent: AgentInfo; selected: boolean; onClick: () => void; }) { const t = useT(); const hint = VENDOR_HINT[agent.vendor]; const proto = PROTOCOL_KEY[agent.protocol]; return ( ); } function ModelPicker({ agent, modelId, onPick, }: { agent: AgentInfo; modelId: string; onPick: (id: string) => void; }) { const t = useT(); const models = agent.models.length ? agent.models : [{ id: "default", label: t("model.defaultLabel") }]; const MARK = "​"; const [before, after = ""] = t("model.label", { agent: MARK }).split(MARK); return (
{t("model.eyebrow")}
{before} {agent.label} {after}
{t("model.defaultHint.prefix")} --model {t("model.defaultHint.suffix")}
{models.map((m) => { const active = m.id === modelId; return ( ); })}
); } function MissingCard({ agent }: { agent: AgentInfo }) { const t = useT(); const hint = VENDOR_HINT[agent.vendor]; return (
{agent.label.charAt(0)}
{agent.label}
{hint && (
{hint.install}
)}
{t("agent.notInstalled")}
); }