orchai/src/components/agents/AgentForm.tsx

180 lines
6.4 KiB
TypeScript
Raw Normal View History

import { useEffect, useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { createAgent, getAgent, improveAgentPrompt, updateAgent } from "../../lib/api";
import { getErrorMessage } from "../../lib/errors";
import type { AgentRole, AgentTool } from "../../lib/types";
export default function AgentForm() {
const navigate = useNavigate();
const { agentId } = useParams<{ agentId: string }>();
const isEditing = Boolean(agentId);
const [name, setName] = useState("");
const [role, setRole] = useState<AgentRole>("analyst");
const [tool, setTool] = useState<AgentTool>("codex");
const [customPrompt, setCustomPrompt] = useState("");
const [isDefaultAgent, setIsDefaultAgent] = useState(false);
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
const [initializing, setInitializing] = useState(false);
const [improvingPrompt, setImprovingPrompt] = useState(false);
useEffect(() => {
async function loadAgent() {
if (!agentId) return;
setInitializing(true);
setError(null);
try {
const agent = await getAgent(agentId);
setName(agent.name);
setRole(agent.role);
setTool(agent.tool);
setCustomPrompt(agent.custom_prompt);
setIsDefaultAgent(agent.is_default);
} catch (err: unknown) {
setError(getErrorMessage(err));
} finally {
setInitializing(false);
}
}
void loadAgent();
}, [agentId]);
async function handleSubmit(event: React.FormEvent) {
event.preventDefault();
setLoading(true);
setError(null);
try {
if (isEditing && agentId) {
await updateAgent(agentId, name, role, tool, customPrompt);
} else {
await createAgent(name, role, tool, customPrompt);
}
navigate("/agents");
} catch (err: unknown) {
setError(getErrorMessage(err));
} finally {
setLoading(false);
}
}
async function handleImprovePrompt() {
setImprovingPrompt(true);
setError(null);
try {
const improvedPrompt = await improveAgentPrompt(name, role, tool, customPrompt);
setCustomPrompt(improvedPrompt);
} catch (err: unknown) {
setError(getErrorMessage(err));
} finally {
setImprovingPrompt(false);
}
}
return (
<div className="mx-auto max-w-2xl p-8">
<h2 className="mb-6 text-xl font-bold">{isEditing ? "Edit agent" : "New agent"}</h2>
<form onSubmit={handleSubmit} className="space-y-4">
{initializing && <div className="text-sm text-gray-500">Loading agent...</div>}
{isEditing && isDefaultAgent && (
<div className="rounded border border-blue-200 bg-blue-50 p-3 text-sm text-blue-700">
This is a default agent. Its tool and script/prompt can be modified, but its name and
role are fixed.
</div>
)}
<div>
<label className="mb-1 block text-sm font-medium text-gray-700">Name</label>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
disabled={isEditing && isDefaultAgent}
required
className="w-full rounded border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
<div>
<label className="mb-1 block text-sm font-medium text-gray-700">Role</label>
<select
value={role}
onChange={(e) => setRole(e.target.value as AgentRole)}
disabled={isEditing && isDefaultAgent}
className="w-full rounded border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<option value="analyst">Analyst</option>
<option value="developer">Developer</option>
</select>
</div>
<div>
<label className="mb-1 block text-sm font-medium text-gray-700">Tool</label>
<select
value={tool}
onChange={(e) => setTool(e.target.value as AgentTool)}
className="w-full rounded border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<option value="codex">Codex</option>
<option value="claude_code">Claude Code</option>
</select>
</div>
<div>
<div className="mb-1 flex items-center justify-between gap-3">
<label className="block text-sm font-medium text-gray-700">
Script / custom prompt (appended to built-in prompt)
</label>
<button
type="button"
onClick={() => void handleImprovePrompt()}
disabled={loading || initializing || improvingPrompt}
className="rounded border border-emerald-300 bg-emerald-50 px-3 py-1 text-xs font-medium text-emerald-700 hover:bg-emerald-100 disabled:cursor-not-allowed disabled:opacity-50"
>
{improvingPrompt ? "Amélioration..." : "Amélioration"}
</button>
</div>
<textarea
rows={12}
value={customPrompt}
onChange={(e) => setCustomPrompt(e.target.value)}
disabled={loading || initializing || improvingPrompt}
className="w-full rounded border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-50"
placeholder="Extra instructions for this agent..."
/>
<p className="mt-1 text-xs text-gray-500">
Réécrit le prompt avec l&apos;outil sélectionné pour le rendre plus clair pour un LLM.
</p>
</div>
{error && (
<div className="rounded border border-red-200 bg-red-50 p-2 text-sm text-red-600">
{error}
</div>
)}
<div className="flex gap-2">
<button
type="submit"
disabled={loading || initializing || improvingPrompt}
className="rounded bg-blue-600 px-4 py-2 text-sm text-white hover:bg-blue-700 disabled:opacity-50"
>
{loading ? "Saving..." : isEditing ? "Save" : "Create"}
</button>
<button
type="button"
onClick={() => navigate(-1)}
className="rounded bg-gray-200 px-4 py-2 text-sm hover:bg-gray-300"
>
Cancel
</button>
</div>
</form>
</div>
);
}