import { useState, useEffect } from "react"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { trpc } from "@/lib/trpc"; import { toast } from "sonner"; import { Loader2, Plus, Wand2, Sparkles } from "lucide-react"; interface AgentCreateModalProps { open: boolean; onOpenChange: (open: boolean) => void; onSuccess?: () => void; } const AGENT_ROLES = [ { value: "developer", label: "Developer — Code generation & testing" }, { value: "researcher", label: "Researcher — Data analysis & research" }, { value: "executor", label: "Executor — Task automation" }, { value: "monitor", label: "Monitor — System monitoring" }, { value: "analyst", label: "Analyst — Data processing & reports" }, { value: "writer", label: "Writer — Content generation" }, { value: "coordinator", label: "Coordinator — Multi-agent orchestration" }, ]; const DEFAULT_FORM = { name: "", description: "", role: "developer", provider: "", model: "", temperature: 0.7, maxTokens: 2048, systemPrompt: "", }; function toNum(v: string | number | null | undefined, fallback = 0): number { if (v === null || v === undefined) return fallback; const n = typeof v === "string" ? parseFloat(v) : v; return isNaN(n) ? fallback : n; } export function AgentCreateModal({ open, onOpenChange, onSuccess }: AgentCreateModalProps) { const [formData, setFormData] = useState({ ...DEFAULT_FORM }); const [isLoading, setIsLoading] = useState(false); const [isAutoFilling, setIsAutoFilling] = useState(false); const [maxTokensHint, setMaxTokensHint] = useState(null); const [modelInfoLoading, setModelInfoLoading] = useState(false); // ─── Remote data ─────────────────────────────────────────────────────────── const { data: modelsData, isLoading: modelsLoading } = trpc.ollama.models.useQuery(undefined, { staleTime: 60_000, }); const { data: configData } = trpc.config.providers.useQuery(undefined, { staleTime: 300_000, }); const connectedProviders = configData?.providers ?? []; // ─── Model info (context_length) ─────────────────────────────────────────── const { data: modelInfoData, isFetching: modelInfoFetching } = trpc.ollama.modelInfo.useQuery( { modelId: formData.model }, { enabled: !!formData.model, staleTime: 300_000, } ); // When model info arrives, update maxTokens with context_length useEffect(() => { if (modelInfoData?.contextLength && modelInfoData.contextLength > 0) { setMaxTokensHint(modelInfoData.contextLength); setFormData((prev) => ({ ...prev, maxTokens: modelInfoData.contextLength })); } }, [modelInfoData]); // Auto-select first provider when providers load useEffect(() => { if (connectedProviders.length > 0 && !formData.provider) { setFormData((prev) => ({ ...prev, provider: connectedProviders[0].name })); } }, [connectedProviders]); // Auto-select first model when models load useEffect(() => { const availableModels: string[] = modelsData?.models?.map((m: any) => m.id || m) ?? []; if (availableModels.length > 0 && !formData.model) { setFormData((prev) => ({ ...prev, model: availableModels[0] })); } }, [modelsData]); // ─── Mutations ───────────────────────────────────────────────────────────── const createMutation = trpc.agents.create.useMutation({ onSuccess: () => { toast.success("Agent created successfully"); setFormData({ ...DEFAULT_FORM }); onOpenChange(false); onSuccess?.(); }, onError: (error) => { toast.error(`Failed to create agent: ${error.message}`); }, }); const compileMutation = trpc.agentCompiler.compile.useMutation({ onSuccess: (result) => { if (!result.success || !result.config) { toast.error("AI auto-fill failed — fill fields manually"); setIsAutoFilling(false); return; } const cfg = result.config; setFormData((prev) => ({ ...prev, role: cfg.role || prev.role, provider: cfg.provider || prev.provider, model: cfg.model || prev.model, temperature: toNum(cfg.temperature, 0.7), maxTokens: cfg.maxTokens || prev.maxTokens, systemPrompt: cfg.systemPrompt || prev.systemPrompt, })); toast.success("AI filled the fields — review and save"); setIsAutoFilling(false); }, onError: (err) => { toast.error(`Auto-fill error: ${err.message}`); setIsAutoFilling(false); }, }); // ─── Handlers ────────────────────────────────────────────────────────────── const handleCreate = async () => { if (!formData.name.trim()) { toast.error("Agent name is required"); return; } setIsLoading(true); try { await createMutation.mutateAsync({ name: formData.name, description: formData.description, role: formData.role, provider: formData.provider || "Ollama", model: formData.model, temperature: formData.temperature, maxTokens: formData.maxTokens, systemPrompt: formData.systemPrompt, allowedTools: [], }); } finally { setIsLoading(false); } }; const handleAutoFill = async () => { if (!formData.name.trim()) { toast.error("Enter Agent Name first — the AI needs it to suggest parameters"); return; } setIsAutoFilling(true); const spec = `Agent name: ${formData.name}\n${formData.description ? `Description: ${formData.description}` : ""}`.trim(); await compileMutation.mutateAsync({ specification: spec, name: formData.name, preferredProvider: formData.provider || undefined, preferredModel: formData.model || undefined, }); }; const handleModelChange = (model: string) => { setFormData((prev) => ({ ...prev, model })); setMaxTokensHint(null); }; // ─── Derived ─────────────────────────────────────────────────────────────── const availableModels: string[] = modelsData?.models?.map((m: any) => m.id || m) ?? []; const canAutoFill = formData.name.trim().length > 0; return ( Deploy New Agent
{/* ── Name + Description ─────────────────────────────────────────── */}
setFormData({ ...formData, name: e.target.value })} className="font-mono" />
{/* Magic wand — auto-fill remaining fields via AI */}
{canAutoFill && !isAutoFilling && (

Click Auto-fill to let AI suggest temperature, system prompt and other params

)}