feat(ui): édition templates — nom d'agent par défaut, permissions système et MCP outils — #242

TemplateEditor/TemplatesPanel étendus aux nouveaux champs, projection
useTemplates/adapters/ports, panneau Agents aligné, templatePresentation.ts
extrait. Tests frontend étendus (templates, agents, routage chat WS).
This commit is contained in:
2026-08-23 11:00:04 +02:00
parent d6c80be56d
commit f952807882
13 changed files with 551 additions and 174 deletions

View File

@ -20,6 +20,7 @@ import { Button, Input, Panel, SmallDropdown, Spinner, cn } from "@/shared";
import { TerminalView } from "@/features/terminals/TerminalView";
import { AnnouncementsPreview } from "@/features/announcements";
import { useDrift } from "@/features/templates/useDrift";
import { templateProfileLabel } from "@/features/templates/templatePresentation";
import { useGateways } from "@/app/di";
import { useAgents } from "./useAgents";
import { correlateModelServerStatus } from "./modelServerLaunch";
@ -203,7 +204,11 @@ export function AgentsPanel({ projectId, projectRoot = "" }: AgentsPanelProps) {
}
}
const canCreate = newName.trim().length > 0 && !vm.busy;
const selectedTemplate = templates.find((t) => t.id === newTemplateId) ?? null;
const selectedTemplateMissingProfile =
selectedTemplate !== null && selectedTemplate.defaultProfileId.trim().length === 0;
const canCreate =
newName.trim().length > 0 && !vm.busy && !selectedTemplateMissingProfile;
async function handleCreate(e: React.FormEvent) {
e.preventDefault();
@ -225,6 +230,14 @@ export function AgentsPanel({ projectId, projectRoot = "" }: AgentsPanelProps) {
setNewTemplateId("");
}
function handleTemplateChange(templateId: string) {
setNewTemplateId(templateId);
const next = templates.find((template) => template.id === templateId);
if (next?.defaultAgentName?.trim()) {
setNewName(next.defaultAgentName.trim());
}
}
function handleLaunch(agentId: string) {
setPanelNodeIds((prev) => ({
...prev,
@ -238,10 +251,9 @@ export function AgentsPanel({ projectId, projectRoot = "" }: AgentsPanelProps) {
setActiveAgentId(null);
}
const selectedAgent = vm.agents.find((a) => a.id === vm.selectedAgentId) ?? null;
// Determine if a template is chosen → profile selector is hidden (template imposes it).
const hasTemplate = newTemplateId !== "";
const selectedAgent = vm.agents.find((a) => a.id === vm.selectedAgentId) ?? null;
const profileLabel = (profile: AgentProfile): string => {
const model =
@ -297,7 +309,7 @@ export function AgentsPanel({ projectId, projectRoot = "" }: AgentsPanelProps) {
id="agent-template-select"
aria-label="agent template"
value={newTemplateId}
onChange={setNewTemplateId}
onChange={handleTemplateChange}
className="w-full"
disabled={vm.busy}
size="md"
@ -306,6 +318,28 @@ export function AgentsPanel({ projectId, projectRoot = "" }: AgentsPanelProps) {
...templates.map((t) => ({ value: t.id, label: t.name })),
]}
/>
{selectedTemplate && (
<div className="rounded-md border border-border bg-raised px-3 py-2 text-xs text-muted">
<div>
Nom initial:{" "}
<span className="text-content">
{selectedTemplate.defaultAgentName || selectedTemplate.name}
</span>
</div>
<div>
Modèle appliqué:{" "}
<span className="text-content">
{templateProfileLabel(selectedTemplate, vm.profiles)}
</span>
</div>
{selectedTemplateMissingProfile && (
<p role="alert" className="mt-1 text-danger">
Ce template n'a pas de modèle AI par défaut. Choisis un autre
template ou édite-le avant de créer l'agent.
</p>
)}
</div>
)}
</div>
{/* Profile selector — hidden when a template is chosen */}