tabby-ai-assistant 1.0.0
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.
- package/README.md +232 -0
- package/dist/components/chat/chat-input.component.d.ts +65 -0
- package/dist/components/chat/chat-interface.component.d.ts +71 -0
- package/dist/components/chat/chat-message.component.d.ts +53 -0
- package/dist/components/chat/chat-settings.component.d.ts +62 -0
- package/dist/components/common/error-message.component.d.ts +11 -0
- package/dist/components/common/loading-spinner.component.d.ts +4 -0
- package/dist/components/security/consent-dialog.component.d.ts +11 -0
- package/dist/components/security/password-prompt.component.d.ts +10 -0
- package/dist/components/security/risk-confirm-dialog.component.d.ts +36 -0
- package/dist/components/settings/ai-settings-tab.component.d.ts +72 -0
- package/dist/components/settings/general-settings.component.d.ts +60 -0
- package/dist/components/settings/provider-config.component.d.ts +182 -0
- package/dist/components/settings/security-settings.component.d.ts +23 -0
- package/dist/components/terminal/ai-toolbar-button.component.d.ts +10 -0
- package/dist/components/terminal/command-preview.component.d.ts +15 -0
- package/dist/components/terminal/command-suggestion.component.d.ts +16 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +2 -0
- package/dist/index.js.LICENSE.txt +18 -0
- package/dist/main.d.ts +8 -0
- package/dist/providers/tabby/ai-config.provider.d.ts +18 -0
- package/dist/providers/tabby/ai-hotkey.provider.d.ts +21 -0
- package/dist/providers/tabby/ai-settings-tab.provider.d.ts +11 -0
- package/dist/providers/tabby/ai-toolbar-button.provider.d.ts +17 -0
- package/dist/services/chat/chat-history.service.d.ts +67 -0
- package/dist/services/chat/chat-session.service.d.ts +58 -0
- package/dist/services/chat/command-generator.service.d.ts +49 -0
- package/dist/services/core/ai-assistant.service.d.ts +88 -0
- package/dist/services/core/ai-provider-manager.service.d.ts +119 -0
- package/dist/services/core/config-provider.service.d.ts +137 -0
- package/dist/services/core/logger.service.d.ts +21 -0
- package/dist/services/providers/anthropic-provider.service.d.ts +39 -0
- package/dist/services/providers/base-provider.service.d.ts +137 -0
- package/dist/services/providers/glm-provider.service.d.ts +91 -0
- package/dist/services/providers/minimax-provider.service.d.ts +93 -0
- package/dist/services/providers/openai-compatible.service.d.ts +39 -0
- package/dist/services/providers/openai-provider.service.d.ts +38 -0
- package/dist/services/security/consent-manager.service.d.ts +65 -0
- package/dist/services/security/password-manager.service.d.ts +67 -0
- package/dist/services/security/risk-assessment.service.d.ts +65 -0
- package/dist/services/security/security-validator.service.d.ts +36 -0
- package/dist/services/terminal/command-analyzer.service.d.ts +20 -0
- package/dist/services/terminal/context-menu.service.d.ts +24 -0
- package/dist/services/terminal/hotkey.service.d.ts +28 -0
- package/dist/services/terminal/terminal-context.service.d.ts +100 -0
- package/dist/types/ai.types.d.ts +107 -0
- package/dist/types/provider.types.d.ts +105 -0
- package/dist/types/security.types.d.ts +85 -0
- package/dist/types/terminal.types.d.ts +150 -0
- package/dist/utils/encryption.utils.d.ts +83 -0
- package/dist/utils/formatting.utils.d.ts +106 -0
- package/dist/utils/validation.utils.d.ts +83 -0
- package/integration-test-output.txt +50 -0
- package/integration-tests/api-integration.test.ts +183 -0
- package/jest.config.js +47 -0
- package/package.json +73 -0
- package/setup-jest.ts +37 -0
- package/src/components/chat/chat-input.component.html +61 -0
- package/src/components/chat/chat-input.component.scss +183 -0
- package/src/components/chat/chat-input.component.ts +149 -0
- package/src/components/chat/chat-interface.component.html +119 -0
- package/src/components/chat/chat-interface.component.scss +354 -0
- package/src/components/chat/chat-interface.component.ts +224 -0
- package/src/components/chat/chat-message.component.html +65 -0
- package/src/components/chat/chat-message.component.scss +178 -0
- package/src/components/chat/chat-message.component.ts +93 -0
- package/src/components/chat/chat-settings.component.html +132 -0
- package/src/components/chat/chat-settings.component.scss +172 -0
- package/src/components/chat/chat-settings.component.ts +168 -0
- package/src/components/common/error-message.component.ts +124 -0
- package/src/components/common/loading-spinner.component.ts +72 -0
- package/src/components/security/consent-dialog.component.ts +77 -0
- package/src/components/security/password-prompt.component.ts +79 -0
- package/src/components/security/risk-confirm-dialog.component.html +87 -0
- package/src/components/security/risk-confirm-dialog.component.scss +360 -0
- package/src/components/security/risk-confirm-dialog.component.ts +96 -0
- package/src/components/settings/ai-settings-tab.component.html +140 -0
- package/src/components/settings/ai-settings-tab.component.scss +371 -0
- package/src/components/settings/ai-settings-tab.component.ts +193 -0
- package/src/components/settings/general-settings.component.html +103 -0
- package/src/components/settings/general-settings.component.scss +285 -0
- package/src/components/settings/general-settings.component.ts +123 -0
- package/src/components/settings/provider-config.component.html +95 -0
- package/src/components/settings/provider-config.component.scss +60 -0
- package/src/components/settings/provider-config.component.ts +206 -0
- package/src/components/settings/security-settings.component.html +51 -0
- package/src/components/settings/security-settings.component.scss +66 -0
- package/src/components/settings/security-settings.component.ts +71 -0
- package/src/components/terminal/ai-toolbar-button.component.ts +49 -0
- package/src/components/terminal/command-preview.component.ts +185 -0
- package/src/components/terminal/command-suggestion.component.ts +128 -0
- package/src/index.ts +163 -0
- package/src/main.ts +16 -0
- package/src/providers/tabby/ai-config.provider.ts +70 -0
- package/src/providers/tabby/ai-hotkey.provider.ts +55 -0
- package/src/providers/tabby/ai-settings-tab.provider.ts +18 -0
- package/src/providers/tabby/ai-toolbar-button.provider.ts +49 -0
- package/src/services/chat/chat-history.service.ts +239 -0
- package/src/services/chat/chat-session.service.spec.ts +249 -0
- package/src/services/chat/chat-session.service.ts +180 -0
- package/src/services/chat/command-generator.service.ts +301 -0
- package/src/services/core/ai-assistant.service.ts +334 -0
- package/src/services/core/ai-provider-manager.service.ts +314 -0
- package/src/services/core/config-provider.service.ts +347 -0
- package/src/services/core/logger.service.ts +104 -0
- package/src/services/providers/anthropic-provider.service.ts +373 -0
- package/src/services/providers/base-provider.service.ts +369 -0
- package/src/services/providers/glm-provider.service.ts +467 -0
- package/src/services/providers/minimax-provider.service.ts +427 -0
- package/src/services/providers/openai-compatible.service.ts +394 -0
- package/src/services/providers/openai-provider.service.ts +376 -0
- package/src/services/security/consent-manager.service.ts +332 -0
- package/src/services/security/password-manager.service.ts +188 -0
- package/src/services/security/risk-assessment.service.ts +340 -0
- package/src/services/security/security-validator.service.ts +143 -0
- package/src/services/terminal/command-analyzer.service.ts +43 -0
- package/src/services/terminal/context-menu.service.ts +45 -0
- package/src/services/terminal/hotkey.service.ts +53 -0
- package/src/services/terminal/terminal-context.service.ts +317 -0
- package/src/styles/ai-assistant.scss +449 -0
- package/src/types/ai.types.ts +133 -0
- package/src/types/provider.types.ts +147 -0
- package/src/types/security.types.ts +103 -0
- package/src/types/terminal.types.ts +186 -0
- package/src/utils/encryption.utils.spec.ts +250 -0
- package/src/utils/encryption.utils.ts +271 -0
- package/src/utils/formatting.utils.ts +359 -0
- package/src/utils/validation.utils.spec.ts +225 -0
- package/src/utils/validation.utils.ts +314 -0
- package/tsconfig.json +45 -0
- package/webpack-docker.config.js +42 -0
- package/webpack.config.js +59 -0
- package/webpack.config.js.backup +57 -0
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
import { Injectable } from '@angular/core';
|
|
2
|
+
import { Subject, Observable } from 'rxjs';
|
|
3
|
+
import { BaseAiProvider, ProviderManager, ProviderInfo, ProviderEvent } from '../../types/provider.types';
|
|
4
|
+
import { LoggerService } from './logger.service';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* AI提供商管理器
|
|
8
|
+
* 负责注册、管理和切换不同的AI提供商
|
|
9
|
+
*/
|
|
10
|
+
@Injectable({ providedIn: 'root' })
|
|
11
|
+
export class AiProviderManagerService implements ProviderManager {
|
|
12
|
+
private providers = new Map<string, BaseAiProvider>();
|
|
13
|
+
private activeProvider: string | null = null;
|
|
14
|
+
private eventSubject = new Subject<ProviderEvent>();
|
|
15
|
+
|
|
16
|
+
constructor(private logger: LoggerService) {}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 注册AI提供商
|
|
20
|
+
*/
|
|
21
|
+
registerProvider(provider: BaseAiProvider): void {
|
|
22
|
+
if (this.providers.has(provider.name)) {
|
|
23
|
+
this.logger.warn(`Provider ${provider.name} is already registered, replacing...`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
this.providers.set(provider.name, provider);
|
|
27
|
+
this.logger.info(`Provider registered: ${provider.name}`);
|
|
28
|
+
|
|
29
|
+
// 如果没有激活的提供商,自动设置第一个为默认
|
|
30
|
+
if (!this.activeProvider) {
|
|
31
|
+
this.setActiveProvider(provider.name);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// 发送事件
|
|
35
|
+
this.emitEvent({
|
|
36
|
+
type: 'config_changed',
|
|
37
|
+
provider: provider.name,
|
|
38
|
+
timestamp: new Date()
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* 注销AI提供商
|
|
44
|
+
*/
|
|
45
|
+
unregisterProvider(name: string): void {
|
|
46
|
+
if (!this.providers.has(name)) {
|
|
47
|
+
this.logger.warn(`Provider ${name} not found, cannot unregister`);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
this.providers.delete(name);
|
|
52
|
+
|
|
53
|
+
// 如果删除的是当前激活的提供商,切换到其他提供商
|
|
54
|
+
if (this.activeProvider === name) {
|
|
55
|
+
const remainingProviders = Array.from(this.providers.keys());
|
|
56
|
+
if (remainingProviders.length > 0) {
|
|
57
|
+
this.setActiveProvider(remainingProviders[0]);
|
|
58
|
+
} else {
|
|
59
|
+
this.activeProvider = null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
this.logger.info(`Provider unregistered: ${name}`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 获取指定提供商
|
|
68
|
+
*/
|
|
69
|
+
getProvider(name: string): BaseAiProvider | undefined {
|
|
70
|
+
return this.providers.get(name);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* 获取所有提供商
|
|
75
|
+
*/
|
|
76
|
+
getAllProviders(): BaseAiProvider[] {
|
|
77
|
+
return Array.from(this.providers.values());
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* 获取当前激活的提供商
|
|
82
|
+
*/
|
|
83
|
+
getActiveProvider(): BaseAiProvider | undefined {
|
|
84
|
+
if (!this.activeProvider) {
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
return this.providers.get(this.activeProvider);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* 设置激活的提供商
|
|
92
|
+
*/
|
|
93
|
+
setActiveProvider(name: string): boolean {
|
|
94
|
+
if (!this.providers.has(name)) {
|
|
95
|
+
this.logger.error(`Provider ${name} not found`);
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const previousProvider = this.activeProvider;
|
|
100
|
+
this.activeProvider = name;
|
|
101
|
+
|
|
102
|
+
this.logger.info(`Active provider changed: ${previousProvider} -> ${name}`);
|
|
103
|
+
|
|
104
|
+
// 发送事件
|
|
105
|
+
this.emitEvent({
|
|
106
|
+
type: 'config_changed',
|
|
107
|
+
provider: name,
|
|
108
|
+
timestamp: new Date()
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* 获取提供商信息
|
|
116
|
+
*/
|
|
117
|
+
getProviderInfo(name: string): ProviderInfo | undefined {
|
|
118
|
+
const provider = this.providers.get(name);
|
|
119
|
+
return provider ? provider.getInfo() : undefined;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* 获取所有提供商信息
|
|
124
|
+
*/
|
|
125
|
+
getAllProviderInfo(): ProviderInfo[] {
|
|
126
|
+
return this.getAllProviders().map(provider => provider.getInfo());
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* 检查提供商是否存在
|
|
131
|
+
*/
|
|
132
|
+
hasProvider(name: string): boolean {
|
|
133
|
+
return this.providers.has(name);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* 获取提供商数量
|
|
138
|
+
*/
|
|
139
|
+
getProviderCount(): number {
|
|
140
|
+
return this.providers.size;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* 获取当前激活的提供商名称
|
|
145
|
+
*/
|
|
146
|
+
getActiveProviderName(): string | null {
|
|
147
|
+
return this.activeProvider;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* 验证所有提供商配置
|
|
152
|
+
*/
|
|
153
|
+
async validateAllProviders(): Promise<{ name: string; valid: boolean; errors: string[] }[]> {
|
|
154
|
+
const results = [];
|
|
155
|
+
|
|
156
|
+
for (const [name, provider] of this.providers) {
|
|
157
|
+
try {
|
|
158
|
+
const validation = provider.validateConfig();
|
|
159
|
+
results.push({
|
|
160
|
+
name,
|
|
161
|
+
valid: validation.valid,
|
|
162
|
+
errors: validation.errors || []
|
|
163
|
+
});
|
|
164
|
+
} catch (error) {
|
|
165
|
+
results.push({
|
|
166
|
+
name,
|
|
167
|
+
valid: false,
|
|
168
|
+
errors: [`Validation error: ${error instanceof Error ? error.message : String(error)}`]
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return results;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* 检查所有提供商健康状态
|
|
178
|
+
*/
|
|
179
|
+
async checkAllProvidersHealth(): Promise<{ provider: string; status: string; latency?: number }[]> {
|
|
180
|
+
const results = [];
|
|
181
|
+
|
|
182
|
+
for (const [name, provider] of this.providers) {
|
|
183
|
+
try {
|
|
184
|
+
const start = Date.now();
|
|
185
|
+
const health = await provider.healthCheck();
|
|
186
|
+
const latency = Date.now() - start;
|
|
187
|
+
|
|
188
|
+
results.push({
|
|
189
|
+
provider: name,
|
|
190
|
+
status: health,
|
|
191
|
+
latency
|
|
192
|
+
});
|
|
193
|
+
} catch (error) {
|
|
194
|
+
results.push({
|
|
195
|
+
provider: name,
|
|
196
|
+
status: 'unhealthy'
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return results;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* 获取启用的提供商
|
|
206
|
+
*/
|
|
207
|
+
getEnabledProviders(): BaseAiProvider[] {
|
|
208
|
+
return this.getAllProviders().filter(provider => {
|
|
209
|
+
const config = provider.getConfig();
|
|
210
|
+
return config?.enabled !== false;
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* 获取启用的提供商名称
|
|
216
|
+
*/
|
|
217
|
+
getEnabledProviderNames(): string[] {
|
|
218
|
+
return this.getEnabledProviders().map(p => p.name);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* 切换到下一个启用的提供商
|
|
223
|
+
*/
|
|
224
|
+
switchToNextProvider(): boolean {
|
|
225
|
+
const enabledProviders = this.getEnabledProviders();
|
|
226
|
+
if (enabledProviders.length === 0) {
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const currentIndex = enabledProviders.findIndex(p => p.name === this.activeProvider);
|
|
231
|
+
const nextIndex = (currentIndex + 1) % enabledProviders.length;
|
|
232
|
+
const nextProvider = enabledProviders[nextIndex];
|
|
233
|
+
|
|
234
|
+
return this.setActiveProvider(nextProvider.name);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* 切换到上一个启用的提供商
|
|
239
|
+
*/
|
|
240
|
+
switchToPreviousProvider(): boolean {
|
|
241
|
+
const enabledProviders = this.getEnabledProviders();
|
|
242
|
+
if (enabledProviders.length === 0) {
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const currentIndex = enabledProviders.findIndex(p => p.name === this.activeProvider);
|
|
247
|
+
const prevIndex = currentIndex === 0 ? enabledProviders.length - 1 : currentIndex - 1;
|
|
248
|
+
const prevProvider = enabledProviders[prevIndex];
|
|
249
|
+
|
|
250
|
+
return this.setActiveProvider(prevProvider.name);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* 订阅提供商事件
|
|
255
|
+
*/
|
|
256
|
+
onEvent(): Observable<ProviderEvent> {
|
|
257
|
+
return this.eventSubject.asObservable();
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* 获取所有启用的提供商中性能最好的(延迟最低)
|
|
262
|
+
*/
|
|
263
|
+
async getBestPerformingProvider(): Promise<BaseAiProvider | null> {
|
|
264
|
+
const healthStatus = await this.checkAllProvidersHealth();
|
|
265
|
+
const healthyProviders = healthStatus
|
|
266
|
+
.filter(h => h.status === 'healthy')
|
|
267
|
+
.sort((a, b) => (a.latency || 0) - (b.latency || 0));
|
|
268
|
+
|
|
269
|
+
if (healthyProviders.length === 0) {
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const bestProviderName = healthyProviders[0].provider;
|
|
274
|
+
return this.providers.get(bestProviderName) || null;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* 发送事件
|
|
279
|
+
*/
|
|
280
|
+
private emitEvent(event: ProviderEvent): void {
|
|
281
|
+
this.eventSubject.next(event);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* 获取提供商统计信息
|
|
286
|
+
*/
|
|
287
|
+
getStats(): {
|
|
288
|
+
totalProviders: number;
|
|
289
|
+
enabledProviders: number;
|
|
290
|
+
activeProvider: string | null;
|
|
291
|
+
providers: { name: string; enabled: boolean; healthy: boolean }[];
|
|
292
|
+
} {
|
|
293
|
+
const providers = this.getAllProviders();
|
|
294
|
+
return {
|
|
295
|
+
totalProviders: providers.length,
|
|
296
|
+
enabledProviders: this.getEnabledProviders().length,
|
|
297
|
+
activeProvider: this.activeProvider,
|
|
298
|
+
providers: providers.map(p => ({
|
|
299
|
+
name: p.name,
|
|
300
|
+
enabled: p.getConfig()?.enabled !== false,
|
|
301
|
+
healthy: true // TODO: 实现健康检查缓存
|
|
302
|
+
}))
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* 重置所有提供商
|
|
308
|
+
*/
|
|
309
|
+
reset(): void {
|
|
310
|
+
this.providers.clear();
|
|
311
|
+
this.activeProvider = null;
|
|
312
|
+
this.logger.info('All providers reset');
|
|
313
|
+
}
|
|
314
|
+
}
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
import { Injectable } from '@angular/core';
|
|
2
|
+
import { Subject, Observable } from 'rxjs';
|
|
3
|
+
import { LoggerService } from './logger.service';
|
|
4
|
+
import { SecurityConfig } from '../../types/security.types';
|
|
5
|
+
import { ProviderConfig } from '../../types/provider.types';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* AI助手配置
|
|
9
|
+
*/
|
|
10
|
+
export interface AiAssistantConfig {
|
|
11
|
+
enabled: boolean;
|
|
12
|
+
defaultProvider: string;
|
|
13
|
+
chatHistoryEnabled: boolean;
|
|
14
|
+
maxChatHistory: number;
|
|
15
|
+
autoSaveChat: boolean;
|
|
16
|
+
theme: 'light' | 'dark' | 'auto';
|
|
17
|
+
language: string;
|
|
18
|
+
logLevel: 'debug' | 'info' | 'warn' | 'error';
|
|
19
|
+
security: SecurityConfig;
|
|
20
|
+
providers: { [name: string]: ProviderConfig };
|
|
21
|
+
hotkeys: {
|
|
22
|
+
openChat: string;
|
|
23
|
+
generateCommand: string;
|
|
24
|
+
explainCommand: string;
|
|
25
|
+
};
|
|
26
|
+
ui: {
|
|
27
|
+
showTooltips: boolean;
|
|
28
|
+
compactMode: boolean;
|
|
29
|
+
fontSize: number;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const DEFAULT_CONFIG: AiAssistantConfig = {
|
|
34
|
+
enabled: true,
|
|
35
|
+
defaultProvider: 'openai',
|
|
36
|
+
chatHistoryEnabled: true,
|
|
37
|
+
maxChatHistory: 100,
|
|
38
|
+
autoSaveChat: true,
|
|
39
|
+
theme: 'auto',
|
|
40
|
+
language: 'zh-CN',
|
|
41
|
+
logLevel: 'info',
|
|
42
|
+
security: {
|
|
43
|
+
enablePasswordProtection: false,
|
|
44
|
+
consentExpiryDays: 30,
|
|
45
|
+
maxConsentAge: 30,
|
|
46
|
+
enableRiskAssessment: true,
|
|
47
|
+
autoApproveLowRisk: true,
|
|
48
|
+
promptForMediumRisk: true,
|
|
49
|
+
requirePasswordForHighRisk: true,
|
|
50
|
+
dangerousPatterns: [
|
|
51
|
+
'rm -rf /',
|
|
52
|
+
'sudo rm -rf /',
|
|
53
|
+
'format',
|
|
54
|
+
'dd if=',
|
|
55
|
+
'> /dev/null',
|
|
56
|
+
'fork\\('
|
|
57
|
+
],
|
|
58
|
+
allowedCommands: [],
|
|
59
|
+
forbiddenCommands: []
|
|
60
|
+
},
|
|
61
|
+
providers: {},
|
|
62
|
+
hotkeys: {
|
|
63
|
+
openChat: 'Ctrl-Shift-A',
|
|
64
|
+
generateCommand: 'Ctrl-Shift-G',
|
|
65
|
+
explainCommand: 'Ctrl-Shift-E'
|
|
66
|
+
},
|
|
67
|
+
ui: {
|
|
68
|
+
showTooltips: true,
|
|
69
|
+
compactMode: false,
|
|
70
|
+
fontSize: 14
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
@Injectable({ providedIn: 'root' })
|
|
75
|
+
export class ConfigProviderService {
|
|
76
|
+
private config: AiAssistantConfig = { ...DEFAULT_CONFIG };
|
|
77
|
+
private configChange$ = new Subject<{ key: string; value: any }>();
|
|
78
|
+
|
|
79
|
+
constructor(private logger: LoggerService) {
|
|
80
|
+
this.loadConfig();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* 加载配置
|
|
85
|
+
*/
|
|
86
|
+
private loadConfig(): void {
|
|
87
|
+
try {
|
|
88
|
+
const stored = localStorage.getItem('ai-assistant-config');
|
|
89
|
+
if (stored) {
|
|
90
|
+
const parsed = JSON.parse(stored);
|
|
91
|
+
this.config = { ...DEFAULT_CONFIG, ...parsed };
|
|
92
|
+
this.logger.info('Configuration loaded from storage');
|
|
93
|
+
} else {
|
|
94
|
+
this.logger.info('No stored configuration found, using defaults');
|
|
95
|
+
}
|
|
96
|
+
} catch (error) {
|
|
97
|
+
this.logger.error('Failed to load configuration', error);
|
|
98
|
+
this.config = { ...DEFAULT_CONFIG };
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* 保存配置
|
|
104
|
+
*/
|
|
105
|
+
private saveConfig(): void {
|
|
106
|
+
try {
|
|
107
|
+
localStorage.setItem('ai-assistant-config', JSON.stringify(this.config));
|
|
108
|
+
this.logger.debug('Configuration saved to storage');
|
|
109
|
+
} catch (error) {
|
|
110
|
+
this.logger.error('Failed to save configuration', error);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* 获取完整配置
|
|
116
|
+
*/
|
|
117
|
+
getConfig(): AiAssistantConfig {
|
|
118
|
+
return { ...this.config };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* 设置完整配置
|
|
123
|
+
*/
|
|
124
|
+
setConfig(config: Partial<AiAssistantConfig>): void {
|
|
125
|
+
this.config = { ...this.config, ...config };
|
|
126
|
+
this.saveConfig();
|
|
127
|
+
this.configChange$.next({ key: '*', value: this.config });
|
|
128
|
+
this.logger.info('Configuration updated');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* 获取指定配置项
|
|
133
|
+
*/
|
|
134
|
+
get<T>(key: string, defaultValue?: T): T | undefined {
|
|
135
|
+
const keys = key.split('.');
|
|
136
|
+
let value: any = this.config;
|
|
137
|
+
|
|
138
|
+
for (const k of keys) {
|
|
139
|
+
if (value === null || value === undefined) {
|
|
140
|
+
return defaultValue;
|
|
141
|
+
}
|
|
142
|
+
value = value[k];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return value !== undefined ? value : defaultValue;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* 设置指定配置项
|
|
150
|
+
*/
|
|
151
|
+
set<T>(key: string, value: T): void {
|
|
152
|
+
const keys = key.split('.');
|
|
153
|
+
const lastKey = keys.pop()!;
|
|
154
|
+
|
|
155
|
+
// 导航到父对象
|
|
156
|
+
let target: any = this.config;
|
|
157
|
+
for (const k of keys) {
|
|
158
|
+
if (!(k in target) || typeof target[k] !== 'object') {
|
|
159
|
+
target[k] = {};
|
|
160
|
+
}
|
|
161
|
+
target = target[k];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// 设置值
|
|
165
|
+
target[lastKey] = value;
|
|
166
|
+
this.saveConfig();
|
|
167
|
+
this.configChange$.next({ key, value });
|
|
168
|
+
|
|
169
|
+
this.logger.debug('Configuration item updated', { key, value });
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* 获取提供商配置
|
|
174
|
+
*/
|
|
175
|
+
getProviderConfig(name: string): ProviderConfig | null {
|
|
176
|
+
return this.config.providers[name] || null;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* 设置提供商配置
|
|
181
|
+
*/
|
|
182
|
+
setProviderConfig(name: string, config: ProviderConfig): void {
|
|
183
|
+
this.config.providers[name] = config;
|
|
184
|
+
this.saveConfig();
|
|
185
|
+
this.configChange$.next({ key: `providers.${name}`, value: config });
|
|
186
|
+
this.logger.info('Provider configuration updated', { provider: name });
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* 删除提供商配置
|
|
191
|
+
*/
|
|
192
|
+
deleteProviderConfig(name: string): void {
|
|
193
|
+
delete this.config.providers[name];
|
|
194
|
+
this.saveConfig();
|
|
195
|
+
this.configChange$.next({ key: `providers.${name}`, value: null });
|
|
196
|
+
this.logger.info('Provider configuration deleted', { provider: name });
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* 获取所有提供商配置
|
|
201
|
+
*/
|
|
202
|
+
getAllProviderConfigs(): { [name: string]: ProviderConfig } {
|
|
203
|
+
return { ...this.config.providers };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* 获取默认提供商
|
|
208
|
+
*/
|
|
209
|
+
getDefaultProvider(): string {
|
|
210
|
+
return this.config.defaultProvider;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* 设置默认提供商
|
|
215
|
+
*/
|
|
216
|
+
setDefaultProvider(name: string): void {
|
|
217
|
+
this.config.defaultProvider = name;
|
|
218
|
+
this.saveConfig();
|
|
219
|
+
this.configChange$.next({ key: 'defaultProvider', value: name });
|
|
220
|
+
this.logger.info('Default provider changed', { provider: name });
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* 获取安全配置
|
|
225
|
+
*/
|
|
226
|
+
getSecurityConfig(): SecurityConfig {
|
|
227
|
+
return { ...this.config.security };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* 更新安全配置
|
|
232
|
+
*/
|
|
233
|
+
updateSecurityConfig(config: Partial<SecurityConfig>): void {
|
|
234
|
+
this.config.security = { ...this.config.security, ...config };
|
|
235
|
+
this.saveConfig();
|
|
236
|
+
this.configChange$.next({ key: 'security', value: this.config.security });
|
|
237
|
+
this.logger.info('Security configuration updated');
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* 获取启用状态
|
|
242
|
+
*/
|
|
243
|
+
isEnabled(): boolean {
|
|
244
|
+
return this.config.enabled;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* 设置启用状态
|
|
249
|
+
*/
|
|
250
|
+
setEnabled(enabled: boolean): void {
|
|
251
|
+
this.config.enabled = enabled;
|
|
252
|
+
this.saveConfig();
|
|
253
|
+
this.configChange$.next({ key: 'enabled', value: enabled });
|
|
254
|
+
this.logger.info('AI Assistant enabled state changed', { enabled });
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* 获取日志级别
|
|
259
|
+
*/
|
|
260
|
+
getLogLevel(): string {
|
|
261
|
+
return this.config.logLevel;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* 设置日志级别
|
|
266
|
+
*/
|
|
267
|
+
setLogLevel(level: 'debug' | 'info' | 'warn' | 'error'): void {
|
|
268
|
+
this.config.logLevel = level;
|
|
269
|
+
this.saveConfig();
|
|
270
|
+
this.configChange$.next({ key: 'logLevel', value: level });
|
|
271
|
+
this.logger.info('Log level changed', { level });
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* 重置为默认配置
|
|
276
|
+
*/
|
|
277
|
+
reset(): void {
|
|
278
|
+
this.config = { ...DEFAULT_CONFIG };
|
|
279
|
+
this.saveConfig();
|
|
280
|
+
this.configChange$.next({ key: '*', value: this.config });
|
|
281
|
+
this.logger.info('Configuration reset to defaults');
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* 订阅配置变化
|
|
286
|
+
*/
|
|
287
|
+
onConfigChange(): Observable<{ key: string; value: any }> {
|
|
288
|
+
return this.configChange$.asObservable();
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* 导出配置
|
|
293
|
+
*/
|
|
294
|
+
exportConfig(): string {
|
|
295
|
+
// 排除敏感信息(如API密钥)
|
|
296
|
+
const exportData = { ...this.config };
|
|
297
|
+
if (exportData.providers) {
|
|
298
|
+
Object.keys(exportData.providers).forEach(name => {
|
|
299
|
+
if (exportData.providers[name].apiKey) {
|
|
300
|
+
exportData.providers[name].apiKey = '***MASKED***';
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
return JSON.stringify(exportData, null, 2);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* 导入配置
|
|
309
|
+
*/
|
|
310
|
+
importConfig(configJson: string): void {
|
|
311
|
+
try {
|
|
312
|
+
const imported = JSON.parse(configJson);
|
|
313
|
+
this.setConfig(imported);
|
|
314
|
+
this.logger.info('Configuration imported successfully');
|
|
315
|
+
} catch (error) {
|
|
316
|
+
this.logger.error('Failed to import configuration', error);
|
|
317
|
+
throw new Error('Invalid configuration format');
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* 验证配置
|
|
323
|
+
*/
|
|
324
|
+
validateConfig(): { valid: boolean; errors: string[] } {
|
|
325
|
+
const errors: string[] = [];
|
|
326
|
+
|
|
327
|
+
// 验证默认提供商
|
|
328
|
+
if (!this.config.providers[this.config.defaultProvider]) {
|
|
329
|
+
errors.push('Default provider configuration not found');
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// 验证安全配置
|
|
333
|
+
if (this.config.security.consentExpiryDays < 1) {
|
|
334
|
+
errors.push('Consent expiry days must be at least 1');
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// 验证聊天历史配置
|
|
338
|
+
if (this.config.maxChatHistory < 0) {
|
|
339
|
+
errors.push('Max chat history must be non-negative');
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return {
|
|
343
|
+
valid: errors.length === 0,
|
|
344
|
+
errors
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
}
|