From 985768f40076ceccf3d5684d790f4fc277db5d0a Mon Sep 17 00:00:00 2001 From: Blomios Date: Tue, 21 Apr 2026 09:17:48 +0200 Subject: [PATCH] feat: add model picker on non-ollama llm --- backend/internal/ai/pipeline.go | 10 +--- backend/internal/api/handlers/admin.go | 19 ++++++++ backend/internal/api/router.go | 1 + frontend/src/api/admin.ts | 2 + frontend/src/pages/admin/AIProviders.tsx | 60 +++++++++++++++++++++++- 5 files changed, 82 insertions(+), 10 deletions(-) diff --git a/backend/internal/ai/pipeline.go b/backend/internal/ai/pipeline.go index 5d97170..d14c758 100644 --- a/backend/internal/ai/pipeline.go +++ b/backend/internal/ai/pipeline.go @@ -39,15 +39,7 @@ func (p *Pipeline) IsGenerating() bool { } func (p *Pipeline) BuildProvider(name, apiKey, endpoint string) (Provider, error) { - provider, err := p.repo.GetActiveAIProvider() - if err != nil { - return nil, err - } - model := "" - if provider != nil { - model = provider.Model - } - return NewProvider(name, apiKey, model, endpoint) + return NewProvider(name, apiKey, "", endpoint) } // buildProviderForRole resolves and builds the AI provider for a given task role. diff --git a/backend/internal/api/handlers/admin.go b/backend/internal/api/handlers/admin.go index 68a1808..251fb0c 100644 --- a/backend/internal/api/handlers/admin.go +++ b/backend/internal/api/handlers/admin.go @@ -174,6 +174,25 @@ func (h *Handler) DeleteAIProvider(c *gin.Context) { httputil.NoContent(c) } +func (h *Handler) ProbeAIModels(c *gin.Context) { + var req aiProviderRequest + if err := c.ShouldBindJSON(&req); err != nil { + httputil.BadRequest(c, err) + return + } + provider, err := h.pipeline.BuildProvider(req.Name, req.APIKey, req.Endpoint) + if err != nil { + httputil.InternalError(c, err) + return + } + models, err := provider.ListModels(c.Request.Context()) + if err != nil { + httputil.InternalError(c, err) + return + } + httputil.OK(c, models) +} + func (h *Handler) ListAIModels(c *gin.Context) { id := c.Param("id") p, err := h.repo.GetAIProviderByID(id) diff --git a/backend/internal/api/router.go b/backend/internal/api/router.go index a533b7a..41415a3 100644 --- a/backend/internal/api/router.go +++ b/backend/internal/api/router.go @@ -63,6 +63,7 @@ func SetupRouter(h *handlers.Handler, jwtSecret string) *gin.Engine { admin.POST("/ai-providers/:id/activate", h.SetActiveAIProvider) admin.DELETE("/ai-providers/:id", h.DeleteAIProvider) admin.GET("/ai-providers/:id/models", h.ListAIModels) + admin.POST("/ai-providers/probe-models", h.ProbeAIModels) admin.GET("/sources", h.ListSources) admin.PUT("/sources/:id", h.UpdateSource) diff --git a/frontend/src/api/admin.ts b/frontend/src/api/admin.ts index a80d1cd..f6d38e5 100644 --- a/frontend/src/api/admin.ts +++ b/frontend/src/api/admin.ts @@ -37,6 +37,8 @@ export const adminApi = { activateProvider: (id: string) => api.post(`/admin/ai-providers/${id}/activate`), deleteProvider: (id: string) => api.delete(`/admin/ai-providers/${id}`), listModels: (id: string) => api.get(`/admin/ai-providers/${id}/models`), + probeModels: (data: { name: string; api_key?: string; endpoint?: string }) => + api.post('/admin/ai-providers/probe-models', data), // AI Roles getRoles: () => api.get('/admin/ai-roles'), diff --git a/frontend/src/pages/admin/AIProviders.tsx b/frontend/src/pages/admin/AIProviders.tsx index b6871d7..fb45f29 100644 --- a/frontend/src/pages/admin/AIProviders.tsx +++ b/frontend/src/pages/admin/AIProviders.tsx @@ -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([]) + 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 ( +
+
+ onChange(e.target.value)} + list="cloud-models-list" + className="flex-1" + /> + +
+ {models.length > 0 && ( + + {models.map(m => + )} + {models.length > 0 && ( + + )} + {loadError &&

{loadError}

} +
+ ) +} + // ── 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} /> ) : ( - setForm(f => ({ ...f, model: e.target.value }))} /> + setForm(f => ({ ...f, model: m }))} + /> )} {error &&

{error}

}