UX decision (carnet #78): the human UI of IdeA is French by default, uniform per surface — proper nouns and technical acronyms (URL, LAN, IP/CIDR, HTTPS, API, CLI…) stay as-is. Settings mixed English (AI Profiles, Deployment) with French (Appareils, frozen by UX for #77). Renames the Settings menu/nav, the Profils IA and Déploiement panels (titles, actions, states, help text) to French, per the carnet's exhaustive list. Updates the affected tests accordingly. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
103 lines
2.8 KiB
TypeScript
103 lines
2.8 KiB
TypeScript
/**
|
|
* Minimal "Settings → AI Profiles" panel (L5). An always-available entry point
|
|
* to review the configured profiles and re-run the setup wizard after the first
|
|
* run. Kept intentionally small; richer per-profile editing reuses the wizard.
|
|
*
|
|
* Pure presentation over the {@link ProfileGateway} port (no `invoke()`).
|
|
*/
|
|
|
|
import { useCallback, useEffect, useState } from "react";
|
|
|
|
import type { AgentProfile, GatewayError } from "@/domain";
|
|
import { useGateways } from "@/app/di";
|
|
import { Button, Panel } from "@/shared";
|
|
import { FirstRunWizard } from "./FirstRunWizard";
|
|
|
|
export function ProfilesSettings() {
|
|
const { profile } = useGateways();
|
|
const [profiles, setProfiles] = useState<AgentProfile[]>([]);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [editing, setEditing] = useState(false);
|
|
|
|
const refresh = useCallback(async () => {
|
|
setError(null);
|
|
try {
|
|
setProfiles(await profile.listProfiles());
|
|
} catch (e) {
|
|
setError(
|
|
e && typeof e === "object" && "message" in e
|
|
? String((e as GatewayError).message)
|
|
: String(e),
|
|
);
|
|
}
|
|
}, [profile]);
|
|
|
|
useEffect(() => {
|
|
void refresh();
|
|
}, [refresh]);
|
|
|
|
async function del(id: string) {
|
|
await profile.deleteProfile(id);
|
|
await refresh();
|
|
}
|
|
|
|
if (editing) {
|
|
// Reopened after the first run, so force the wizard to render.
|
|
return (
|
|
<FirstRunWizard
|
|
forceOpen
|
|
onDone={() => {
|
|
setEditing(false);
|
|
void refresh();
|
|
}}
|
|
/>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Panel
|
|
aria-label="ai profiles settings"
|
|
title="Profils IA"
|
|
actions={
|
|
<Button size="sm" onClick={() => setEditing(true)}>
|
|
Configurer les profils
|
|
</Button>
|
|
}
|
|
>
|
|
<div className="flex flex-col gap-3">
|
|
{error && (
|
|
<p role="alert" className="text-sm text-danger">
|
|
{error}
|
|
</p>
|
|
)}
|
|
|
|
{profiles.length === 0 ? (
|
|
<p className="text-sm text-muted">Aucun profil configuré.</p>
|
|
) : (
|
|
<ul className="flex flex-col divide-y divide-border">
|
|
{profiles.map((p) => (
|
|
<li
|
|
key={p.id}
|
|
className="flex items-center justify-between gap-3 py-2 first:pt-0 last:pb-0"
|
|
>
|
|
<span className="flex items-baseline gap-2">
|
|
<strong className="text-sm text-content">{p.name}</strong>
|
|
<code className="text-xs text-muted">{p.command}</code>
|
|
</span>
|
|
<Button
|
|
size="sm"
|
|
variant="ghost"
|
|
aria-label={`supprimer ${p.name}`}
|
|
onClick={() => void del(p.id)}
|
|
>
|
|
Supprimer
|
|
</Button>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
)}
|
|
</div>
|
|
</Panel>
|
|
);
|
|
}
|