feat(settings): Add separate Auto-Fix Model setting for remediation
Add configurable model specifically for automatic remediation actions: Backend (internal/config/ai.go): - Add AutoFixModel field to AIConfig - Add GetAutoFixModel() getter with fallback chain: AutoFixModel -> PatrolModel -> Model Frontend (AISettings.tsx, types/ai.ts): - Add auto_fix_model to AISettings types - Add Auto-Fix Model dropdown (only shows when patrol_auto_fix enabled) - Falls back to patrol model if not set API (ai_handlers.go): - Add auto_fix_model to response and update request - Handle saving/loading the new field Rationale: - Auto-fix takes real actions, may warrant a more capable model - Patrol observation can use cheaper models for cost savings - Gives users granular control over model costs vs reliability - Model hierarchy: Chat > AutoFix > Patrol > Default
This commit is contained in:
parent
f98fd845e1
commit
de8b36d65d
4 changed files with 78 additions and 0 deletions
|
|
@ -79,6 +79,7 @@ export const AISettings: Component = () => {
|
|||
model: '',
|
||||
chatModel: '', // Empty means use default model
|
||||
patrolModel: '', // Empty means use default model
|
||||
autoFixModel: '', // Empty means use patrol model
|
||||
baseUrl: '', // Legacy - kept for compatibility
|
||||
clearApiKey: false,
|
||||
autonomousMode: false,
|
||||
|
|
@ -105,6 +106,7 @@ export const AISettings: Component = () => {
|
|||
model: '', // Will be set when provider is configured
|
||||
chatModel: '',
|
||||
patrolModel: '',
|
||||
autoFixModel: '',
|
||||
baseUrl: '',
|
||||
clearApiKey: false,
|
||||
autonomousMode: false,
|
||||
|
|
@ -130,6 +132,7 @@ export const AISettings: Component = () => {
|
|||
model: data.model || '', // User must select a model
|
||||
chatModel: data.chat_model || '',
|
||||
patrolModel: data.patrol_model || '',
|
||||
autoFixModel: data.auto_fix_model || '',
|
||||
baseUrl: data.base_url || '',
|
||||
clearApiKey: false,
|
||||
autonomousMode: data.autonomous_mode || false,
|
||||
|
|
@ -280,6 +283,10 @@ export const AISettings: Component = () => {
|
|||
payload.patrol_model = form.patrolModel;
|
||||
}
|
||||
|
||||
if (form.autoFixModel !== (settings()?.auto_fix_model || '')) {
|
||||
payload.auto_fix_model = form.autoFixModel;
|
||||
}
|
||||
|
||||
// Include multi-provider credentials if set (non-empty)
|
||||
if (form.anthropicApiKey.trim()) {
|
||||
payload.anthropic_api_key = form.anthropicApiKey.trim();
|
||||
|
|
@ -607,6 +614,48 @@ export const AISettings: Component = () => {
|
|||
</p>
|
||||
</div>
|
||||
|
||||
{/* Auto-Fix Model Override */}
|
||||
<Show when={form.patrolAutoFix}>
|
||||
<div class={formField}>
|
||||
<label class={labelClass()}>Auto-Fix Model (Remediation)</label>
|
||||
<Show when={availableModels().length > 0} fallback={
|
||||
<input
|
||||
type="text"
|
||||
value={form.autoFixModel}
|
||||
onInput={(e) => setForm('autoFixModel', e.currentTarget.value)}
|
||||
placeholder="Leave empty to use patrol model"
|
||||
class={controlClass()}
|
||||
disabled={saving()}
|
||||
/>
|
||||
}>
|
||||
<select
|
||||
value={form.autoFixModel}
|
||||
onChange={(e) => setForm('autoFixModel', e.currentTarget.value)}
|
||||
class={controlClass()}
|
||||
disabled={saving()}
|
||||
>
|
||||
<option value="">Use patrol model ({form.patrolModel || form.model || 'not set'})</option>
|
||||
<For each={Array.from(groupModelsByProvider(availableModels()).entries())}>
|
||||
{([provider, models]) => (
|
||||
<optgroup label={PROVIDER_DISPLAY_NAMES[provider] || provider}>
|
||||
<For each={models}>
|
||||
{(model) => (
|
||||
<option value={model.id}>
|
||||
{model.name || model.id.split(':').pop()}
|
||||
</option>
|
||||
)}
|
||||
</For>
|
||||
</optgroup>
|
||||
)}
|
||||
</For>
|
||||
</select>
|
||||
</Show>
|
||||
<p class={formHelpText}>
|
||||
Model for automatic remediation actions. Consider a more capable model since it takes action.
|
||||
</p>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
{/* AI Provider Configuration - Configure API keys for all providers */}
|
||||
<div class={`${formField} p-4 rounded-lg border bg-gradient-to-br from-purple-50 to-indigo-50 dark:from-purple-900/20 dark:to-indigo-900/20 border-purple-200 dark:border-purple-800`}>
|
||||
<div class="mb-3">
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ export interface AISettings {
|
|||
model: string;
|
||||
chat_model?: string; // Model for interactive chat (empty = use default)
|
||||
patrol_model?: string; // Model for background patrol (empty = use default)
|
||||
auto_fix_model?: string; // Model for auto-fix remediation (empty = use patrol model)
|
||||
base_url?: string; // DEPRECATED: legacy base URL
|
||||
configured: boolean; // true if AI is ready to use
|
||||
autonomous_mode: boolean; // true if AI can execute commands without approval
|
||||
|
|
@ -55,6 +56,7 @@ export interface AISettingsUpdateRequest {
|
|||
// Model overrides for different use cases
|
||||
chat_model?: string; // Model for interactive chat
|
||||
patrol_model?: string; // Model for background patrol
|
||||
auto_fix_model?: string; // Model for auto-fix remediation
|
||||
// Patrol settings for token efficiency
|
||||
patrol_schedule_preset?: string; // DEPRECATED: use patrol_interval_minutes
|
||||
patrol_interval_minutes?: number; // Custom interval in minutes (0 = disabled, minimum 10)
|
||||
|
|
|
|||
|
|
@ -144,6 +144,7 @@ type AISettingsResponse struct {
|
|||
Model string `json:"model"`
|
||||
ChatModel string `json:"chat_model,omitempty"` // Model for interactive chat (empty = use default)
|
||||
PatrolModel string `json:"patrol_model,omitempty"` // Model for patrol (empty = use default)
|
||||
AutoFixModel string `json:"auto_fix_model,omitempty"` // Model for auto-fix (empty = use patrol model)
|
||||
BaseURL string `json:"base_url,omitempty"` // DEPRECATED: legacy base URL
|
||||
Configured bool `json:"configured"` // true if AI is ready to use
|
||||
AutonomousMode bool `json:"autonomous_mode"` // true if AI can execute without approval
|
||||
|
|
@ -154,6 +155,7 @@ type AISettingsResponse struct {
|
|||
// Patrol settings for token efficiency
|
||||
PatrolSchedulePreset string `json:"patrol_schedule_preset"` // DEPRECATED: legacy preset
|
||||
PatrolIntervalMinutes int `json:"patrol_interval_minutes"` // Patrol interval in minutes (0 = disabled)
|
||||
PatrolAutoFix bool `json:"patrol_auto_fix"` // true if patrol can auto-fix issues
|
||||
AlertTriggeredAnalysis bool `json:"alert_triggered_analysis"` // true if AI analyzes when alerts fire
|
||||
AvailableModels []config.ModelInfo `json:"available_models"` // List of models for current provider
|
||||
// Multi-provider credentials - shows which providers are configured
|
||||
|
|
@ -176,6 +178,7 @@ type AISettingsUpdateRequest struct {
|
|||
Model *string `json:"model,omitempty"`
|
||||
ChatModel *string `json:"chat_model,omitempty"` // Model for interactive chat
|
||||
PatrolModel *string `json:"patrol_model,omitempty"` // Model for background patrol
|
||||
AutoFixModel *string `json:"auto_fix_model,omitempty"` // Model for auto-fix remediation
|
||||
BaseURL *string `json:"base_url,omitempty"` // DEPRECATED: use per-provider URLs
|
||||
AutonomousMode *bool `json:"autonomous_mode,omitempty"`
|
||||
CustomContext *string `json:"custom_context,omitempty"` // user-provided infrastructure context
|
||||
|
|
@ -183,6 +186,7 @@ type AISettingsUpdateRequest struct {
|
|||
// Patrol settings for token efficiency
|
||||
PatrolSchedulePreset *string `json:"patrol_schedule_preset,omitempty"` // DEPRECATED: use patrol_interval_minutes
|
||||
PatrolIntervalMinutes *int `json:"patrol_interval_minutes,omitempty"` // Custom interval in minutes (0 = disabled, minimum 10)
|
||||
PatrolAutoFix *bool `json:"patrol_auto_fix,omitempty"` // true if patrol can auto-fix issues
|
||||
AlertTriggeredAnalysis *bool `json:"alert_triggered_analysis,omitempty"` // true if AI analyzes when alerts fire
|
||||
// Multi-provider credentials
|
||||
AnthropicAPIKey *string `json:"anthropic_api_key,omitempty"` // Set Anthropic API key
|
||||
|
|
@ -230,6 +234,7 @@ func (h *AISettingsHandler) HandleGetAISettings(w http.ResponseWriter, r *http.R
|
|||
Model: settings.GetModel(),
|
||||
ChatModel: settings.ChatModel,
|
||||
PatrolModel: settings.PatrolModel,
|
||||
AutoFixModel: settings.AutoFixModel,
|
||||
BaseURL: settings.BaseURL,
|
||||
Configured: settings.IsConfigured(),
|
||||
AutonomousMode: settings.AutonomousMode,
|
||||
|
|
@ -239,6 +244,7 @@ func (h *AISettingsHandler) HandleGetAISettings(w http.ResponseWriter, r *http.R
|
|||
// Patrol settings
|
||||
PatrolSchedulePreset: settings.PatrolSchedulePreset,
|
||||
PatrolIntervalMinutes: settings.PatrolIntervalMinutes,
|
||||
PatrolAutoFix: settings.PatrolAutoFix,
|
||||
AlertTriggeredAnalysis: settings.AlertTriggeredAnalysis,
|
||||
AvailableModels: nil, // Now populated via /api/ai/models endpoint
|
||||
// Multi-provider configuration
|
||||
|
|
@ -332,6 +338,14 @@ func (h *AISettingsHandler) HandleUpdateAISettings(w http.ResponseWriter, r *htt
|
|||
settings.PatrolModel = strings.TrimSpace(*req.PatrolModel)
|
||||
}
|
||||
|
||||
if req.AutoFixModel != nil {
|
||||
settings.AutoFixModel = strings.TrimSpace(*req.AutoFixModel)
|
||||
}
|
||||
|
||||
if req.PatrolAutoFix != nil {
|
||||
settings.PatrolAutoFix = *req.PatrolAutoFix
|
||||
}
|
||||
|
||||
if req.BaseURL != nil {
|
||||
settings.BaseURL = strings.TrimSpace(*req.BaseURL)
|
||||
}
|
||||
|
|
@ -474,6 +488,7 @@ func (h *AISettingsHandler) HandleUpdateAISettings(w http.ResponseWriter, r *htt
|
|||
Model: settings.GetModel(),
|
||||
ChatModel: settings.ChatModel,
|
||||
PatrolModel: settings.PatrolModel,
|
||||
AutoFixModel: settings.AutoFixModel,
|
||||
BaseURL: settings.BaseURL,
|
||||
Configured: settings.IsConfigured(),
|
||||
AutonomousMode: settings.AutonomousMode,
|
||||
|
|
@ -482,6 +497,7 @@ func (h *AISettingsHandler) HandleUpdateAISettings(w http.ResponseWriter, r *htt
|
|||
OAuthConnected: settings.OAuthAccessToken != "",
|
||||
PatrolSchedulePreset: settings.PatrolSchedulePreset,
|
||||
PatrolIntervalMinutes: settings.PatrolIntervalMinutes,
|
||||
PatrolAutoFix: settings.PatrolAutoFix,
|
||||
AlertTriggeredAnalysis: settings.AlertTriggeredAnalysis,
|
||||
AvailableModels: nil, // Now populated via /api/ai/models endpoint
|
||||
// Multi-provider configuration
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ type AIConfig struct {
|
|||
PatrolAnalyzeDocker bool `json:"patrol_analyze_docker,omitempty"` // Include Docker hosts in patrol
|
||||
PatrolAnalyzeStorage bool `json:"patrol_analyze_storage,omitempty"` // Include storage in patrol
|
||||
PatrolAutoFix bool `json:"patrol_auto_fix,omitempty"` // When true, patrol can attempt automatic remediation (default: false, observe only)
|
||||
AutoFixModel string `json:"auto_fix_model,omitempty"` // Model for automatic remediation (defaults to PatrolModel, may want more capable model)
|
||||
|
||||
// Alert-triggered AI analysis - analyze specific resources when alerts fire
|
||||
AlertTriggeredAnalysis bool `json:"alert_triggered_analysis,omitempty"` // Enable AI analysis when alerts fire (token-efficient)
|
||||
|
|
@ -317,6 +318,16 @@ func (c *AIConfig) GetPatrolModel() string {
|
|||
return c.GetModel()
|
||||
}
|
||||
|
||||
// GetAutoFixModel returns the model for automatic remediation actions
|
||||
// Falls back to PatrolModel, then to the main Model if AutoFixModel is not set
|
||||
// Auto-fix may warrant a more capable model since it takes actions
|
||||
func (c *AIConfig) GetAutoFixModel() string {
|
||||
if c.AutoFixModel != "" {
|
||||
return c.AutoFixModel
|
||||
}
|
||||
return c.GetPatrolModel()
|
||||
}
|
||||
|
||||
// ClearOAuthTokens clears OAuth tokens (used when switching back to API key auth)
|
||||
func (c *AIConfig) ClearOAuthTokens() {
|
||||
c.OAuthAccessToken = ""
|
||||
|
|
|
|||
Loading…
Reference in a new issue