"use client" import { useMemo, useState } from "react" import { Plus, RefreshCw, Trash2 } from "lucide-react" import { OrgSettingsSection } from "@/components/admin/settings/org-settings-form" import { DeployLockedHint } from "@/components/admin/settings/deploy-locked-hint" import { useDeployFieldLocked } from "@/components/admin/settings/deploy-locked-hint" import { useOrgSettingsStore } from "@/lib/admin-settings/org-settings-store" import type { AiModelCatalogEntry } from "@/lib/admin-settings/org-settings-types" import { useDiscoverOrgLLMModels } from "@/lib/api/hooks/use-admin-llm" import { Label } from "@/components/ui/label" import { Switch } from "@/components/ui/switch" import { Button } from "@/components/ui/button" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Input } from "@/components/ui/input" import { Badge } from "@/components/ui/badge" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select" function emptyModelEntry(): AiModelCatalogEntry { return { model_id: "", label: "", enabled: true, } } export function AiAssistantSection() { const aiAssistant = useOrgSettingsStore((s) => s.aiAssistant) const setAiAssistant = useOrgSettingsStore((s) => s.setAiAssistant) const setPlugins = useOrgSettingsStore((s) => s.setPlugins) const plugins = useOrgSettingsStore((s) => s.plugins) const llm = useOrgSettingsStore((s) => s.llm) const effective = useOrgSettingsStore((s) => s.meta?.effective.ai_assistant) const enabledLocked = useDeployFieldLocked("ai_assistant", "enabled") const publicPathLocked = useDeployFieldLocked("ai_assistant", "public_path") const openwebuiLocked = useDeployFieldLocked("ai_assistant", "openwebui_internal_url") const pluginEnabled = plugins.find((p) => p.id === "ai-assistant")?.enabled ?? false const runtimeEnabled = effective?.enabled ?? false const orgEnabled = aiAssistant.enabled || pluginEnabled const [discoverProviderId, setDiscoverProviderId] = useState(llm.default_provider_id) const [discoveredModels, setDiscoveredModels] = useState([]) const discoverProvider = useMemo( () => llm.providers.find((p) => p.id === discoverProviderId) ?? llm.providers[0], [discoverProviderId, llm.providers], ) const discoverModels = useDiscoverOrgLLMModels() async function handleDiscoverModels() { if (!discoverProvider?.id) return setDiscoveredModels([]) try { const result = await discoverModels.mutateAsync(discoverProvider.id) setDiscoveredModels(result.models ?? []) } catch { setDiscoveredModels([]) } } function updateModel(index: number, patch: Partial) { const models = aiAssistant.models.map((entry, i) => i === index ? { ...entry, ...patch } : entry, ) setAiAssistant({ models }) } function removeModel(index: number) { setAiAssistant({ models: aiAssistant.models.filter((_, i) => i !== index) }) } function addManualModel() { setAiAssistant({ models: [...aiAssistant.models, emptyModelEntry()] }) } function addDiscoveredModel(modelId: string) { if (aiAssistant.models.some((entry) => entry.model_id === modelId)) return setAiAssistant({ models: [ ...aiAssistant.models, { model_id: modelId, label: modelId, enabled: true }, ], }) } function setUltiAIEnabled(enabled: boolean) { setAiAssistant({ enabled }) setPlugins( plugins.map((plugin) => plugin.id === "ai-assistant" ? { ...plugin, enabled } : plugin, ), ) } return (
Assistant IA Active le plugin UltiAI pour toute l'organisation. Le service OpenWebUI doit aussi être déployé.
{enabledLocked ? ( ) : null}
Politique org. {orgEnabled ? "activée" : "désactivée"} Runtime Compose {runtimeEnabled ? "actif" : "inactif"}
{!orgEnabled && !runtimeEnabled ? (

Activez le plugin UltiAI dans Administration → Plugins, ou définissez{" "} AI_ASSISTANT_ENABLED=true dans le déploiement, puis redémarrez le backend et OpenWebUI.

) : null}
setAiAssistant({ public_path: e.target.value })} placeholder="/ai" disabled={publicPathLocked} />
setAiAssistant({ openwebui_internal_url: e.target.value })} placeholder="http://openwebui:8080" disabled={openwebuiLocked} />
setAiAssistant({ default_model: e.target.value })} placeholder="gpt-4o" />
setAiAssistant({ chat_nc_path: e.target.value })} placeholder="/.ultimail/ai/chats" />

Les panneaux mail/drive/contacts ne sauvegardent pas l'historique.

setAiAssistant({ embed_default_temporary: v })} />

Pipeline OpenWebUI → fichiers .ultichat.json sur le drive utilisateur.

setAiAssistant({ chat_sync_enabled: v })} />
Modèles autorisés Liste vide = tous les modèles des fournisseurs LLM org. Sinon, seuls les modèles autorisés sont visibles pour les utilisateurs. Le surnom remplace le nom technique. {llm.providers.length === 0 ? (

Configurez d'abord un fournisseur LLM dans Administration → Fournisseurs LLM.

) : (
)} {discoverModels.isError ? (

{discoverModels.error instanceof Error ? discoverModels.error.message : "Impossible de lister les modèles sur ce fournisseur. Enregistrez d'abord le fournisseur LLM avec une clé API valide."}

) : null} {discoveredModels.length ? (
{discoveredModels.map((modelId) => { const alreadyAdded = aiAssistant.models.some( (entry) => entry.model_id === modelId, ) return ( ) })}
) : null}
{aiAssistant.models.length === 0 ? (

Aucune restriction — tous les modèles LLM configurés restent disponibles.

) : (
{aiAssistant.models.map((entry, index) => (
updateModel(index, { model_id: e.target.value })} placeholder="gpt-4o-mini" />
updateModel(index, { label: e.target.value })} placeholder="GPT-4o Mini" />
))}
)}
) }