diff --git a/internal/ai/service.go b/internal/ai/service.go index 5686437..6d3e5d5 100644 --- a/internal/ai/service.go +++ b/internal/ai/service.go @@ -262,6 +262,9 @@ func (s *Service) Enabled() bool { // ReloadLLMProvider reloads a specific LLM provider configuration func (s *Service) ReloadLLMProvider(config interface{}) error { + if s == nil { + return nil + } if !s.enabled || s.LLMState == nil { return nil } @@ -274,6 +277,9 @@ func (s *Service) ReloadLLMProvider(config interface{}) error { // AddLLMProvider adds a new LLM provider func (s *Service) AddLLMProvider(config interface{}) error { + if s == nil { + return nil + } if !s.enabled || s.LLMState == nil { return nil } @@ -286,6 +292,9 @@ func (s *Service) AddLLMProvider(config interface{}) error { // RemoveLLMProvider removes an LLM provider func (s *Service) RemoveLLMProvider(name string) error { + if s == nil { + return nil + } if !s.enabled || s.LLMState == nil { return nil } diff --git a/internal/llmadmin/admin_llm_routes.go b/internal/llmadmin/admin_llm_routes.go index 54eae9f..ab0bf01 100644 --- a/internal/llmadmin/admin_llm_routes.go +++ b/internal/llmadmin/admin_llm_routes.go @@ -298,25 +298,30 @@ func handleCreateLLMProvider(store *storepkg.Store, aiService LLMProviderReloade } // Reload AI service with new provider - if aiService != nil { - providerConfig := map[string]interface{}{ - "Name": record.Name, - "Active": record.Active, - "APIKey": record.APIKey, - "BaseURL": record.BaseURL, - "Model": record.Model, - "Timeout": record.Timeout, - "ContextWindowTokens": record.ContextWindowTokens, - } - if err := aiService.AddLLMProvider(providerConfig); err != nil { - // Log warning but don't fail the request - database is already updated - c.JSON(http.StatusOK, gin.H{ - "status": "ok", - "item": llmProviderDTO(*record), - "warning": "provider created but failed to reload AI service: " + err.Error(), - }) - return - } + if aiService == nil { + c.JSON(http.StatusOK, gin.H{ + "status": "ok", + "item": llmProviderDTO(*record), + "warning": "AI 服务未运行,配置已保存但需重启服务后生效", + }) + return + } + providerConfig := map[string]interface{}{ + "Name": record.Name, + "Active": record.Active, + "APIKey": record.APIKey, + "BaseURL": record.BaseURL, + "Model": record.Model, + "Timeout": record.Timeout, + "ContextWindowTokens": record.ContextWindowTokens, + } + if err := aiService.AddLLMProvider(providerConfig); err != nil { + c.JSON(http.StatusOK, gin.H{ + "status": "ok", + "item": llmProviderDTO(*record), + "warning": "provider created but failed to reload AI service: " + err.Error(), + }) + return } c.JSON(http.StatusOK, gin.H{"status": "ok", "item": llmProviderDTO(*record)}) @@ -385,25 +390,30 @@ func handleUpdateLLMProvider(store *storepkg.Store, aiService LLMProviderReloade } // Reload AI service with updated provider - if aiService != nil { - providerConfig := map[string]interface{}{ - "Name": record.Name, - "Active": record.Active, - "APIKey": record.APIKey, - "BaseURL": record.BaseURL, - "Model": record.Model, - "Timeout": record.Timeout, - "ContextWindowTokens": record.ContextWindowTokens, - } - if err := aiService.ReloadLLMProvider(providerConfig); err != nil { - // Log warning but don't fail the request - database is already updated - c.JSON(http.StatusOK, gin.H{ - "status": "ok", - "item": llmProviderDTO(*record), - "warning": "provider updated but failed to reload AI service: " + err.Error(), - }) - return - } + if aiService == nil { + c.JSON(http.StatusOK, gin.H{ + "status": "ok", + "item": llmProviderDTO(*record), + "warning": "AI 服务未运行,配置已保存但需重启服务后生效", + }) + return + } + providerConfig := map[string]interface{}{ + "Name": record.Name, + "Active": record.Active, + "APIKey": record.APIKey, + "BaseURL": record.BaseURL, + "Model": record.Model, + "Timeout": record.Timeout, + "ContextWindowTokens": record.ContextWindowTokens, + } + if err := aiService.ReloadLLMProvider(providerConfig); err != nil { + c.JSON(http.StatusOK, gin.H{ + "status": "ok", + "item": llmProviderDTO(*record), + "warning": "provider updated but failed to reload AI service: " + err.Error(), + }) + return } c.JSON(http.StatusOK, gin.H{"status": "ok", "item": llmProviderDTO(*record)}) @@ -424,15 +434,19 @@ func handleDeleteLLMProvider(store *storepkg.Store, aiService LLMProviderReloade } // Remove provider from AI service - if aiService != nil { - if err := aiService.RemoveLLMProvider(name); err != nil { - // Log warning but don't fail the request - database is already updated - c.JSON(http.StatusOK, gin.H{ - "status": "ok", - "warning": "provider deleted but failed to reload AI service: " + err.Error(), - }) - return - } + if aiService == nil { + c.JSON(http.StatusOK, gin.H{ + "status": "ok", + "warning": "AI 服务未运行,配置已删除但需重启服务后生效", + }) + return + } + if err := aiService.RemoveLLMProvider(name); err != nil { + c.JSON(http.StatusOK, gin.H{ + "status": "ok", + "warning": "provider deleted but failed to reload AI service: " + err.Error(), + }) + return } c.JSON(http.StatusOK, gin.H{"status": "ok"}) diff --git a/main.go b/main.go index 6d1eea5..bfd4111 100644 --- a/main.go +++ b/main.go @@ -475,7 +475,11 @@ func run(cfg *configpkg.Config) error { return err } mqttStatus := webpkg.MQTTRuntimeStatus{Server: server, Address: mqttAddr, TLS: cfg.MQTT.TLS.Enabled, Stats: messageStats, ClientStats: clientStats, DBQueue: dbQueue, DedupQueue: mqttHook.dedupQueue} - handler := webpkg.NewRouter(cfg.Web, cfg.ConsoleLog.Web, store, sessions, mqttStatus, blocking, forwardManager, settings, botSender, aiService) + var llmReloader webpkg.LLMProviderReloader + if aiService != nil { + llmReloader = aiService + } + handler := webpkg.NewRouter(cfg.Web, cfg.ConsoleLog.Web, store, sessions, mqttStatus, blocking, forwardManager, settings, botSender, llmReloader) webAddresses := []string{} if cfg.Web.PortEnabled { httpServer := &http.Server{ diff --git a/meshmap_frontend/src/api.ts b/meshmap_frontend/src/api.ts index 6ec38ba..20067b8 100644 --- a/meshmap_frontend/src/api.ts +++ b/meshmap_frontend/src/api.ts @@ -515,8 +515,8 @@ export function updateLLMProvider(name: string, payload: Partial(`/api/admin/llm/providers/${encodeURIComponent(name)}`, payload) } -export function deleteLLMProvider(name: string): Promise<{ status: string }> { - return deleteJSON<{ status: string }>(`/api/admin/llm/providers/${encodeURIComponent(name)}`) +export function deleteLLMProvider(name: string): Promise<{ status: string; warning?: string }> { + return deleteJSON<{ status: string; warning?: string }>(`/api/admin/llm/providers/${encodeURIComponent(name)}`) } // LLM Tool Router API diff --git a/meshmap_frontend/src/components/AdminLLMApi.vue b/meshmap_frontend/src/components/AdminLLMApi.vue index 446fc45..c19bff2 100644 --- a/meshmap_frontend/src/components/AdminLLMApi.vue +++ b/meshmap_frontend/src/components/AdminLLMApi.vue @@ -17,6 +17,7 @@ import type { LLMPlatformRouter, LLMTopicConfig, LLMProvider, LLMPrimaryConfig } const loading = ref(false) const error = ref('') const success = ref('') +const showWarning = ref(false) // LLM Provider 相关 const providers = ref([]) @@ -169,12 +170,18 @@ async function saveProvider() { } try { + let response: any if (isCreatingProvider.value) { - await createLLMProvider(providerForm.value) - success.value = '创建成功' + response = await createLLMProvider(providerForm.value) } else if (editingProvider.value) { - await updateLLMProvider(editingProvider.value.name, providerForm.value) - success.value = '更新成功' + response = await updateLLMProvider(editingProvider.value.name, providerForm.value) + } + if (response.warning) { + success.value = response.warning + showWarning.value = true + } else { + success.value = isCreatingProvider.value ? '创建成功' : '更新成功' + showWarning.value = false } clearSuccess() closeProviderForm() @@ -189,8 +196,14 @@ async function confirmDeleteProvider(name: string) { return } try { - await deleteLLMProvider(name) - success.value = '删除成功' + const response = await deleteLLMProvider(name) + if (response.warning) { + success.value = response.warning + showWarning.value = true + } else { + success.value = '删除成功' + showWarning.value = false + } clearSuccess() await loadProviders() } catch (err) { @@ -299,7 +312,7 @@ onMounted(() => {

LLM API 配置管理

{{ error }}

-

{{ success }}

+

{{ success }}

@@ -973,6 +986,19 @@ onMounted(() => { gap: 0.5rem; } +.warning { + color: #92400e; + padding: 1rem 1.25rem; + background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%); + border-radius: 10px; + margin-bottom: 1.25rem; + border: 1px solid #fcd34d; + font-weight: 500; + display: flex; + align-items: center; + gap: 0.5rem; +} + .admin-loading { padding: 3rem; text-align: center; diff --git a/meshmap_frontend/src/types.ts b/meshmap_frontend/src/types.ts index 4af78b1..6e9cdab 100644 --- a/meshmap_frontend/src/types.ts +++ b/meshmap_frontend/src/types.ts @@ -644,6 +644,7 @@ export interface LLMProviderPayload { export interface LLMProviderResponse { item: LLMProvider + warning?: string } // LLM Tool Router 相关类型