import { useEffect, useState } from "react"; import { useStore } from "../store/useStore"; import { deleteModel, getBenchScores, getHardware, pullModel, runBench, } from "../api/client"; import type { AgentConfig, BenchResult, HardwareInfo } from "../api/client"; import { DownloadIcon, RefreshIcon } from "../components/Icon"; const TOOL_DESC: Record = { read_file: "Lire un fichier du projet", write_file: "Créer / modifier un fichier", edit_file: "Modification chirurgicale (recherche/remplacement)", list_dir: "Lister un répertoire", grep_search: "Chercher dans les fichiers", code_task: "Moteur code (multi-fichiers, git)", web_search: "Recherche web", run_shell: "Exécuter une commande", }; const SENSITIVE = new Set(["run_shell"]); /** Vue Configuration — Modèle & génération, outils, invite système. */ export function SettingsView() { const { models, selectedModel, setSelectedModel, refreshModels, loadedModels, warmingModel, warmError, status, config, availableTools, refreshConfig, updateConfig, } = useStore(); const [draft, setDraft] = useState(config); const [saved, setSaved] = useState(false); useEffect(() => { refreshConfig(); }, [refreshConfig, selectedModel]); useEffect(() => { if (config) setDraft(config); }, [config]); const [pullName, setPullName] = useState(""); const [pullStatus, setPullStatus] = useState(null); const [pullPct, setPullPct] = useState(0); const [deletingModel, setDeletingModel] = useState(null); const [deleteError, setDeleteError] = useState(null); const doDeleteModel = async (name: string) => { if (!window.confirm(`Supprimer définitivement le modèle ${name} ?`)) return; setDeletingModel(name); setDeleteError(null); try { await deleteModel(name); await refreshModels(); } catch (err) { setDeleteError(err instanceof Error ? err.message : "suppression impossible"); } finally { setDeletingModel(null); } }; const doPull = async () => { if (!pullName.trim()) return; setPullStatus("démarrage…"); setPullPct(0); try { await pullModel(pullName.trim(), (s, p) => { setPullStatus(s); if (p) setPullPct(p); }); setPullStatus("terminé"); await refreshModels(); } catch { setPullStatus("échec"); } }; const dirty = draft && config && JSON.stringify(draft) !== JSON.stringify(config); const save = async () => { if (!draft) return; await updateConfig(draft); setSaved(true); setTimeout(() => setSaved(false), 2000); }; const set = (key: K, value: AgentConfig[K]) => setDraft((d) => (d ? { ...d, [key]: value } : d)); const activeTools = draft ? Object.values(draft.tools).filter(Boolean).length : 0; return (
{/* En-tête */}
MODÈLE & GÉNÉRATION
Choisis le modèle local et règle son comportement. Les changements sont sauvegardés pour le modèle sélectionné.
{status?.connected ? "Ollama connecté" : "Ollama déconnecté"}
{!draft ? (
Chargement de la configuration…
) : ( <>
{/* Modèle Ollama */}
MODÈLE OLLAMA
{models.length === 0 && (
Aucun modèle installé. Télécharge-en un ci-dessous.
)} {models.map((m) => { const on = m.name === selectedModel; const loaded = loadedModels.find((item) => item.name === m.name); const warming = warmingModel === m.name; return (
); })} {deleteError && (
{deleteError}
)} {warmError && (
Préchargement : {warmError}
)}
TÉLÉCHARGER
setPullName(e.target.value)} placeholder="gemma2:9b" className="h-9 flex-1 border-[3px] border-line bg-card-soft px-3 text-[13px] text-ink outline-none placeholder:text-muted-3" />
{pullStatus && (
{pullName} {pullStatus} {pullPct ? `· ${pullPct}%` : ""}
)} {/* Génération + Outils */}
GÉNÉRATION
v.toFixed(2)} onChange={(v) => set("temperature", v)} /> v.toFixed(2)} onChange={(v) => set("top_p", v)} /> String(Math.round(v))} onChange={(v) => set("top_k", Math.round(v))} /> String(Math.round(v))} onChange={(v) => set("max_tokens", Math.round(v))} /> `${Math.round(v / 1024)}K`} onChange={(v) => set("num_ctx", Math.round(v))} /> (v < 0 ? "auto" : String(Math.round(v)))} onChange={(v) => set("num_gpu", Math.round(v))} /> String(Math.round(v))} onChange={(v) => set("num_batch", Math.round(v))} last />
Mode réflexion
Désactive si le modèle ne renvoie que du raisonnement sans réponse.
set("think", !draft.think)} />
La précision KV (f16/q8_0) est un réglage global du serveur Ollama et nécessite son redémarrage.
OUTILS
{activeTools} / {availableTools.length} actifs
{availableTools.map((name, i) => { const on = draft.tools[name] ?? false; return (
0 ? "border-t-2 border-line-soft" : "" }`} > {name} {TOOL_DESC[name] ?? ""} {SENSITIVE.has(name) && ( · sensible )} set("tools", { ...draft.tools, [name]: !on })} />
); })} {draft.tools.run_shell && (
Demander une validation avant chaque commande shell set("confirm_shell", !draft.confirm_shell)} />
)}
INTELLIGENCE
{[ ["plan_mode", "Plan-puis-exécute", "Décompose les demandes complexes en étapes"] as const, ["self_review", "Auto-critique (Qualité +)", "Relit et révise la réponse avant de la donner"] as const, ["rag_enabled", "Mémoire long-terme (RAG)", "Se souvient des anciennes sessions (modèle d'embedding requis)"] as const, ].map(([key, label, desc], i) => (
0 ? "border-t-2 border-line-soft" : "" }`} > {label} {desc} set(key, !draft[key])} />
))}
Maintien en VRAM (préchargement) Garde le modèle chargé pour des réponses instantanées
{/* Invite système */}
INVITE SYSTÈME
{draft.system_prompt.length} caractères