tabby-ai-assistant 1.0.5 → 1.0.6

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 (116) hide show
  1. package/dist/components/chat/ai-sidebar.component.d.ts +147 -0
  2. package/dist/components/chat/chat-interface.component.d.ts +38 -6
  3. package/dist/components/settings/general-settings.component.d.ts +6 -3
  4. package/dist/components/settings/provider-config.component.d.ts +25 -12
  5. package/dist/components/terminal/command-preview.component.d.ts +38 -0
  6. package/dist/index-full.d.ts +8 -0
  7. package/dist/index-minimal.d.ts +3 -0
  8. package/dist/index.d.ts +7 -3
  9. package/dist/index.js +1 -2
  10. package/dist/providers/tabby/ai-config.provider.d.ts +57 -5
  11. package/dist/providers/tabby/ai-hotkey.provider.d.ts +8 -14
  12. package/dist/providers/tabby/ai-toolbar-button.provider.d.ts +8 -9
  13. package/dist/services/chat/ai-sidebar.service.d.ts +89 -0
  14. package/dist/services/chat/chat-history.service.d.ts +78 -0
  15. package/dist/services/chat/chat-session.service.d.ts +57 -2
  16. package/dist/services/context/compaction.d.ts +90 -0
  17. package/dist/services/context/manager.d.ts +69 -0
  18. package/dist/services/context/memory.d.ts +116 -0
  19. package/dist/services/context/token-budget.d.ts +105 -0
  20. package/dist/services/core/ai-assistant.service.d.ts +40 -1
  21. package/dist/services/core/checkpoint.service.d.ts +130 -0
  22. package/dist/services/platform/escape-sequence.service.d.ts +132 -0
  23. package/dist/services/platform/platform-detection.service.d.ts +146 -0
  24. package/dist/services/providers/anthropic-provider.service.d.ts +5 -0
  25. package/dist/services/providers/base-provider.service.d.ts +6 -1
  26. package/dist/services/providers/glm-provider.service.d.ts +5 -0
  27. package/dist/services/providers/minimax-provider.service.d.ts +10 -1
  28. package/dist/services/providers/ollama-provider.service.d.ts +76 -0
  29. package/dist/services/providers/openai-compatible.service.d.ts +5 -0
  30. package/dist/services/providers/openai-provider.service.d.ts +5 -0
  31. package/dist/services/providers/vllm-provider.service.d.ts +82 -0
  32. package/dist/services/terminal/buffer-analyzer.service.d.ts +128 -0
  33. package/dist/services/terminal/terminal-manager.service.d.ts +185 -0
  34. package/dist/services/terminal/terminal-tools.service.d.ts +79 -0
  35. package/dist/types/ai.types.d.ts +92 -0
  36. package/dist/types/provider.types.d.ts +1 -1
  37. package/package.json +7 -10
  38. package/src/components/chat/ai-sidebar.component.ts +945 -0
  39. package/src/components/chat/chat-input.component.html +9 -24
  40. package/src/components/chat/chat-input.component.scss +3 -2
  41. package/src/components/chat/chat-interface.component.html +77 -69
  42. package/src/components/chat/chat-interface.component.scss +54 -4
  43. package/src/components/chat/chat-interface.component.ts +250 -34
  44. package/src/components/chat/chat-settings.component.scss +4 -4
  45. package/src/components/chat/chat-settings.component.ts +22 -11
  46. package/src/components/common/error-message.component.html +15 -0
  47. package/src/components/common/error-message.component.scss +77 -0
  48. package/src/components/common/error-message.component.ts +2 -96
  49. package/src/components/common/loading-spinner.component.html +4 -0
  50. package/src/components/common/loading-spinner.component.scss +57 -0
  51. package/src/components/common/loading-spinner.component.ts +2 -63
  52. package/src/components/security/consent-dialog.component.html +22 -0
  53. package/src/components/security/consent-dialog.component.scss +34 -0
  54. package/src/components/security/consent-dialog.component.ts +2 -55
  55. package/src/components/security/password-prompt.component.html +19 -0
  56. package/src/components/security/password-prompt.component.scss +30 -0
  57. package/src/components/security/password-prompt.component.ts +2 -54
  58. package/src/components/security/risk-confirm-dialog.component.html +8 -12
  59. package/src/components/security/risk-confirm-dialog.component.scss +8 -5
  60. package/src/components/security/risk-confirm-dialog.component.ts +6 -6
  61. package/src/components/settings/ai-settings-tab.component.html +16 -20
  62. package/src/components/settings/ai-settings-tab.component.scss +8 -5
  63. package/src/components/settings/ai-settings-tab.component.ts +12 -12
  64. package/src/components/settings/general-settings.component.html +8 -17
  65. package/src/components/settings/general-settings.component.scss +6 -3
  66. package/src/components/settings/general-settings.component.ts +62 -22
  67. package/src/components/settings/provider-config.component.html +19 -39
  68. package/src/components/settings/provider-config.component.scss +182 -39
  69. package/src/components/settings/provider-config.component.ts +119 -7
  70. package/src/components/settings/security-settings.component.scss +1 -1
  71. package/src/components/terminal/ai-toolbar-button.component.html +8 -0
  72. package/src/components/terminal/ai-toolbar-button.component.scss +20 -0
  73. package/src/components/terminal/ai-toolbar-button.component.ts +2 -30
  74. package/src/components/terminal/command-preview.component.html +61 -0
  75. package/src/components/terminal/command-preview.component.scss +72 -0
  76. package/src/components/terminal/command-preview.component.ts +127 -140
  77. package/src/components/terminal/command-suggestion.component.html +23 -0
  78. package/src/components/terminal/command-suggestion.component.scss +55 -0
  79. package/src/components/terminal/command-suggestion.component.ts +2 -77
  80. package/src/index-minimal.ts +32 -0
  81. package/src/index.ts +94 -11
  82. package/src/index.ts.backup +165 -0
  83. package/src/providers/tabby/ai-config.provider.ts +60 -51
  84. package/src/providers/tabby/ai-hotkey.provider.ts +23 -39
  85. package/src/providers/tabby/ai-settings-tab.provider.ts +2 -2
  86. package/src/providers/tabby/ai-toolbar-button.provider.ts +29 -24
  87. package/src/services/chat/ai-sidebar.service.ts +258 -0
  88. package/src/services/chat/chat-history.service.ts +308 -0
  89. package/src/services/chat/chat-history.service.ts.backup +239 -0
  90. package/src/services/chat/chat-session.service.ts +276 -3
  91. package/src/services/context/compaction.ts +483 -0
  92. package/src/services/context/manager.ts +442 -0
  93. package/src/services/context/memory.ts +519 -0
  94. package/src/services/context/token-budget.ts +422 -0
  95. package/src/services/core/ai-assistant.service.ts +280 -5
  96. package/src/services/core/ai-provider-manager.service.ts +2 -2
  97. package/src/services/core/checkpoint.service.ts +619 -0
  98. package/src/services/platform/escape-sequence.service.ts +499 -0
  99. package/src/services/platform/platform-detection.service.ts +494 -0
  100. package/src/services/providers/anthropic-provider.service.ts +28 -1
  101. package/src/services/providers/base-provider.service.ts +7 -1
  102. package/src/services/providers/glm-provider.service.ts +28 -1
  103. package/src/services/providers/minimax-provider.service.ts +209 -11
  104. package/src/services/providers/ollama-provider.service.ts +445 -0
  105. package/src/services/providers/openai-compatible.service.ts +9 -0
  106. package/src/services/providers/openai-provider.service.ts +9 -0
  107. package/src/services/providers/vllm-provider.service.ts +463 -0
  108. package/src/services/security/risk-assessment.service.ts +6 -2
  109. package/src/services/terminal/buffer-analyzer.service.ts +594 -0
  110. package/src/services/terminal/terminal-manager.service.ts +748 -0
  111. package/src/services/terminal/terminal-tools.service.ts +441 -0
  112. package/src/styles/ai-assistant.scss +78 -6
  113. package/src/types/ai.types.ts +144 -0
  114. package/src/types/provider.types.ts +1 -1
  115. package/tsconfig.json +9 -9
  116. package/webpack.config.js +28 -6
