import { Component, OnInit, inject, signal, output } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { HttpClient } from '@angular/common/http'; import { finalize } from 'rxjs'; interface ModelSettings { base_url: string; model: string; api_key_configured: boolean; timeout_seconds: number; max_retries: number; env_path: string; message?: string; } @Component({ selector: 'app-model-settings', standalone: true, imports: [FormsModule], template: `

统一使用 OpenAI Responses。保存后新任务立即生效,重启后自动加载。

填写接口根地址,通常包含 /v1,无需添加 /responses。
@if (loading()) {

正在加载配置…

} @if (message()) {

{{ message() }}

} @if (error()) { } @if (envPath()) { 保存位置:{{ envPath() }} }
`, styles: [` .model-settings { display: grid; gap: 10px; color: #1e293b; } p, small { margin: 0; line-height: 1.6; color: #64748b; } label { font-weight: 600; } label span { font-weight: 400; font-size: 12px; } input { box-sizing: border-box; width: 100%; padding: 12px; border-radius: 10px; border: 1px solid #64748b66; background: #f8fafc; color: inherit; font: inherit; } input: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; } .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: #047857; } .error { color: #be123c; } small { overflow-wrap: anywhere; } @media(max-width: 480px) { .number-fields { grid-template-columns: 1fr; } } `] }) export class ModelSettingsComponent implements OnInit { private http = inject(HttpClient); saved = output(); baseUrl = ''; model = ''; apiKey = ''; timeout = 120; retries = 0; busy = signal<'save' | 'test' | null>(null); loading = signal(true); keyConfigured = signal(false); envPath = signal(''); message = signal(''); error = signal(''); ngOnInit(): void { this.http.get('/api/system/model-settings').pipe(finalize(() => this.loading.set(false))).subscribe({ next: config => this.apply(config), error: () => this.error.set('配置加载失败,请刷新页面重试。') }); } valid(): boolean { return !!this.baseUrl.trim() && !!this.model.trim() && (!!this.apiKey.trim() || this.keyConfigured()) && this.timeout > 0 && this.timeout <= 3600 && Number.isInteger(this.retries) && this.retries >= 0 && this.retries <= 10; } private payload() { return { base_url: this.baseUrl.trim(), model: this.model.trim(), api_key: this.apiKey.trim() || undefined, timeout_seconds: this.timeout, max_retries: this.retries }; } private apply(config: ModelSettings): void { this.baseUrl = config.base_url; this.model = config.model; this.timeout = config.timeout_seconds; this.retries = config.max_retries; this.keyConfigured.set(config.api_key_configured); this.envPath.set(config.env_path); } save(): void { if (this.busy() || !this.valid()) return; this.busy.set('save'); this.message.set(''); this.error.set(''); this.http.post('/api/system/model-settings', this.payload()).pipe(finalize(() => this.busy.set(null))).subscribe({ next: config => { this.apply(config); this.apiKey = ''; this.message.set(config.message || '配置已保存。'); this.saved.emit(); }, error: err => this.error.set(typeof err.error?.detail === 'string' ? err.error.detail : '保存失败,请检查配置内容。') }); } test(): void { if (this.busy() || !this.valid()) return; this.busy.set('test'); this.message.set(''); this.error.set(''); this.http.post<{ message: string }>('/api/system/model-settings/test', this.payload()).pipe(finalize(() => this.busy.set(null))).subscribe({ next: result => this.message.set(result.message), error: err => this.error.set(typeof err.error?.detail === 'string' ? err.error.detail : '连接测试失败,请检查配置。') }); } }