ultisuite-client/components/admin/settings/sections/llm-section.tsx
2026-06-07 21:55:42 +02:00

178 lines
5.8 KiB
TypeScript

"use client"
import { useEffect, useState } from "react"
import { Plus, Trash2 } from "lucide-react"
import { OrgSettingsSection } from "@/components/admin/settings/org-settings-form"
import { useOrgSettingsStore } from "@/lib/admin-settings/org-settings-store"
import type { ApiLLMProvider } from "@/lib/contacts/discovery-types"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Switch } from "@/components/ui/switch"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
function emptyProvider(): ApiLLMProvider {
return {
id: crypto.randomUUID(),
name: "",
base_url: "https://api.openai.com/v1",
api_key: "",
default_model: "gpt-4o-mini",
}
}
export function LlmSection() {
const llm = useOrgSettingsStore((s) => s.llm)
const setLlm = useOrgSettingsStore((s) => s.setLlm)
const [draft, setDraft] = useState(llm)
useEffect(() => {
setDraft(llm)
}, [llm])
function updateProvider(index: number, patch: Partial<ApiLLMProvider>) {
setDraft((prev) => {
const providers = [...prev.providers]
providers[index] = { ...providers[index], ...patch }
return { ...prev, providers }
})
}
function addProvider() {
const p = emptyProvider()
setDraft((prev) => ({
...prev,
providers: [...prev.providers, p],
default_provider_id: prev.default_provider_id || p.id,
}))
}
function removeProvider(index: number) {
setDraft((prev) => {
const removed = prev.providers[index]
const providers = prev.providers.filter((_, i) => i !== index)
let defaultId = prev.default_provider_id
if (defaultId === removed?.id) defaultId = providers[0]?.id ?? ""
return { ...prev, providers, default_provider_id: defaultId }
})
}
return (
<OrgSettingsSection
title="Fournisseurs LLM"
description="Modèles IA organisationnels pour le tri, l'enrichissement contacts et les automatisations."
policySection="llm"
beforeSave={() => setLlm(draft)}
>
<div className="flex flex-wrap gap-4 rounded-lg border p-4">
<label className="flex flex-1 items-center justify-between gap-4">
<div>
<p className="text-sm font-medium">Imposer les fournisseurs org.</p>
<p className="text-xs text-muted-foreground">
Les utilisateurs ne peuvent pas utiliser d&apos;autres clés API.
</p>
</div>
<Switch
checked={draft.enforce_org_providers}
onCheckedChange={(enforce_org_providers) =>
setDraft((p) => ({ ...p, enforce_org_providers }))
}
/>
</label>
<label className="flex flex-1 items-center justify-between gap-4">
<div>
<p className="text-sm font-medium">Autoriser surcharge utilisateur</p>
</div>
<Switch
checked={draft.allow_user_override}
onCheckedChange={(allow_user_override) =>
setDraft((p) => ({ ...p, allow_user_override }))
}
/>
</label>
</div>
<div className="flex items-center justify-between">
<Label>Fournisseur par défaut</Label>
<Button variant="outline" size="sm" onClick={addProvider}>
<Plus className="mr-2 size-4" />
Ajouter
</Button>
</div>
{draft.providers.length > 0 ? (
<Select
value={draft.default_provider_id}
onValueChange={(default_provider_id) =>
setDraft((p) => ({ ...p, default_provider_id }))
}
>
<SelectTrigger className="h-9">
<SelectValue placeholder="Choisir…" />
</SelectTrigger>
<SelectContent>
{draft.providers.map((p) => (
<SelectItem key={p.id} value={p.id}>
{p.name || p.id}
</SelectItem>
))}
</SelectContent>
</Select>
) : null}
<div className="grid gap-4 lg:grid-cols-2">
{draft.providers.map((provider, index) => (
<div key={provider.id} className="space-y-3 rounded-lg border p-4">
<div className="flex items-center justify-between">
<span className="text-sm font-medium">
{provider.name || `Fournisseur ${index + 1}`}
</span>
<Button variant="ghost" size="icon" onClick={() => removeProvider(index)}>
<Trash2 className="size-4" />
</Button>
</div>
<div>
<Label className="text-xs">Nom</Label>
<Input
className="mt-1 h-9"
value={provider.name}
onChange={(e) => updateProvider(index, { name: e.target.value })}
/>
</div>
<div>
<Label className="text-xs">URL de base</Label>
<Input
className="mt-1 h-9"
value={provider.base_url}
onChange={(e) => updateProvider(index, { base_url: e.target.value })}
/>
</div>
<div>
<Label className="text-xs">Clé API</Label>
<Input
className="mt-1 h-9"
type="password"
value={provider.api_key ?? ""}
onChange={(e) => updateProvider(index, { api_key: e.target.value })}
/>
</div>
<div>
<Label className="text-xs">Modèle par défaut</Label>
<Input
className="mt-1 h-9"
value={provider.default_model}
onChange={(e) => updateProvider(index, { default_model: e.target.value })}
/>
</div>
</div>
))}
</div>
</OrgSettingsSection>
)
}