tabby-ai-assistant 1.0.8 → 1.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/dist/components/chat/ai-sidebar.component.d.ts +16 -3
  2. package/dist/components/chat/chat-input.component.d.ts +4 -0
  3. package/dist/components/chat/chat-interface.component.d.ts +22 -1
  4. package/dist/components/chat/chat-settings.component.d.ts +21 -11
  5. package/dist/components/settings/ai-settings-tab.component.d.ts +14 -4
  6. package/dist/components/settings/general-settings.component.d.ts +43 -12
  7. package/dist/components/settings/provider-config.component.d.ts +83 -5
  8. package/dist/components/settings/security-settings.component.d.ts +14 -4
  9. package/dist/i18n/index.d.ts +48 -0
  10. package/dist/i18n/translations/en-US.d.ts +5 -0
  11. package/dist/i18n/translations/ja-JP.d.ts +5 -0
  12. package/dist/i18n/translations/zh-CN.d.ts +5 -0
  13. package/dist/i18n/types.d.ts +198 -0
  14. package/dist/index.js +1 -1
  15. package/dist/services/chat/ai-sidebar.service.d.ts +23 -1
  16. package/dist/services/core/theme.service.d.ts +53 -0
  17. package/package.json +1 -1
  18. package/src/components/chat/ai-sidebar.component.scss +468 -0
  19. package/src/components/chat/ai-sidebar.component.ts +47 -344
  20. package/src/components/chat/chat-input.component.scss +2 -2
  21. package/src/components/chat/chat-input.component.ts +16 -5
  22. package/src/components/chat/chat-interface.component.html +11 -11
  23. package/src/components/chat/chat-interface.component.scss +410 -4
  24. package/src/components/chat/chat-interface.component.ts +105 -14
  25. package/src/components/chat/chat-message.component.scss +3 -3
  26. package/src/components/chat/chat-message.component.ts +3 -2
  27. package/src/components/chat/chat-settings.component.html +95 -61
  28. package/src/components/chat/chat-settings.component.scss +224 -50
  29. package/src/components/chat/chat-settings.component.ts +56 -30
  30. package/src/components/security/risk-confirm-dialog.component.scss +7 -7
  31. package/src/components/settings/ai-settings-tab.component.html +27 -27
  32. package/src/components/settings/ai-settings-tab.component.scss +34 -20
  33. package/src/components/settings/ai-settings-tab.component.ts +59 -20
  34. package/src/components/settings/general-settings.component.html +69 -40
  35. package/src/components/settings/general-settings.component.scss +151 -58
  36. package/src/components/settings/general-settings.component.ts +168 -55
  37. package/src/components/settings/provider-config.component.html +149 -60
  38. package/src/components/settings/provider-config.component.scss +273 -153
  39. package/src/components/settings/provider-config.component.ts +177 -19
  40. package/src/components/settings/security-settings.component.html +70 -39
  41. package/src/components/settings/security-settings.component.scss +104 -8
  42. package/src/components/settings/security-settings.component.ts +48 -10
  43. package/src/i18n/index.ts +129 -0
  44. package/src/i18n/translations/en-US.ts +193 -0
  45. package/src/i18n/translations/ja-JP.ts +193 -0
  46. package/src/i18n/translations/zh-CN.ts +193 -0
  47. package/src/i18n/types.ts +224 -0
  48. package/src/index.ts +6 -0
  49. package/src/services/chat/ai-sidebar.service.ts +157 -5
  50. package/src/services/core/theme.service.ts +480 -0
  51. package/src/styles/ai-assistant.scss +8 -88
  52. package/src/styles/themes.scss +161 -0
@@ -1,13 +1,17 @@
1
- import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core';
1
+ import { Component, Input, Output, EventEmitter, OnInit, OnDestroy, ViewEncapsulation } from '@angular/core';
2
+ import { Subject } from 'rxjs';
3
+ import { takeUntil } from 'rxjs/operators';
2
4
  import { ConfigProviderService } from '../../services/core/config-provider.service';
3
5
  import { LoggerService } from '../../services/core/logger.service';
6
+ import { TranslateService } from '../../i18n';
4
7
 
