feat: add model picker on non-ollama llm

This commit is contained in:
2026-04-21 09:17:48 +02:00
parent 2761282c0b
commit 985768f400
5 changed files with 82 additions and 10 deletions

View File

@ -37,6 +37,8 @@ export const adminApi = {
activateProvider: (id: string) => api.post<void>(`/admin/ai-providers/${id}/activate`),
deleteProvider: (id: string) => api.delete<void>(`/admin/ai-providers/${id}`),
listModels: (id: string) => api.get<string[]>(`/admin/ai-providers/${id}/models`),
probeModels: (data: { name: string; api_key?: string; endpoint?: string }) =>
api.post<string[]>('/admin/ai-providers/probe-models', data),
// AI Roles
getRoles: () => api.get<AIRoles>('/admin/ai-roles'),

View File

@ -214,6 +214,58 @@ function ModelTag({ fullName, size, installed, isPulling, pulling, onRequest }:
)
}
// ── Cloud model picker (OpenAI / Anthropic / Gemini / …) ──────────────────
function CloudModelPicker({ value, providerName, apiKey, endpoint, onChange }: {
value: string; providerName: string; apiKey: string; endpoint: string
onChange: (model: string) => void
}) {
const [models, setModels] = useState<string[]>([])
const [loading, setLoading] = useState(false)
const [loadError, setLoadError] = useState('')
async function loadModels() {
if (!apiKey) { setLoadError('Renseigne la clé API d\'abord'); return }
setLoading(true); setLoadError('')
try {
const list = await adminApi.probeModels({ name: providerName, api_key: apiKey, endpoint })
setModels(list ?? [])
if ((list ?? []).length === 0) setLoadError('Aucun modèle trouvé')
} catch (e) {
setLoadError(e instanceof Error ? e.message : 'Erreur')
} finally { setLoading(false) }
}
return (
<div className="space-y-1.5">
<div className="flex gap-2">
<Input
placeholder="gpt-4o-mini, claude-sonnet-4-6…"
value={value}
onChange={e => onChange(e.target.value)}
list="cloud-models-list"
className="flex-1"
/>
<Button type="button" variant="outline" size="sm" className="shrink-0" onClick={loadModels} disabled={loading}>
{loading ? <Spinner className="h-3 w-3" /> : 'Charger'}
</Button>
</div>
{models.length > 0 && (
<datalist id="cloud-models-list">
{models.map(m => <option key={m} value={m} />)}
</datalist>
)}
{models.length > 0 && (
<Select value={value} onChange={e => onChange(e.target.value)}>
<option value=""> Sélectionner un modèle </option>
{models.map(m => <option key={m} value={m}>{m}</option>)}
</Select>
)}
{loadError && <p className="text-xs text-destructive">{loadError}</p>}
</div>
)
}
// ── Ollama model picker (inside provider form) ─────────────────────────────
function OllamaModelPicker({ value, installedModels, onSelect, onRefresh }: {
@ -530,7 +582,13 @@ export function AIProviders() {
onSelect={m => setForm(f => ({ ...f, model: m }))}
onRefresh={loadOllamaModels} />
) : (
<Input placeholder="gpt-4o-mini, claude-sonnet-4-6…" value={form.model} onChange={e => setForm(f => ({ ...f, model: e.target.value }))} />
<CloudModelPicker
value={form.model}
providerName={form.name}
apiKey={form.api_key}
endpoint={form.endpoint}
onChange={m => setForm(f => ({ ...f, model: m }))}
/>
)}
</div>
{error && <p className="text-sm text-destructive">{error}</p>}