Spaces:
Sleeping
Sleeping
| import { useState, useEffect } from "react"; | |
| import { Button } from "@/components/ui/button"; | |
| import { Input } from "@/components/ui/input"; | |
| import { Textarea } from "@/components/ui/textarea"; | |
| import { Label } from "@/components/ui/label"; | |
| import { Switch } from "@/components/ui/switch"; | |
| import { | |
| Dialog, | |
| DialogContent, | |
| DialogHeader, | |
| DialogTitle, | |
| DialogFooter, | |
| DialogDescription, | |
| } from "@/components/ui/dialog"; | |
| import type { AgentProfile, AgentCreateRequest, AgentLLMConfig } from "@/types/agent"; | |
| interface AgentFormProps { | |
| open: boolean; | |
| onOpenChange: (open: boolean) => void; | |
| agent?: AgentProfile | null; | |
| onSubmit: (data: AgentCreateRequest) => Promise<void>; | |
| } | |
| export function AgentForm({ open, onOpenChange, agent, onSubmit }: AgentFormProps) { | |
| const isEdit = !!agent; | |
| const [agentId, setAgentId] = useState(""); | |
| const [displayName, setDisplayName] = useState(""); | |
| const [persona, setPersona] = useState(""); | |
| const [description, setDescription] = useState(""); | |
| const [llmBackbone, setLlmBackbone] = useState(""); | |
| const [tools, setTools] = useState(""); | |
| const [showLlmConfig, setShowLlmConfig] = useState(false); | |
| const [llmConfig, setLlmConfig] = useState<AgentLLMConfig>({}); | |
| const [submitting, setSubmitting] = useState(false); | |
| const [error, setError] = useState(""); | |
| useEffect(() => { | |
| if (agent) { | |
| setAgentId(agent.agent_id); | |
| setDisplayName(agent.display_name); | |
| setPersona(agent.persona || ""); | |
| setDescription(agent.description || ""); | |
| setLlmBackbone(agent.llm_backbone || ""); | |
| setTools(agent.tools.join(", ")); | |
| if (agent.llm_config) { | |
| setShowLlmConfig(true); | |
| setLlmConfig(agent.llm_config); | |
| } | |
| } else { | |
| setAgentId(""); | |
| setDisplayName(""); | |
| setPersona(""); | |
| setDescription(""); | |
| setLlmBackbone(""); | |
| setTools(""); | |
| setShowLlmConfig(false); | |
| setLlmConfig({}); | |
| } | |
| setError(""); | |
| }, [agent, open]); | |
| const handleSubmit = async () => { | |
| if (!agentId.trim() || !displayName.trim()) { | |
| setError("Agent ID and Display Name are required."); | |
| return; | |
| } | |
| setSubmitting(true); | |
| setError(""); | |
| try { | |
| await onSubmit({ | |
| agent_id: agentId.trim(), | |
| display_name: displayName.trim(), | |
| persona: persona.trim(), | |
| description: description.trim(), | |
| llm_backbone: llmBackbone.trim() || null, | |
| llm_config: showLlmConfig ? llmConfig : null, | |
| tools: tools | |
| .split(",") | |
| .map((t) => t.trim()) | |
| .filter(Boolean), | |
| }); | |
| onOpenChange(false); | |
| } catch (e) { | |
| setError(String(e)); | |
| } finally { | |
| setSubmitting(false); | |
| } | |
| }; | |
| return ( | |
| <Dialog open={open} onOpenChange={onOpenChange}> | |
| <DialogContent className="max-h-[85vh] overflow-y-auto sm:max-w-[550px]"> | |
| <DialogHeader> | |
| <DialogTitle>{isEdit ? "Edit Agent" : "Create Agent"}</DialogTitle> | |
| <DialogDescription> | |
| {isEdit ? "Update the agent configuration." : "Define a new agent for your workflows."} | |
| </DialogDescription> | |
| </DialogHeader> | |
| <div className="grid gap-4 py-4"> | |
| <div className="grid grid-cols-2 gap-4"> | |
| <div className="space-y-2"> | |
| <Label htmlFor="agent-id">Agent ID</Label> | |
| <Input | |
| id="agent-id" | |
| placeholder="researcher" | |
| value={agentId} | |
| onChange={(e) => setAgentId(e.target.value)} | |
| disabled={isEdit} | |
| /> | |
| </div> | |
| <div className="space-y-2"> | |
| <Label htmlFor="display-name">Display Name</Label> | |
| <Input | |
| id="display-name" | |
| placeholder="Research Agent" | |
| value={displayName} | |
| onChange={(e) => setDisplayName(e.target.value)} | |
| /> | |
| </div> | |
| </div> | |
| <div className="space-y-2"> | |
| <Label htmlFor="persona">Persona</Label> | |
| <Textarea | |
| id="persona" | |
| placeholder="You are an expert researcher who gathers and analyzes information..." | |
| value={persona} | |
| onChange={(e) => setPersona(e.target.value)} | |
| rows={3} | |
| /> | |
| </div> | |
| <div className="space-y-2"> | |
| <Label htmlFor="description">Description</Label> | |
| <Input | |
| id="description" | |
| placeholder="Researches topics and gathers information" | |
| value={description} | |
| onChange={(e) => setDescription(e.target.value)} | |
| /> | |
| </div> | |
| <div className="grid grid-cols-2 gap-4"> | |
| <div className="space-y-2"> | |
| <Label htmlFor="llm-backbone">LLM Model</Label> | |
| <Input | |
| id="llm-backbone" | |
| placeholder="gpt-4, claude-3-opus, etc." | |
| value={llmBackbone} | |
| onChange={(e) => setLlmBackbone(e.target.value)} | |
| /> | |
| </div> | |
| <div className="space-y-2"> | |
| <Label htmlFor="tools">Tools (comma-separated)</Label> | |
| <Input | |
| id="tools" | |
| placeholder="web_search, code_interpreter" | |
| value={tools} | |
| onChange={(e) => setTools(e.target.value)} | |
| /> | |
| </div> | |
| </div> | |
| <div className="flex items-center gap-2"> | |
| <Switch checked={showLlmConfig} onCheckedChange={setShowLlmConfig} /> | |
| <Label>Advanced LLM Configuration</Label> | |
| </div> | |
| {showLlmConfig && ( | |
| <div className="grid grid-cols-2 gap-3 rounded-md border p-3"> | |
| <div className="space-y-1"> | |
| <Label className="text-xs">Model Name</Label> | |
| <Input | |
| placeholder="gpt-4" | |
| value={llmConfig.model_name || ""} | |
| onChange={(e) => setLlmConfig({ ...llmConfig, model_name: e.target.value || null })} | |
| /> | |
| </div> | |
| <div className="space-y-1"> | |
| <Label className="text-xs">Base URL</Label> | |
| <Input | |
| placeholder="https://api.openai.com/v1" | |
| value={llmConfig.base_url || ""} | |
| onChange={(e) => setLlmConfig({ ...llmConfig, base_url: e.target.value || null })} | |
| /> | |
| </div> | |
| <div className="space-y-1"> | |
| <Label className="text-xs">API Key</Label> | |
| <Input | |
| placeholder="$OPENAI_API_KEY" | |
| value={llmConfig.api_key || ""} | |
| onChange={(e) => setLlmConfig({ ...llmConfig, api_key: e.target.value || null })} | |
| /> | |
| </div> | |
| <div className="space-y-1"> | |
| <Label className="text-xs">Temperature</Label> | |
| <Input | |
| type="number" | |
| step="0.1" | |
| min="0" | |
| max="2" | |
| placeholder="0.7" | |
| value={llmConfig.temperature ?? ""} | |
| onChange={(e) => | |
| setLlmConfig({ | |
| ...llmConfig, | |
| temperature: e.target.value ? parseFloat(e.target.value) : null, | |
| }) | |
| } | |
| /> | |
| </div> | |
| <div className="space-y-1"> | |
| <Label className="text-xs">Max Tokens</Label> | |
| <Input | |
| type="number" | |
| placeholder="2000" | |
| value={llmConfig.max_tokens ?? ""} | |
| onChange={(e) => | |
| setLlmConfig({ | |
| ...llmConfig, | |
| max_tokens: e.target.value ? parseInt(e.target.value) : null, | |
| }) | |
| } | |
| /> | |
| </div> | |
| <div className="space-y-1"> | |
| <Label className="text-xs">Top P</Label> | |
| <Input | |
| type="number" | |
| step="0.05" | |
| min="0" | |
| max="1" | |
| placeholder="1.0" | |
| value={llmConfig.top_p ?? ""} | |
| onChange={(e) => | |
| setLlmConfig({ | |
| ...llmConfig, | |
| top_p: e.target.value ? parseFloat(e.target.value) : null, | |
| }) | |
| } | |
| /> | |
| </div> | |
| </div> | |
| )} | |
| {error && <p className="text-sm text-destructive">{error}</p>} | |
| </div> | |
| <DialogFooter> | |
| <Button variant="outline" onClick={() => onOpenChange(false)}> | |
| Cancel | |
| </Button> | |
| <Button onClick={handleSubmit} disabled={submitting}> | |
| {submitting ? "Saving..." : isEdit ? "Update" : "Create"} | |
| </Button> | |
| </DialogFooter> | |
| </DialogContent> | |
| </Dialog> | |
| ); | |
| } | |