import { Component, OnInit, inject, signal, output } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { HttpClient } from '@angular/common/http'; import { finalize, timeout } from 'rxjs'; import { UI_READ_TIMEOUT_MS } from '../../core/http-timeouts'; import { SettingSliderComponent, REASONING_VALUES, REASONING_LABELS } from '../setting-slider/setting-slider.component'; import { ModelSelectorComponent } from '../model-selector/model-selector.component'; import { MODEL_PRESETS } from '../../core/model-presets'; interface ModelSettings { transport?: 'http' | 'websocket'; base_url: string; model: string; api_key_configured: boolean; timeout_seconds: number; max_retries: number; retry_interval_seconds: number; rate_limit_retry_seconds: number; tool_fallback_model: string; tool_fallback_reasoning_effort: string; tool_fallback_max_retries: number; tool_fallback_no_progress_attempts: number; reasoning_effort: string; model_presets: string[]; env_path: string; message?: string; } @Component({ selector: 'app-model-settings', standalone: true, imports: [FormsModule, SettingSliderComponent, ModelSelectorComponent], template: `
`, styles: [` .model-settings { display: grid; gap: 10px; color: var(--app-text-control, #1e293b); } p, small { margin: 0; line-height: 1.6; color: var(--app-text-muted, #64748b); } label { font-weight: 600; } label span { font-weight: 400; font-size: 12px; } input, select { box-sizing: border-box; width: 100%; padding: 12px; border-radius: 10px; border: 1px solid rgb(var(--app-border-control-rgb, 100 116 139) / 0.4000); background: var(--app-surface-soft, #f8fafc); color: inherit; font: inherit; } input:focus, select:focus { outline: 2px solid #38bdf8; } .number-fields { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-top: 8px; } .number-fields input { margin-top: 8px; } .recovery-settings { border: 1px solid var(--app-border-control, #cbd5e1); border-radius: 10px; background: var(--app-surface-soft, #f8fafc); padding: 10px 14px; } .recovery-settings summary { cursor: pointer; font-weight: 600; color: var(--app-text-control, #334155); } .recovery-content { display: grid; gap: 10px; padding-top: 12px; } .recovery-content label input, .recovery-content label select { margin-top: 8px; } .actions { display: flex; gap: 12px; margin-top: 8px; } button { padding: 11px 20px; border-radius: 10px; border: 1px solid #38bdf877; background: #0284c7; color: white; cursor: pointer; font: inherit; } button:disabled { opacity: .5; cursor: default; } .success { color: var(--app-success-text, #047857); } .error { color: var(--app-error-text, #be123c); } button:focus-visible { outline: 2px solid #0284c7; outline-offset: 2px; } small { overflow-wrap: anywhere; } @media(max-width: 480px) { .number-fields { grid-template-columns: 1fr; } } .model-settings {gap:12px;font-size:13px} label {margin-top:6px}input,select {background:var(--app-surface, #fff);border-color:var(--app-border, #dbe3ec);padding:11px 12px} input:focus,select:focus {outline:2px solid var(--app-primary-border, #a5b4fc);outline-offset:2px} .actions {border-top:1px solid var(--app-border, #e2e8f0);padding-top:16px;align-items:center}.actions button {min-height:40px;background:#4f46e5;border-color:#4f46e5;font-weight:600;border-radius:12px}.actions button[type=button] {background:var(--app-surface, #fff);border-color:var(--app-border, #dbe3ec);color:var(--app-text-secondary, #475569)} .recovery-settings {border-color:var(--app-border, #e2e8f0);border-radius:14px;padding:14px 16px}.recovery-settings summary {font-size:13px} `] }) export class ModelSettingsComponent implements OnInit { transport: 'http' | 'websocket' = 'websocket'; readonly reasoningValues = REASONING_VALUES; readonly reasoningLabels = REASONING_LABELS; private http = inject(HttpClient); saved = output