5
8
  @Component({
6
9
  selector: 'app-provider-config',
7
10
  templateUrl: './provider-config.component.html',
8
- styleUrls: ['./provider-config.component.scss']
11
+ styleUrls: ['./provider-config.component.scss'],
12
+ encapsulation: ViewEncapsulation.None
9
13
  })
10
- export class ProviderConfigComponent implements OnInit {
14
+ export class ProviderConfigComponent implements OnInit, OnDestroy {
11
15
  @Input() providerStatus: any = {};
12
16
  @Output() refreshStatus = new EventEmitter<void>();
13
17
  @Output() switchProvider = new EventEmitter<string>();
@@ -17,12 +21,20 @@ export class ProviderConfigComponent implements OnInit {
17
21
 
18
22
  selectedProvider = '';
19
23
  configs: { [key: string]: any } = {};
24
+ expandedProvider: string = '';
25
+ localStatus: { [key: string]: boolean } = {};
20
26
 
21
- // 预定义的提供商模板
22
- providerTemplates = {
27
+ // 翻译对象
28
+ t: any;
29
+
30
+ private destroy$ = new Subject<void>();
31
+
32
+ // 云端提供商模板
33
+ cloudProviderTemplates = {
23
34
  'openai': {
24
35
  name: 'OpenAI',
25
36
  description: 'OpenAI GPT模型',
37
+ icon: 'fa-robot',
26
38
  fields: [
27
39
  { key: 'apiKey', label: 'API Key', type: 'password', required: true },
28
40
  { key: 'baseURL', label: 'Base URL', type: 'text', default: 'https://api.openai.com/v1', required: false },
@@ -32,6 +44,7 @@ export class ProviderConfigComponent implements OnInit {
32
44
  'anthropic': {
33
45
  name: 'Anthropic Claude',
34
46
  description: 'Anthropic Claude模型',
47
+ icon: 'fa-comments',
35
48
  fields: [
36
49
  { key: 'apiKey', label: 'API Key', type: 'password', required: true },
37
50
  { key: 'baseURL', label: 'Base URL', type: 'text', default: 'https://api.anthropic.com', required: false },
@@ -41,6 +54,7 @@ export class ProviderConfigComponent implements OnInit {
41
54
  'minimax': {
42
55
  name: 'Minimax',
43
56
  description: 'Minimax AI模型',
57
+ icon: 'fa-brain',
44
58
  fields: [
45
59
  { key: 'apiKey', label: 'API Key', type: 'password', required: true },
46
60
  { key: 'baseURL', label: 'Base URL', type: 'text', default: 'https://api.minimaxi.com/anthropic', required: false },
@@ -50,6 +64,7 @@ export class ProviderConfigComponent implements OnInit {
50
64
  'glm': {
51
65
  name: 'GLM (ChatGLM)',
52
66
  description: '智谱AI ChatGLM模型',
67
+ icon: 'fa-network-wired',
53
68
  fields: [
54
69
  { key: 'apiKey', label: 'API Key', type: 'password', required: true },
55
70
  { key: 'baseURL', label: 'Base URL', type: 'text', default: 'https://open.bigmodel.cn/api/paas/v4', required: false },
@@ -58,13 +73,55 @@ export class ProviderConfigComponent implements OnInit {
58
73
  }
59
74
  };
60
75
 
76
+ // 本地提供商模板(不需要 API Key)
77
+ localProviderTemplates = {
78
+ 'ollama': {
79
+ name: 'Ollama (本地)',
80
+ description: '本地运行的 Ollama 服务,支持 Llama、Qwen 等模型',
81
+ icon: 'fa-server',
82
+ defaultURL: 'http://localhost:11434/v1',
83
+ fields: [
84
+ { key: 'baseURL', label: 'Base URL', type: 'text', default: 'http://localhost:11434/v1', required: true, placeholder: '例如: http://localhost:11434/v1' },
85
+ { key: 'model', label: 'Model', type: 'text', default: 'llama3.1', required: false, placeholder: '例如: llama3.1, qwen2.5, mistral' }
86
+ ]
87
+ },
88
+ 'vllm': {
89
+ name: 'vLLM (本地)',
90
+ description: '本地运行的 vLLM 服务,适合生产部署',
91
+ icon: 'fa-database',
92
+ defaultURL: 'http://localhost:8000/v1',
93
+ fields: [
94
+ { key: 'baseURL', label: 'Base URL', type: 'text', default: 'http://localhost:8000/v1', required: true, placeholder: '例如: http://localhost:8000/v1' },
95
+ { key: 'apiKey', label: 'API Key (可选)', type: 'password', required: false },
96
+ { key: 'model', label: 'Model', type: 'text', default: 'meta-llama/Llama-3.1-8B', required: false, placeholder: 'HuggingFace 模型路径' }
97
+ ]
98
+ }
99
+ };
100
+
61
101
  constructor(
62
102
  private config: ConfigProviderService,
63
- private logger: LoggerService
64
- ) { }
103
+ private logger: LoggerService,
104
+ private translate: TranslateService
105
+ ) {
106
+ this.t = this.translate.t;
107
+ }
65
108
 
66
109
  ngOnInit(): void {
110
+ // 监听语言变化
111
+ this.translate.translation$.pipe(
112
+ takeUntil(this.destroy$)
113
+ ).subscribe(translation => {
114
+ this.t = translation;
115
+ });
116
+
67
117
  this.loadConfigs();
118
+ // 检测本地供应商状态
119
+ this.checkLocalProviderStatus();
120
+ }
121
+
122
+ ngOnDestroy(): void {
123
+ this.destroy$.next();
124
+ this.destroy$.complete();
68
125
  }
69
126
 
70
127
  /**
@@ -76,6 +133,89 @@ export class ProviderConfigComponent implements OnInit {
76
133
  this.selectedProvider = this.config.getDefaultProvider();
77
134
  }
78
135
 
136
+ /**
137
+ * 切换展开/折叠
138
+ */
139
+ toggleExpand(providerName: string): void {
140
+ this.expandedProvider = this.expandedProvider === providerName ? '' : providerName;
141
+ }
142
+
143
+ /**
144
+ * 检查是否是本地提供商
145
+ */
146
+ isLocalProvider(providerName: string): boolean {
147
+ return providerName in this.localProviderTemplates;
148
+ }
149
+
150
+ /**
151
+ * 检测本地供应商状态
152
+ */
153
+ private async checkLocalProviderStatus(): Promise<void> {
154
+ const localUrls: { [key: string]: string } = {
155
+ 'ollama': 'http://localhost:11434/v1/models',
156
+ 'vllm': 'http://localhost:8000/v1/models'
157
+ };
158
+
159
+ for (const [name, url] of Object.entries(localUrls)) {
160
+ try {
161
+ const controller = new AbortController();
162
+ setTimeout(() => controller.abort(), 2000);
163
+
164
+ const response = await fetch(url, { signal: controller.signal });
165
+ this.localStatus[name] = response.ok;
166
+ } catch {
167
+ this.localStatus[name] = false;
168
+ }
169
+ }
170
+ }
171
+
172
+ /**
173
+ * 获取本地供应商在线状态
174
+ */
175
+ getLocalStatus(providerName: string): { text: string; color: string; icon: string } {
176
+ const isOnline = this.localStatus[providerName];
177
+ return isOnline
178
+ ? { text: '在线', color: '#4caf50', icon: 'fa-check-circle' }
179
+ : { text: '离线', color: '#f44336', icon: 'fa-times-circle' };
180
+ }
181
+
182
+ /**
183
+ * 测试本地提供商连接
184
+ */
185
+ async testLocalProvider(providerName: string): Promise<void> {
186
+ const template = this.localProviderTemplates[providerName];
187
+ const baseURL = this.configs[providerName]?.baseURL || template?.defaultURL;
188
+
189
+ if (!baseURL) {
190
+ alert(this.t.providers.baseURL + ': ' + this.t.providers.testError);
191
+ return;
192
+ }
193
+
194
+ const testingMessage = `${this.t.providers.testConnection} ${template.name}...`;
195
+ this.logger.info(testingMessage);
196
+
197
+ try {
198
+ const response = await fetch(`${baseURL}/models`, {
199
+ method: 'GET',
200
+ signal: AbortSignal.timeout(5000)
201
+ });
202
+
203
+ if (response.ok) {
204
+ alert(`✅ ${template.name}: ${this.t.providers.testSuccess}`);
205
+ this.localStatus[providerName] = true;
206
+ this.logger.info('Local provider test successful', { provider: providerName });
207
+ } else {
208
+ alert(`❌ ${this.t.providers.testFail}: ${response.status}`);
209
+ this.localStatus[providerName] = false;
210
+ }
211
+ } catch (error) {
212
+ const errorMessage = error instanceof Error ? error.message : this.t.providers.testError;
213
+ alert(`❌ ${template.name}\n\n${this.t.providers.testError}\n${errorMessage}`);
214
+ this.localStatus[providerName] = false;
215
+ this.logger.error('Local provider test failed', { provider: providerName, error: errorMessage });
216
+ }
217
+ }
218
+
79
219
  /**
80
220
  * 保存配置
81
221
  */
@@ -92,7 +232,11 @@ export class ProviderConfigComponent implements OnInit {
92
232
  */
93
233
  addProvider(providerName: string): void {
94
234
  if (!this.configs[providerName]) {
95
- const template = this.providerTemplates[providerName];
235
+ // 检查是云端还是本地提供商
236
+ let template = this.cloudProviderTemplates[providerName];
237
+ if (!template) {
238
+ template = this.localProviderTemplates[providerName];
239
+ }
96
240
  if (template) {
97
241
  const newConfig = {
98
242
  name: providerName,
@@ -110,7 +254,7 @@ export class ProviderConfigComponent implements OnInit {
110
254
  * 删除提供商
111
255
  */
112
256
  removeProvider(providerName: string): void {
113
- if (confirm(`确定要删除 ${this.providerTemplates[providerName]?.name} 的配置吗?`)) {
257
+ if (confirm(this.t.providers.deleteConfirm)) {
114
258
  delete this.configs[providerName];
115
259
  this.config.deleteProviderConfig(providerName);
116
260
  this.logger.info('Provider config removed', { provider: providerName });
@@ -133,7 +277,13 @@ export class ProviderConfigComponent implements OnInit {
133
277
  async testConnection(providerName: string): Promise<void> {
134
278
  const providerConfig = this.configs[providerName];
135
279
  if (!providerConfig) {
136
- alert('请先配置该提供商');
280
+ alert(this.t.providers.testError);
281
+ return;
282
+ }
283
+
284
+ // 本地提供商使用不同的测试方法
285
+ if (this.isLocalProvider(providerName)) {
286
+ await this.testLocalProvider(providerName);
137
287
  return;
138
288
  }
139
289
 
@@ -141,15 +291,15 @@ export class ProviderConfigComponent implements OnInit {
141
291
  const baseURL = providerConfig.baseURL;
142
292
 
143
293
  if (!apiKey) {
144
- alert('请先填写 API Key');
294
+ alert(this.t.providers.apiKey + ': ' + this.t.providers.testError);
145
295
  return;
146
296
  }
147
297
 
148
- const template = (this.providerTemplates as any)[providerName];
298
+ const template = this.cloudProviderTemplates[providerName];
149
299
  const providerDisplayName = template?.name || providerName;
150
300
 
151
301
  // 显示测试中状态
152
- const testingMessage = `正在测试 ${providerDisplayName} 的连接...`;
302
+ const testingMessage = `${this.t.providers.testConnection} ${providerDisplayName}...`;
153
303
  this.logger.info(testingMessage);
154
304
 
155
305
  try {
@@ -164,16 +314,16 @@ export class ProviderConfigComponent implements OnInit {
164
314
  });
165
315
 
166
316
  if (response.ok) {
167
- alert(`✅ ${providerDisplayName} 连接测试成功!`);
317
+ alert(`✅ ${this.t.providers.testSuccess}`);
168
318
  this.logger.info('Connection test successful', { provider: providerName });
169
319
  } else {
170
320
  const errorData = await response.text();
171
- alert(`❌ ${providerDisplayName} 连接测试失败\n\n状态码: ${response.status}\n错误信息: ${errorData.substring(0, 200)}`);
321
+ alert(`❌ ${this.t.providers.testFail}\n\nStatus: ${response.status}\n${errorData.substring(0, 200)}`);
172
322
  this.logger.error('Connection test failed', { provider: providerName, status: response.status });
173
323
  }
174
324
  } catch (error) {
175
- const errorMessage = error instanceof Error ? error.message : '未知错误';
176
- alert(`❌ ${providerDisplayName} 连接测试失败\n\n错误: ${errorMessage}`);
325
+ const errorMessage = error instanceof Error ? error.message : this.t.providers.testError;
326
+ alert(`❌ ${this.t.providers.testFail}\n\n${errorMessage}`);
177
327
  this.logger.error('Connection test error', { provider: providerName, error: errorMessage });
178
328
  }
179
329
  }
@@ -286,10 +436,18 @@ export class ProviderConfigComponent implements OnInit {
286
436
  }
287
437
 
288
438
  /**
289
- * 获取提供商模板
439
+ * 获取提供商模板(支持云端和本地)
290
440
  */
291
441
  getProviderTemplate(providerName: string): any {
292
- return this.providerTemplates[providerName];
442
+ return this.cloudProviderTemplates[providerName] || this.localProviderTemplates[providerName];
443
+ }
444
+
445
+ /**
446
+ * 获取提供商图标
447
+ */
448
+ getProviderIcon(providerName: string): string {
449
+ const template = this.getProviderTemplate(providerName);
450
+ return template?.icon || 'fa-cog';
293
451
  }
294
452
 
295
453
  /**
@@ -1,51 +1,82 @@
1
1
  <div class="security-settings">
2
- <h3>Security Settings</h3>
2
+ <h3>{{ t.security.title }}</h3>
3
3
 
4
- <div class="form-group">
5
- <label class="form-label">
6
- <input type="checkbox" [(ngModel)]="enablePasswordProtection">
7
- Enable Password Protection
8
- </label>
9
- <small class="form-text text-muted">
10
- Require password for high-risk commands
11
- </small>
4
+ <!-- 密码保护 -->
5
+ <div class="settings-section">
6
+ <h4>{{ t.security.accessControl }}</h4>
7
+ <div class="form-group">
8
+ <label class="form-label">
9
+ <input type="checkbox" [(ngModel)]="settings.enablePasswordProtection">
10
+ {{ t.security.passwordProtection }}
11
+ </label>
12
+ <small class="form-text text-muted">
13
+ {{ t.security.passwordProtectionDesc }}
14
+ </small>
15
+ </div>
16
+ <div class="form-group" *ngIf="settings.enablePasswordProtection">
17
+ <label class="form-label">{{ t.security.setPassword }}</label>
18
+ <input type="password" class="form-control" [(ngModel)]="password" [placeholder]="t.security.passwordPlaceholder">
19
+ </div>
12
20
  </div>
13
21
 
14
- <div class="form-group" *ngIf="enablePasswordProtection">
15
- <label class="form-label">Set Password</label>
16
- <input type="password" class="form-control" [(ngModel)]="password" placeholder="Enter password">
22
+ <!-- 风险评估 -->
23
+ <div class="settings-section">
24
+ <h4>{{ t.security.riskAssessment }}</h4>
25
+ <div class="form-group">
26
+ <label class="form-label">
27
+ <input type="checkbox" [(ngModel)]="settings.enableRiskAssessment">
28
+ {{ t.security.riskAssessment }}
29
+ </label>
30
+ <small class="form-text text-muted">
31
+ {{ t.security.riskAssessmentDesc }}
32
+ </small>
33
+ </div>
34
+ <div class="form-group">
35
+ <label class="form-label">{{ t.security.defaultRiskLevel }}</label>
36
+ <select class="form-control" [(ngModel)]="settings.defaultRiskLevel">
37
+ <option value="low">{{ t.security.riskLow }}</option>
38
+ <option value="medium">{{ t.security.riskMedium }}</option>
39
+ <option value="high">{{ t.security.riskHigh }}</option>
40
+ </select>
41
+ </div>
17
42
  </div>
18
43
 
19
- <div class="form-group">
20
- <label class="form-label">
21
- <input type="checkbox" [(ngModel)]="enableRiskAssessment">
22
- Enable Risk Assessment
23
- </label>
24
- <small class="form-text text-muted">
25
- Automatically assess command risk levels
26
- </small>
44
+ <!-- 用户授权 -->
45
+ <div class="settings-section">
46
+ <h4>{{ t.security.userConsent }}</h4>
47
+ <div class="form-group">
48
+ <label class="form-label">
49
+ <input type="checkbox" [(ngModel)]="settings.enableConsentPersistence">
50
+ {{ t.security.rememberConsent }}
51
+ </label>
52
+ <small class="form-text text-muted">
53
+ {{ t.security.rememberConsentDesc }} {{ settings.consentExpiryDays }} {{ t.security.consentExpiryDays }}
54
+ </small>
55
+ </div>
27
56
  </div>
28
57
 
29
- <div class="form-group">
30
- <label class="form-label">Default Risk Level</label>
31
- <select class="form-control" [(ngModel)]="defaultRiskLevel">
32
- <option value="low">Low</option>
33
- <option value="medium">Medium</option>
34
- <option value="high">High</option>
35
- </select>
58
+ <!-- 危险命令模式 -->
59
+ <div class="settings-section">
60
+ <h4>{{ t.security.dangerousPatterns }}</h4>
61
+ <div class="pattern-list">
62
+ <div *ngFor="let pattern of dangerousPatterns; let i = index" class="pattern-item">
63
+ <code>{{ pattern }}</code>
64
+ <button type="button" class="btn-icon" (click)="removeDangerousPattern(i)">
65
+ <i class="fa fa-times"></i>
66
+ </button>
67
+ </div>
68
+ </div>
69
+ <div class="form-group add-pattern">
70
+ <input type="text" class="form-control" [(ngModel)]="newPattern" [placeholder]="t.security.patternPlaceholder">
71
+ <button type="button" class="btn btn-secondary" (click)="addDangerousPattern(newPattern)">
72
+ {{ t.security.addPattern }}
73
+ </button>
74
+ </div>
36
75
  </div>
37
76
 
38
- <div class="form-group">
39
- <label class="form-label">
40
- <input type="checkbox" [(ngModel)]="enableConsentPersistence">
41
- Persist User Consent
42
- </label>
43
- <small class="form-text text-muted">
44
- Remember user consent for 30 days
45
- </small>
77
+ <!-- 操作按钮 -->
78
+ <div class="settings-actions">
79
+ <button class="btn btn-primary" (click)="saveSettings()">{{ t.common.save }}</button>
80
+ <button class="btn btn-outline" (click)="resetToDefaults()">{{ t.security.resetDefaults }}</button>
46
81
  </div>
47
-
48
- <button class="btn btn-primary" (click)="saveSettings()">
49
- Save Settings
50
- </button>
51
82
  </div>
@@ -2,17 +2,36 @@
2
2
  .security-settings {
3
3
  h3 {
4
4
  margin-bottom: 1.5rem;
5
- color: #333;
5
+ color: var(--ai-text-primary);
6
+ }
7
+
8
+ h4 {
9
+ font-size: 1.1rem;
10
+ margin-bottom: 1rem;
11
+ color: var(--ai-text-primary);
12
+ border-bottom: 1px solid var(--ai-border);
13
+ padding-bottom: 0.5rem;
14
+ }
15
+
16
+ .settings-section {
17
+ margin-bottom: 2rem;
18
+ padding-bottom: 1.5rem;
19
+ border-bottom: 1px solid var(--ai-border);
20
+
21
+ &:last-of-type {
22
+ border-bottom: none;
23
+ }
6
24
  }
7
25
 
8
26
  .form-group {
9
- margin-bottom: 1.5rem;
27
+ margin-bottom: 1.25rem;
10
28
  }
11
29
 
12
30
  .form-label {
13
31
  font-weight: 600;
14
32
  margin-bottom: 0.5rem;
15
33
  display: block;
34
+ color: var(--ai-text-primary);
16
35
 
17
36
  input[type="checkbox"] {
18
37
  margin-right: 0.5rem;
@@ -22,14 +41,16 @@
22
41
  .form-control {
23
42
  width: 100%;
24
43
  padding: 0.5rem;
25
- border: 1px solid #ccc;
44
+ border: 1px solid var(--ai-border);
26
45
  border-radius: 4px;
27
46
  font-size: 14px;
47
+ background-color: var(--ai-bg-primary);
48
+ color: var(--ai-text-primary);
28
49
 
29
50
  &:focus {
30
51
  outline: none;
31
- border-color: #007bff;
32
- box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
52
+ border-color: var(--ai-primary);
53
+ box-shadow: 0 0 0 0.2rem rgba(var(--ai-primary-rgb, 0, 123, 255), 0.25);
33
54
  }
34
55
  }
35
56
 
@@ -37,7 +58,61 @@
37
58
  display: block;
38
59
  margin-top: 0.25rem;
39
60
  font-size: 12px;
40
- color: #6c757d;
61
+ color: var(--ai-text-secondary);
62
+ }
63
+
64
+ /* 危险命令模式列表 */
65
+ .pattern-list {
66
+ margin-bottom: 1rem;
67
+ }
68
+
69
+ .pattern-item {
70
+ display: flex;
71
+ align-items: center;
72
+ justify-content: space-between;
73
+ padding: 0.5rem 0.75rem;
74
+ background-color: var(--ai-bg-secondary);
75
+ border-radius: 4px;
76
+ margin-bottom: 0.5rem;
77
+
78
+ code {
79
+ font-family: 'Consolas', 'Monaco', monospace;
80
+ color: var(--ai-danger);
81
+ font-size: 13px;
82
+ }
83
+ }
84
+
85
+ .btn-icon {
86
+ background: none;
87
+ border: none;
88
+ color: var(--ai-text-secondary);
89
+ cursor: pointer;
90
+ padding: 0.25rem 0.5rem;
91
+ border-radius: 3px;
92
+ transition: color 0.2s, background-color 0.2s;
93
+
94
+ &:hover {
95
+ color: var(--ai-danger);
96
+ background-color: rgba(var(--ai-danger-rgb, 220, 53, 69), 0.1);
97
+ }
98
+ }
99
+
100
+ .add-pattern {
101
+ display: flex;
102
+ gap: 0.5rem;
103
+
104
+ .form-control {
105
+ flex: 1;
106
+ }
107
+ }
108
+
109
+ /* 操作按钮 */
110
+ .settings-actions {
111
+ display: flex;
112
+ gap: 1rem;
113
+ margin-top: 2rem;
114
+ padding-top: 1.5rem;
115
+ border-top: 1px solid var(--ai-border);
41
116
  }
42
117
 
43
118
  .btn {
@@ -50,11 +125,11 @@
50
125
  transition: all 0.2s;
51
126
 
52
127
  &.btn-primary {
53
- background-color: #007bff;
128
+ background-color: var(--ai-primary);
54
129
  color: white;
55
130
 
56
131
  &:hover {
57
- background-color: #0056b3;
132
+ background-color: var(--ai-primary-hover);
58
133
  transform: translateY(-1px);
59
134
  }
60
135
 
@@ -62,5 +137,26 @@
62
137
  transform: translateY(0);
63
138
  }
64
139
  }
140
+
141
+ &.btn-secondary {
142
+ background-color: var(--ai-secondary);
143
+ color: white;
144
+
145
+ &:hover {
146
+ background-color: var(--ai-secondary);
147
+ filter: brightness(0.9);
148
+ }
149
+ }
150
+
151
+ &.btn-outline {
152
+ background-color: transparent;
153
+ border: 1px solid var(--ai-border);
154
+ color: var(--ai-text-secondary);
155
+
156
+ &:hover {
157
+ background-color: var(--ai-bg-secondary);
158
+ color: var(--ai-text-primary);
159
+ }
160
+ }
65
161
  }
66
162
  }