orchai/src/components/agents/AgentForm.tsx

187 lines
5.8 KiB
TypeScript

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";
import {
buttonClass,
inputClass,
labelClass,
noticeClass,
pageTitleClass,
textAreaClass,
} from "../ui/primitives";
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 ${pageTitleClass}`}>{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={noticeClass("info")}>
This is a default agent. Its tool and script/prompt can be modified, but its name and
role are fixed.
</div>
)}
<div>
<label className={labelClass}>Name</label>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
disabled={isEditing && isDefaultAgent}
required
className={inputClass}
/>
</div>
<div>
<label className={labelClass}>Role</label>
<select
value={role}
onChange={(e) => setRole(e.target.value as AgentRole)}
disabled={isEditing && isDefaultAgent}
className={inputClass}
>
<option value="analyst">Analyst</option>
<option value="developer">Developer</option>
</select>
</div>
<div>
<label className={labelClass}>Tool</label>
<select
value={tool}
onChange={(e) => setTool(e.target.value as AgentTool)}
className={inputClass}
>
<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={labelClass}>
Script / custom prompt (appended to built-in prompt)
</label>
<button
type="button"
onClick={() => void handleImprovePrompt()}
disabled={loading || initializing || improvingPrompt}
className={buttonClass({ variant: "success", size: "xs" })}
>
{improvingPrompt ? "Amélioration..." : "Amélioration"}
</button>
</div>
<textarea
rows={12}
value={customPrompt}
onChange={(e) => setCustomPrompt(e.target.value)}
disabled={loading || initializing || improvingPrompt}
className={`${textAreaClass} 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={noticeClass("error", true)}>
{error}
</div>
)}
<div className="flex gap-2">
<button
type="submit"
disabled={loading || initializing || improvingPrompt}
className={buttonClass({ variant: "primary" })}
>
{loading ? "Saving..." : isEditing ? "Save" : "Create"}
</button>
<button
type="button"
onClick={() => navigate(-1)}
className={buttonClass({ variant: "secondary" })}
>
Cancel
</button>
</div>
</form>
</div>
);
}