"use client" import { useEffect, useState } from "react" import { ExternalLink, Plus, Trash2 } from "lucide-react" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select" import { FieldGroup } from "@/components/admin/settings/field-group" import { TechBrandSelectLabel } from "@/components/admin/settings/tech-brand-select-label" import { AutomationTabMasonry } from "@/components/gmail/settings/automation/automation-tab-masonry" import type { ApiLLMProvider, ApiLLMProviderType } from "@/lib/contacts/discovery-types" import { emptyLlmProvider, inferLlmProviderType, isLlmProviderConfigured, llmCatalogEntry, LLM_PROVIDER_CATALOG, normalizeLlmProvider, } from "@/lib/llm/llm-provider-catalog" import { cn } from "@/lib/utils" type LlmProviderSecrets = Record export type LlmProvidersEditorProps = { providers: ApiLLMProvider[] defaultProviderId: string onProvidersChange: (providers: ApiLLMProvider[]) => void onDefaultProviderIdChange: (id: string) => void className?: string columns?: 1 | 2 providerSecrets?: LlmProviderSecrets renderDefaultModelInput?: (props: { provider: ApiLLMProvider index: number onChange: (default_model: string) => void }) => React.ReactNode } function providerOptions( providers: ApiLLMProvider[], providerSecrets?: LlmProviderSecrets, ) { return providers.map((provider) => ({ provider: normalizeLlmProvider(provider), configured: isLlmProviderConfigured(provider, { apiKeyConfigured: providerSecrets?.[provider.id]?.configured, }), })) } export function LlmProvidersEditor({ providers, defaultProviderId, onProvidersChange, onDefaultProviderIdChange, className, columns = 2, providerSecrets, renderDefaultModelInput, }: LlmProvidersEditorProps) { const options = providerOptions(providers, providerSecrets) const [editingProviderId, setEditingProviderId] = useState(null) useEffect(() => { if (editingProviderId && !providers.some((provider) => provider.id === editingProviderId)) { setEditingProviderId(null) } }, [editingProviderId, providers]) function commit(nextProviders: ApiLLMProvider[]) { onProvidersChange(nextProviders.map(normalizeLlmProvider)) } function updateProvider(index: number, patch: Partial) { const next = [...providers] next[index] = { ...next[index], ...patch } commit(next) } function setProviderType(index: number, type: ApiLLMProviderType) { const current = providers[index] const entry = llmCatalogEntry(type) const next = { ...emptyLlmProvider(type), id: current?.id ?? emptyLlmProvider(type).id, api_key: current?.api_key ?? "", } const updated = [...providers] updated[index] = next commit(updated) } function addProvider() { const provider = emptyLlmProvider("openai") commit([...providers, provider]) setEditingProviderId(provider.id) if (!defaultProviderId) { onDefaultProviderIdChange(provider.id) } } function removeProvider(index: number) { const removed = providers[index] if (editingProviderId === removed?.id) { setEditingProviderId(null) } const nextProviders = providers.filter((_, i) => i !== index) const remaining = providerOptions(nextProviders, providerSecrets) let nextDefaultId = defaultProviderId if (nextDefaultId === removed?.id) { nextDefaultId = remaining.find((entry) => entry.configured)?.provider.id ?? nextProviders[0]?.id ?? "" } onProvidersChange(nextProviders.map(normalizeLlmProvider)) onDefaultProviderIdChange(nextDefaultId) } return (
{providers.length > 0 ? ( {options.some((entry) => !entry.configured) ? (

Les fournisseurs incomplets restent visibles mais ne peuvent pas être sélectionnés par défaut.

) : null}
) : null} {providers.map((provider, index) => { const normalized = normalizeLlmProvider(provider) const type = inferLlmProviderType(normalized) const entry = llmCatalogEntry(type) const apiKeyConfigured = providerSecrets?.[provider.id]?.configured ?? false const configured = isLlmProviderConfigured(provider, { apiKeyConfigured }) const isEditing = editingProviderId === provider.id const displayName = normalized.name || entry.label || `Fournisseur ${index + 1}` if (!isEditing) { return (
{displayName}
) } return (
{displayName} {!configured ? (

Configuration incomplète

) : apiKeyConfigured && !(provider.api_key ?? "").trim() ? (

Clé API enregistrée sur le serveur

) : null}

{entry.description}

{entry.docsUrl ? ( Documentation ) : null}
updateProvider(index, { name: e.target.value })} placeholder={entry.label} /> updateProvider(index, { base_url: e.target.value })} placeholder={entry.baseURLPlaceholder ?? entry.defaultBaseURL} /> {type !== "ollama" ? ( updateProvider(index, { api_key: e.target.value })} placeholder={ apiKeyConfigured && !(provider.api_key ?? "").trim() ? "•••••••• (laisser vide pour conserver)" : type === "openai" ? "sk-…" : undefined } /> ) : null} {renderDefaultModelInput ? ( renderDefaultModelInput({ provider, index, onChange: (default_model) => updateProvider(index, { default_model }), }) ) : ( updateProvider(index, { default_model: e.target.value })} placeholder={entry.defaultModel || "gpt-4o-mini"} /> )}
) })}
) } export { emptyLlmProvider }