feat(ai): improve AI settings first-time setup UX

- Add setup modal that appears when enabling AI without configured provider
- Modal allows selecting provider (Anthropic, OpenAI, DeepSeek, Ollama)
- Enter API key/URL and enable AI in one smooth flow
- Reorder backend to apply API keys before enabled check
- Fix Ollama to strip 'ollama:' prefix from model names
- Simplify backend error message for unconfigured providers
This commit is contained in:
rcourtman 2025-12-15 18:59:19 +00:00
parent fe20b2c55b
commit 43d658556b
3 changed files with 1085 additions and 885 deletions

File diff suppressed because it is too large Load diff

View file

@ -95,6 +95,10 @@ func (c *OllamaClient) Chat(ctx context.Context, req ChatRequest) (*ChatResponse
// Use provided model or fall back to client default
model := req.Model
// Strip "ollama:" prefix if present - callers may pass the full "provider:model" string
if strings.HasPrefix(model, "ollama:") {
model = strings.TrimPrefix(model, "ollama:")
}
if model == "" {
model = c.model
}

View file

@ -359,27 +359,41 @@ func (h *AISettingsHandler) HandleUpdateAISettings(w http.ResponseWriter, r *htt
settings.CustomContext = strings.TrimSpace(*req.CustomContext)
}
// Handle multi-provider credentials FIRST - before enabled check
// This allows the setup flow to send API key + enabled:true together
// Clear flags take priority over setting new values
if req.ClearAnthropicKey != nil && *req.ClearAnthropicKey {
settings.AnthropicAPIKey = ""
} else if req.AnthropicAPIKey != nil {
settings.AnthropicAPIKey = strings.TrimSpace(*req.AnthropicAPIKey)
}
if req.ClearOpenAIKey != nil && *req.ClearOpenAIKey {
settings.OpenAIAPIKey = ""
} else if req.OpenAIAPIKey != nil {
settings.OpenAIAPIKey = strings.TrimSpace(*req.OpenAIAPIKey)
}
if req.ClearDeepSeekKey != nil && *req.ClearDeepSeekKey {
settings.DeepSeekAPIKey = ""
} else if req.DeepSeekAPIKey != nil {
settings.DeepSeekAPIKey = strings.TrimSpace(*req.DeepSeekAPIKey)
}
if req.ClearOllamaURL != nil && *req.ClearOllamaURL {
settings.OllamaBaseURL = ""
} else if req.OllamaBaseURL != nil {
settings.OllamaBaseURL = strings.TrimSpace(*req.OllamaBaseURL)
}
if req.OpenAIBaseURL != nil {
settings.OpenAIBaseURL = strings.TrimSpace(*req.OpenAIBaseURL)
}
if req.Enabled != nil {
// Only allow enabling if at least one provider is configured
if *req.Enabled {
configuredProviders := settings.GetConfiguredProviders()
if len(configuredProviders) == 0 {
// Fall back to legacy validation for backwards compatibility
switch settings.Provider {
case config.AIProviderAnthropic, config.AIProviderOpenAI, config.AIProviderDeepSeek:
if settings.APIKey == "" {
http.Error(w, "Cannot enable AI: configure at least one AI provider first", http.StatusBadRequest)
return
}
case config.AIProviderOllama:
// Ollama doesn't need API key, but needs base URL (or will use default)
if settings.BaseURL == "" {
settings.BaseURL = config.DefaultOllamaBaseURL
}
default:
http.Error(w, "Cannot enable AI: configure at least one AI provider first", http.StatusBadRequest)
return
}
// No providers configured - give a helpful error
http.Error(w, "Please configure an AI provider (API key or Ollama URL) before enabling AI", http.StatusBadRequest)
return
}
// If we have configured providers, we're good to enable
}
@ -429,32 +443,6 @@ func (h *AISettingsHandler) HandleUpdateAISettings(w http.ResponseWriter, r *htt
settings.AlertTriggeredAnalysis = *req.AlertTriggeredAnalysis
}
// Handle multi-provider credentials
// Clear flags take priority over setting new values
if req.ClearAnthropicKey != nil && *req.ClearAnthropicKey {
settings.AnthropicAPIKey = ""
} else if req.AnthropicAPIKey != nil {
settings.AnthropicAPIKey = strings.TrimSpace(*req.AnthropicAPIKey)
}
if req.ClearOpenAIKey != nil && *req.ClearOpenAIKey {
settings.OpenAIAPIKey = ""
} else if req.OpenAIAPIKey != nil {
settings.OpenAIAPIKey = strings.TrimSpace(*req.OpenAIAPIKey)
}
if req.ClearDeepSeekKey != nil && *req.ClearDeepSeekKey {
settings.DeepSeekAPIKey = ""
} else if req.DeepSeekAPIKey != nil {
settings.DeepSeekAPIKey = strings.TrimSpace(*req.DeepSeekAPIKey)
}
if req.ClearOllamaURL != nil && *req.ClearOllamaURL {
settings.OllamaBaseURL = ""
} else if req.OllamaBaseURL != nil {
settings.OllamaBaseURL = strings.TrimSpace(*req.OllamaBaseURL)
}
if req.OpenAIBaseURL != nil {
settings.OpenAIBaseURL = strings.TrimSpace(*req.OpenAIBaseURL)
}
// Save settings
if err := h.persistence.SaveAIConfig(*settings); err != nil {
log.Error().Err(err).Msg("Failed to save AI settings")