@@ -15,18 +15,18 @@ export class AiSettingsTabComponent implements OnInit {
15
15
  providerStatus: any = {};
16
16
 
17
17
  tabs = [
18
- { id: 'general', label: '基本设置', icon: 'icon-settings' },
19
- { id: 'providers', label: 'AI提供商', icon: 'icon-provider' },
20
- { id: 'security', label: '安全设置', icon: 'icon-shield' },
21
- { id: 'chat', label: '聊天设置', icon: 'icon-chat' },
22
- { id: 'advanced', label: '高级设置', icon: 'icon-advanced' }
18
+ { id: 'general', label: '基本设置', icon: 'fa fa-cog' },
19
+ { id: 'providers', label: 'AI提供商', icon: 'fa fa-cloud' },
20
+ { id: 'security', label: '安全设置', icon: 'fa fa-shield' },
21
+ { id: 'chat', label: '聊天设置', icon: 'fa fa-comments' },
22
+ { id: 'advanced', label: '高级设置', icon: 'fa fa-sliders' }
23
23
  ];
24
24
 
25
25
  constructor(
26
26
  private aiService: AiAssistantService,
27
27
  private config: ConfigProviderService,
28
28
  private logger: LoggerService
29
- ) {}
29
+ ) { }
30
30
 
31
31
  ngOnInit(): void {
32
32
  this.loadSettings();
@@ -95,13 +95,13 @@ export class AiSettingsTabComponent implements OnInit {
95
95
  */
96
96
  getProviderIcon(providerName: string): string {
97
97
  const icons: { [key: string]: string } = {
98
- 'openai': 'icon-openai',
99
- 'anthropic': 'icon-anthropic',
100
- 'minimax': 'icon-minimax',
101
- 'glm': 'icon-glm',
102
- 'openai-compatible': 'icon-compatible'
98
+ 'openai': 'fa fa-robot',
99
+ 'anthropic': 'fa fa-brain',
100
+ 'minimax': 'fa fa-microchip',
101
+ 'glm': 'fa fa-language',
102
+ 'openai-compatible': 'fa fa-plug'
103
103
  };
104
- return icons[providerName] || 'icon-provider';
104
+ return icons[providerName] || 'fa fa-cloud';
105
105
  }
106
106
 
107
107
  /**
@@ -5,11 +5,7 @@
5
5
  <div class="settings-section">
6
6
  <h4>功能状态</h4>
7
7
  <div class="form-check">
8
- <input
9
- type="checkbox"
10
- id="enabled"
11
- [(ngModel)]="isEnabled"
12
- (change)="updateEnabled(isEnabled)">
8
+ <input type="checkbox" id="enabled" [(ngModel)]="isEnabled" (change)="updateEnabled(isEnabled)">
13
9
  <label for="enabled">
14
10
  <strong>启用AI助手</strong>
15
11
  <p class="form-description">启用或禁用整个AI助手功能</p>
@@ -23,13 +19,11 @@
23
19
  <div class="provider-selector">
24
20
  <label>选择默认提供商</label>
25
21
  <div class="providers-grid">
26
- <div
27
- *ngFor="let provider of availableProviders"
28
- class="provider-card"
22
+ <div *ngFor="let provider of availableProviders" class="provider-card"
29
23
  [class.selected]="selectedProvider === provider.name"
30
24
  (click)="updateDefaultProvider(provider.name)">
31
25
  <div class="provider-header">
32
- <i class="icon-provider"></i>
26
+ <i class="fa fa-cloud"></i>
33
27
  <h5>{{ provider.displayName }}</h5>
34
28
  </div>
35
29
  <div class="provider-body">
@@ -61,13 +55,10 @@
61
55
  <div class="form-group">
62
56
  <label>主题</label>
63
57
  <div class="theme-selector">
64
- <button
65
- *ngFor="let theme of themes"
66
- class="theme-btn"
67
- [class.active]="this.theme === theme.value"
68
- (click)="updateTheme(theme.value)">
69
- <div class="theme-preview" [attr.data-theme]="theme.value"></div>
70
- <span>{{ theme.label }}</span>
58
+ <button *ngFor="let themeOption of themes" class="theme-btn"
59
+ [class.active]="theme === themeOption.value" (click)="updateTheme(themeOption.value)" type="button">
60
+ <div class="theme-preview" [attr.data-theme]="themeOption.value"></div>
61
+ <span>{{ themeOption.label }}</span>
71
62
  </button>
72
63
  </div>
73
64
  </div>
@@ -100,4 +91,4 @@
100
91
  </div>
101
92
  </div>
102
93
  </div>
103
- </div>
94
+ </div>
@@ -235,12 +235,15 @@
235
235
 
236
236
  kbd {
237
237
  padding: 0.25rem 0.5rem;
238
- background-color: var(--ai-bg-primary);
239
- border: 1px solid var(--ai-border);
238
+ background-color: var(--ai-bg-primary, #2a2a40);
239
+ border: 1px solid var(--ai-border, #444);
240
240
  border-radius: 0.25rem;
241
241
  font-size: 0.75rem;
242
242
  font-family: monospace;
243
243
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
244
+ color: var(--ai-dark, #e0e0e0);
245
+ display: inline-block;
246
+ margin: 0 0.125rem;
244
247
  }
245
248
  }
246
249
  }
@@ -282,4 +285,4 @@
282
285
  }
283
286
  }
284
287
  }
285
- }
288
+ }
@@ -2,6 +2,7 @@ import { Component, Output, EventEmitter, OnInit } from '@angular/core';
2
2
  import { AiAssistantService } from '../../services/core/ai-assistant.service';
3
3
  import { ConfigProviderService } from '../../services/core/config-provider.service';
4
4
  import { LoggerService } from '../../services/core/logger.service';
5
+ import { ConfigService } from 'tabby-core';
5
6
 
6
7
  @Component({
7
8
  selector: 'app-general-settings',
@@ -12,10 +13,10 @@ export class GeneralSettingsComponent implements OnInit {
12
13
  @Output() providerChanged = new EventEmitter<string>();
13
14
 
14
15
  availableProviders: any[] = [];
15
- selectedProvider = '';
16
- isEnabled = true;
17
- language = 'zh-CN';
18
- theme = 'auto';
16
+ selectedProvider: string = '';
17
+ isEnabled: boolean = true;
18
+ language: string = 'zh-CN';
19
+ theme: string = 'auto';
19
20
 
20
21
  languages = [
21
22
  { value: 'zh-CN', label: '简体中文' },
@@ -29,33 +30,53 @@ export class GeneralSettingsComponent implements OnInit {
29
30
  { value: 'dark', label: '深色主题' }
30
31
  ];
31
32
 
33
+ // 提供商模板,用于显示名称
34
+ private providerNames: { [key: string]: string } = {
35
+ 'openai': 'OpenAI',
36
+ 'anthropic': 'Anthropic Claude',
37
+ 'minimax': 'Minimax',
38
+ 'glm': 'GLM (ChatGLM)'
39
+ };
40
+
32
41
  constructor(
33
42
  private aiService: AiAssistantService,
34
43
  private config: ConfigProviderService,
44
+ private tabbyConfig: ConfigService,
35
45
  private logger: LoggerService
36
- ) {}
46
+ ) { }
37
47
 
38
48
  ngOnInit(): void {
39
49
  this.loadSettings();
40
50
  this.loadProviders();
51
+ // 应用当前主题
52
+ this.applyTheme(this.theme);
41
53
  }
42
54
 
43
55
  /**
44
56
  * 加载设置
45
57
  */
46
58
  private loadSettings(): void {
47
- this.selectedProvider = this.config.getDefaultProvider();
48
- this.isEnabled = this.config.isEnabled();
49
- this.language = this.config.get('language', 'zh-CN');
50
- this.theme = this.config.get('theme', 'auto');
59
+ this.selectedProvider = this.config.getDefaultProvider() || '';
60
+ this.isEnabled = this.config.isEnabled() ?? true;
61
+ this.language = this.config.get('language', 'zh-CN') || 'zh-CN';
62
+ this.theme = this.config.get('theme', 'auto') || 'auto';
51
63
  }
52
64
 
53
65
  /**
54
- * 加载可用提供商
66
+ * 加载可用提供商 - 从配置服务读取已配置的提供商
55
67
  */
56
68
  private loadProviders(): void {
57
- const status = this.aiService.getProviderStatus();
58
- this.availableProviders = status.all || [];
69
+ const allConfigs = this.config.getAllProviderConfigs();
70
+ this.availableProviders = Object.keys(allConfigs)
71
+ .filter(key => allConfigs[key] && allConfigs[key].apiKey)
72
+ .map(key => ({
73
+ name: key,
74
+ displayName: allConfigs[key].displayName || this.providerNames[key] || key,
75
+ description: `已配置的 ${this.providerNames[key] || key} 提供商`,
76
+ enabled: allConfigs[key].enabled !== false
77
+ }));
78
+
79
+ this.logger.info('Loaded providers from config', { count: this.availableProviders.length });
59
80
  }
60
81
 
61
82
  /**
@@ -97,16 +118,28 @@ export class GeneralSettingsComponent implements OnInit {
97
118
  }
98
119
 
99
120
  /**
100
- * 应用主题
121
+ * 应用主题 - 同步 Tabby 主题或手动设置
101
122
  */
102
123
  private applyTheme(theme: string): void {
103
- const body = document.body;
104
- body.classList.remove('light-theme', 'dark-theme');
124
+ const root = document.documentElement;
125
+
126
+ // 移除现有主题类
127
+ root.classList.remove('ai-theme-light', 'ai-theme-dark');
128
+ document.body.classList.remove('ai-theme-light', 'ai-theme-dark');
105
129
 
106
- if (theme === 'light') {
107
- body.classList.add('light-theme');
108
- } else if (theme === 'dark') {
109
- body.classList.add('dark-theme');
130
+ if (theme === 'auto') {
131
+ // 跟随 Tabby 主题
132
+ const tabbyTheme = this.tabbyConfig.store?.appearance?.theme || 'dark';
133
+ const isDark = tabbyTheme.toLowerCase().includes('dark');
134
+ const themeClass = isDark ? 'ai-theme-dark' : 'ai-theme-light';
135
+ root.classList.add(themeClass);
136
+ document.body.classList.add(themeClass);
137
+ } else if (theme === 'light') {
138
+ root.classList.add('ai-theme-light');
139
+ document.body.classList.add('ai-theme-light');
140
+ } else {
141
+ root.classList.add('ai-theme-dark');
142
+ document.body.classList.add('ai-theme-dark');
110
143
  }
111
144
  }
112
145
 
@@ -114,10 +147,17 @@ export class GeneralSettingsComponent implements OnInit {
114
147
  * 获取提供商状态
115
148
  */
116
149
  getProviderStatus(providerName: string): { text: string; color: string } {
117
- // TODO: 实现真实的健康检查
150
+ const providerConfig = this.config.getProviderConfig(providerName);
151
+ if (providerConfig && providerConfig.apiKey) {
152
+ return {
153
+ text: providerConfig.enabled !== false ? '已启用' : '已禁用',
154
+ color: providerConfig.enabled !== false ? '#4caf50' : '#ff9800'
155
+ };
156
+ }
118
157
  return {
119
- text: '正常',
120
- color: 'var(--ai-success)'
158
+ text: '未配置',
159
+ color: '#9e9e9e'
121
160
  };
122
161
  }
123
162
  }
163
+
@@ -3,34 +3,24 @@
3
3
 
4
4
  <!-- 可用提供商列表 -->
5
5
  <div class="providers-list">
6
- <div
7
- *ngFor="let providerName of Object.keys(providerTemplates)"
8
- class="provider-item"
6
+ <div *ngFor="let providerName of Object.keys(providerTemplates)" class="provider-item"
9
7
  [class.configured]="hasConfig(providerName)">
10
8
  <div class="provider-info">
11
9
  <h4>{{ providerTemplates[providerName].name }}</h4>
12
10
  <p>{{ providerTemplates[providerName].description }}</p>
13
11
  </div>
14
12
  <div class="provider-actions">
15
- <button
16
- *ngIf="!hasConfig(providerName)"
17
- class="btn btn-primary"
18
- (click)="addProvider(providerName)">
19
- <i class="icon-plus"></i>
13
+ <button *ngIf="!hasConfig(providerName)" class="btn btn-primary" (click)="addProvider(providerName)">
14
+ <i class="fa fa-plus"></i>
20
15
  添加
21
16
  </button>
22
- <button
23
- *ngIf="hasConfig(providerName)"
24
- class="btn btn-secondary"
17
+ <button *ngIf="hasConfig(providerName)" class="btn btn-secondary"
25
18
  (click)="testConnection(providerName)">
26
- <i class="icon-test"></i>
19
+ <i class="fa fa-plug"></i>
27
20
  测试
28
21
  </button>
29
- <button
30
- *ngIf="hasConfig(providerName)"
31
- class="btn btn-danger"
32
- (click)="removeProvider(providerName)">
33
- <i class="icon-trash"></i>
22
+ <button *ngIf="hasConfig(providerName)" class="btn btn-danger" (click)="removeProvider(providerName)">
23
+ <i class="fa fa-trash"></i>
34
24
  删除
35
25
  </button>
36
26
  </div>
@@ -40,20 +30,15 @@
40
30
  <div class="form-row">
41
31
  <div class="form-group">
42
32
  <label>显示名称</label>
43
- <input
44
- type="text"
45
- class="form-control"
46
- [(ngModel)]="configs[providerName].displayName">
33
+ <input type="text" class="form-control" [(ngModel)]="configs[providerName].displayName">
47
34
  </div>
48
35
  <div class="form-group">
49
36
  <label>状态</label>
50
37
  <div class="toggle-switch">
51
- <input
52
- type="checkbox"
53
- [id]="'enabled-' + providerName"
38
+ <input type="checkbox" [id]="'enabled-' + providerName"
54
39
  [checked]="configs[providerName].enabled"
55
40
  (change)="toggleProviderEnabled(providerName)">
56
- <label [for]="'enabled-' + providerName]">
41
+ <label [for]="'enabled-' + providerName">
57
42
  {{ configs[providerName].enabled ? '已启用' : '已禁用' }}
58
43
  </label>
59
44
  </div>
@@ -65,18 +50,13 @@
65
50
  {{ field.label }}
66
51
  <span *ngIf="isRequired(field)" class="required">*</span>
67
52
  </label>
68
- <input
69
- *ngIf="getFieldType(field) === 'text'"
70
- [type]="isPasswordField(field) ? 'password' : 'text'"
71
- class="form-control"
72
- [placeholder]="field.placeholder || ''"
73
- [value]="getConfigValue(providerName, field.key, field.default)"
74
- (input)="updateConfigValue(providerName, field.key, $any($event.target).value)">
75
- <select
76
- *ngIf="getFieldType(field) === 'select'"
77
- class="form-control"
78
- [value]="getConfigValue(providerName, field.key, field.default)"
79
- (change)="updateConfigValue(providerName, field.key, $any($event.target).value)">
53
+ <!-- 文本和密码输入框 -->
54
+ <input *ngIf="getFieldType(field) === 'text' || getFieldType(field) === 'password'"
55
+ [type]="isPasswordField(field) ? 'password' : 'text'" class="form-control"
56
+ [placeholder]="field.placeholder || ''" [(ngModel)]="configs[providerName][field.key]">
57
+ <!-- 下拉选择框 -->
58
+ <select *ngIf="getFieldType(field) === 'select'" class="form-control"
59
+ [(ngModel)]="configs[providerName][field.key]">
80
60
  <option *ngFor="let option of getFieldOptions(field)" [value]="option">
81
61
  {{ option }}
82
62
  </option>
@@ -85,11 +65,11 @@
85
65
 
86
66
  <div class="form-actions">
87
67
  <button class="btn btn-primary" (click)="saveConfig(providerName)">
88
- <i class="icon-save"></i>
68
+ <i class="fa fa-save"></i>
89
69
  保存配置
90
70
  </button>
91
71
  </div>
92
72
  </div>
93
73
  </div>
94
74
  </div>
95
- </div>
75
+ </div>
@@ -1,60 +1,203 @@
1
1
  /* Provider Configuration Styles */
2
2
  .provider-config {
3
- .form-group {
4
- margin-bottom: 1rem;
5
- }
3
+ max-width: 900px;
6
4
 
7
- .form-label {
5
+ h3 {
6
+ margin: 0 0 1.5rem 0;
7
+ font-size: 1.25rem;
8
8
  font-weight: 600;
9
- margin-bottom: 0.5rem;
10
- display: block;
9
+ color: var(--ai-dark);
10
+ }
11
+
12
+ .providers-list {
13
+ display: flex;
14
+ flex-direction: column;
15
+ gap: 1.5rem;
11
16
  }
12
17
 
13
- .form-control {
14
- width: 100%;
15
- padding: 0.5rem;
16
- border: 1px solid #ccc;
17
- border-radius: 4px;
18
+ .provider-item {
19
+ padding: 1.5rem;
20
+ background-color: var(--ai-bg-secondary);
21
+ border-radius: 0.5rem;
22
+ border: 1px solid var(--ai-border);
18
23
 
19
- &:focus {
20
- outline: none;
21
- border-color: #007bff;
22
- box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
24
+ &.configured {
25
+ border-color: var(--ai-primary);
26
+ border-width: 2px;
27
+ }
28
+
29
+ .provider-info {
30
+ margin-bottom: 1rem;
31
+
32
+ h4 {
33
+ margin: 0 0 0.5rem 0;
34
+ font-size: 1.125rem;
35
+ font-weight: 600;
36
+ color: var(--ai-dark);
37
+ }
38
+
39
+ p {
40
+ margin: 0;
41
+ font-size: 0.875rem;
42
+ color: var(--ai-secondary);
43
+ }
23
44
  }
24
- }
25
45
 
26
- .btn {
27
- padding: 0.5rem 1rem;
28
- border: none;
29
- border-radius: 4px;
30
- cursor: pointer;
31
- transition: background-color 0.2s;
46
+ .provider-actions {
47
+ display: flex;
48
+ gap: 0.75rem;
49
+ margin-bottom: 1rem;
32
50
 
33
- &.btn-primary {
34
- background-color: #007bff;
35
- color: white;
51
+ .btn {
52
+ padding: 0.5rem 1rem;
53
+ border: none;
54
+ border-radius: 0.375rem;
55
+ cursor: pointer;
56
+ transition: all 0.2s;
57
+ display: inline-flex;
58
+ align-items: center;
59
+ gap: 0.375rem;
60
+ font-size: 0.875rem;
61
+ font-weight: 500;
36
62
 
37
- &:hover {
38
- background-color: #0056b3;
63
+ &.btn-primary {
64
+ background-color: var(--ai-primary);
65
+ color: white;
66
+
67
+ &:hover {
68
+ background-color: #0056b3;
69
+ }
70
+ }
71
+
72
+ &.btn-secondary {
73
+ background-color: var(--ai-secondary);
74
+ color: white;
75
+
76
+ &:hover {
77
+ background-color: #5a6268;
78
+ }
79
+ }
80
+
81
+ &.btn-danger {
82
+ background-color: var(--ai-danger);
83
+ color: white;
84
+
85
+ &:hover {
86
+ background-color: #c82333;
87
+ }
88
+ }
39
89
  }
40
90
  }
41
91
  }
42
92
 
43
- .alert {
44
- padding: 0.75rem;
45
- border-radius: 4px;
46
- margin-bottom: 1rem;
93
+ .config-form {
94
+ margin-top: 1rem;
95
+ padding-top: 1rem;
96
+ border-top: 1px solid var(--ai-border);
47
97
 
48
- &.alert-danger {
49
- background-color: #f8d7da;
50
- border: 1px solid #f5c6cb;
51
- color: #721c24;
98
+ .form-row {
99
+ display: grid;
100
+ grid-template-columns: 1fr 1fr;
101
+ gap: 1rem;
102
+ margin-bottom: 1rem;
52
103
  }
53
104
 
54
- &.alert-success {
55
- background-color: #d4edda;
56
- border: 1px solid #c3e6cb;
57
- color: #155724;
105
+ .form-group {
106
+ margin-bottom: 1rem;
107
+
108
+ label {
109
+ display: block;
110
+ font-weight: 500;
111
+ margin-bottom: 0.5rem;
112
+ color: var(--ai-dark);
113
+ font-size: 0.875rem;
114
+
115
+ .required {
116
+ color: var(--ai-danger);
117
+ margin-left: 0.25rem;
118
+ }
119
+ }
120
+
121
+ .form-control {
122
+ width: 100%;
123
+ padding: 0.625rem 0.75rem;
124
+ border: 1px solid var(--ai-border);
125
+ border-radius: 0.375rem;
126
+ background-color: var(--ai-bg-primary);
127
+ color: var(--ai-dark);
128
+ font-size: 0.875rem;
129
+ transition: border-color 0.2s, box-shadow 0.2s;
130
+
131
+ &:focus {
132
+ outline: none;
133
+ border-color: var(--ai-primary);
134
+ box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.15);
135
+ }
136
+
137
+ &::placeholder {
138
+ color: var(--ai-secondary);
139
+ opacity: 0.6;
140
+ }
141
+ }
142
+
143
+ select.form-control {
144
+ cursor: pointer;
145
+ appearance: auto;
146
+ }
147
+ }
148
+
149
+ .toggle-switch {
150
+ display: flex;
151
+ align-items: center;
152
+ gap: 0.5rem;
153
+
154
+ input[type="checkbox"] {
155
+ width: 1.25rem;
156
+ height: 1.25rem;
157
+ accent-color: var(--ai-primary);
158
+ }
159
+
160
+ label {
161
+ margin: 0;
162
+ font-weight: normal;
163
+ }
164
+ }
165
+
166
+ .form-actions {
167
+ margin-top: 1.5rem;
168
+ padding-top: 1rem;
169
+ border-top: 1px solid var(--ai-border);
170
+
171
+ .btn {
172
+ padding: 0.625rem 1.25rem;
173
+ border: none;
174
+ border-radius: 0.375rem;
175
+ cursor: pointer;
176
+ transition: all 0.2s;
177
+ display: inline-flex;
178
+ align-items: center;
179
+ gap: 0.5rem;
180
+ font-size: 0.875rem;
181
+ font-weight: 500;
182
+
183
+ &.btn-primary {
184
+ background-color: var(--ai-primary);
185
+ color: white;
186
+
187
+ &:hover {
188
+ background-color: #0056b3;
189
+ }
190
+ }
191
+ }
58
192
  }
59
193
  }
60
194
  }
195
+
196
+ /* 响应式设计 */
197
+ @media (max-width: 768px) {
198
+ .provider-config {
199
+ .config-form .form-row {
200
+ grid-template-columns: 1fr;
201
+ }
202
+ }
203
+ }