187 lines
5.8 KiB
TypeScript
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'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>
|
|
);
|
|
}
|