feat: add model picker on non-ollama llm
This commit is contained in:
@ -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'),
|
||||
|
||||
@ -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>}
|
||||
|
||||
Reference in New Issue
Block a user