gMAS / web_ui /frontend /src /components /agents /AgentForm.tsx
Артём Боярских
chore: initial commit
3193174
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>
);
}