tabby-ai-assistant 1.0.9 → 1.0.11
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/dist/index.js +1 -1
- package/package.json +3 -3
- package/src/components/chat/ai-sidebar.component.scss +43 -0
- package/src/components/chat/ai-sidebar.component.ts +43 -1
- package/src/components/settings/ai-settings-tab.component.html +13 -5
- package/src/components/settings/ai-settings-tab.component.scss +164 -33
- package/src/components/settings/ai-settings-tab.component.ts +1 -0
- package/src/components/settings/context-settings.component.html +87 -0
- package/src/components/settings/context-settings.component.scss +133 -0
- package/src/components/settings/context-settings.component.ts +91 -0
- package/src/components/settings/provider-config.component.html +46 -12
- package/src/components/settings/provider-config.component.scss +59 -0
- package/src/components/settings/provider-config.component.ts +112 -15
- package/src/i18n/translations/en-US.ts +27 -1
- package/src/i18n/translations/ja-JP.ts +27 -1
- package/src/i18n/translations/zh-CN.ts +27 -1
- package/src/i18n/types.ts +28 -0
- package/src/index.ts +6 -0
- package/src/services/chat/chat-session.service.ts +91 -2
- package/src/services/context/manager.ts +33 -2
- package/src/services/core/ai-assistant.service.ts +35 -11
- package/src/services/core/config-provider.service.ts +66 -0
- package/src/services/core/toast.service.ts +36 -0
- package/src/services/providers/anthropic-provider.service.ts +12 -4
- package/src/services/providers/glm-provider.service.ts +12 -4
- package/src/services/providers/minimax-provider.service.ts +12 -4
- package/src/types/ai.types.ts +3 -3
- package/src/types/provider.types.ts +1 -0
- package/dist/components/chat/ai-sidebar.component.d.ts +0 -160
- package/dist/components/chat/chat-input.component.d.ts +0 -69
- package/dist/components/chat/chat-interface.component.d.ts +0 -124
- package/dist/components/chat/chat-message.component.d.ts +0 -53
- package/dist/components/chat/chat-settings.component.d.ts +0 -72
- package/dist/components/common/error-message.component.d.ts +0 -11
- package/dist/components/common/loading-spinner.component.d.ts +0 -4
- package/dist/components/security/consent-dialog.component.d.ts +0 -11
- package/dist/components/security/password-prompt.component.d.ts +0 -10
- package/dist/components/security/risk-confirm-dialog.component.d.ts +0 -36
- package/dist/components/settings/ai-settings-tab.component.d.ts +0 -82
- package/dist/components/settings/general-settings.component.d.ts +0 -94
- package/dist/components/settings/provider-config.component.d.ts +0 -273
- package/dist/components/settings/security-settings.component.d.ts +0 -33
- package/dist/components/terminal/ai-toolbar-button.component.d.ts +0 -10
- package/dist/components/terminal/command-preview.component.d.ts +0 -53
- package/dist/components/terminal/command-suggestion.component.d.ts +0 -16
- package/dist/i18n/index.d.ts +0 -48
- package/dist/i18n/translations/en-US.d.ts +0 -5
- package/dist/i18n/translations/ja-JP.d.ts +0 -5
- package/dist/i18n/translations/zh-CN.d.ts +0 -5
- package/dist/i18n/types.d.ts +0 -198
- package/dist/index-full.d.ts +0 -8
- package/dist/index-minimal.d.ts +0 -3
- package/dist/index.d.ts +0 -12
- package/dist/main.d.ts +0 -8
- package/dist/providers/tabby/ai-config.provider.d.ts +0 -70
- package/dist/providers/tabby/ai-hotkey.provider.d.ts +0 -15
- package/dist/providers/tabby/ai-settings-tab.provider.d.ts +0 -11
- package/dist/providers/tabby/ai-toolbar-button.provider.d.ts +0 -16
- package/dist/services/chat/ai-sidebar.service.d.ts +0 -111
- package/dist/services/chat/chat-history.service.d.ts +0 -145
- package/dist/services/chat/chat-session.service.d.ts +0 -113
- package/dist/services/chat/command-generator.service.d.ts +0 -49
- package/dist/services/context/compaction.d.ts +0 -90
- package/dist/services/context/manager.d.ts +0 -69
- package/dist/services/context/memory.d.ts +0 -116
- package/dist/services/context/token-budget.d.ts +0 -105
- package/dist/services/core/ai-assistant.service.d.ts +0 -127
- package/dist/services/core/ai-provider-manager.service.d.ts +0 -119
- package/dist/services/core/checkpoint.service.d.ts +0 -130
- package/dist/services/core/config-provider.service.d.ts +0 -137
- package/dist/services/core/logger.service.d.ts +0 -21
- package/dist/services/core/theme.service.d.ts +0 -53
- package/dist/services/platform/escape-sequence.service.d.ts +0 -132
- package/dist/services/platform/platform-detection.service.d.ts +0 -146
- package/dist/services/providers/anthropic-provider.service.d.ts +0 -44
- package/dist/services/providers/base-provider.service.d.ts +0 -142
- package/dist/services/providers/glm-provider.service.d.ts +0 -96
- package/dist/services/providers/minimax-provider.service.d.ts +0 -102
- package/dist/services/providers/ollama-provider.service.d.ts +0 -76
- package/dist/services/providers/openai-compatible.service.d.ts +0 -44
- package/dist/services/providers/openai-provider.service.d.ts +0 -43
- package/dist/services/providers/vllm-provider.service.d.ts +0 -82
- package/dist/services/security/consent-manager.service.d.ts +0 -65
- package/dist/services/security/password-manager.service.d.ts +0 -67
- package/dist/services/security/risk-assessment.service.d.ts +0 -65
- package/dist/services/security/security-validator.service.d.ts +0 -36
- package/dist/services/terminal/buffer-analyzer.service.d.ts +0 -128
- package/dist/services/terminal/command-analyzer.service.d.ts +0 -20
- package/dist/services/terminal/context-menu.service.d.ts +0 -24
- package/dist/services/terminal/hotkey.service.d.ts +0 -28
- package/dist/services/terminal/terminal-context.service.d.ts +0 -100
- package/dist/services/terminal/terminal-manager.service.d.ts +0 -185
- package/dist/services/terminal/terminal-tools.service.d.ts +0 -79
- package/dist/types/ai.types.d.ts +0 -199
- package/dist/types/provider.types.d.ts +0 -105
- package/dist/types/security.types.d.ts +0 -85
- package/dist/types/terminal.types.d.ts +0 -150
- package/dist/utils/encryption.utils.d.ts +0 -83
- package/dist/utils/formatting.utils.d.ts +0 -106
- package/dist/utils/validation.utils.d.ts +0 -83
- package/jest.config.js +0 -47
- package/webpack-docker.config.js +0 -42
- package/webpack.config.js +0 -81
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { Component, OnInit, OnDestroy, ViewEncapsulation } from '@angular/core';
|
|
2
|
+
import { Subject } from 'rxjs';
|
|
3
|
+
import { takeUntil } from 'rxjs/operators';
|
|
4
|
+
import { ConfigProviderService } from '../../services/core/config-provider.service';
|
|
5
|
+
import { ContextManager } from '../../services/context/manager';
|
|
6
|
+
import { ToastService } from '../../services/core/toast.service';
|
|
7
|
+
import { TranslateService } from '../../i18n';
|
|
8
|
+
import { ContextConfig, DEFAULT_CONTEXT_CONFIG } from '../../types/ai.types';
|
|
9
|
+
|
|
10
|
+
@Component({
|
|
11
|
+
selector: 'app-context-settings',
|
|
12
|
+
templateUrl: './context-settings.component.html',
|
|
13
|
+
styleUrls: ['./context-settings.component.scss'],
|
|
14
|
+
encapsulation: ViewEncapsulation.None
|
|
15
|
+
})
|
|
16
|
+
export class ContextSettingsComponent implements OnInit, OnDestroy {
|
|
17
|
+
// 配置项
|
|
18
|
+
config: ContextConfig = { ...DEFAULT_CONTEXT_CONFIG };
|
|
19
|
+
autoCompactEnabled = true;
|
|
20
|
+
|
|
21
|
+
// 当前供应商的上下文限制
|
|
22
|
+
activeProviderContextWindow: number = 200000;
|
|
23
|
+
|
|
24
|
+
// 翻译对象
|
|
25
|
+
t: any;
|
|
26
|
+
|
|
27
|
+
private destroy$ = new Subject<void>();
|
|
28
|
+
|
|
29
|
+
constructor(
|
|
30
|
+
private configService: ConfigProviderService,
|
|
31
|
+
private contextManager: ContextManager,
|
|
32
|
+
private toast: ToastService,
|
|
33
|
+
private translate: TranslateService
|
|
34
|
+
) {
|
|
35
|
+
this.t = this.translate.t;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
ngOnInit(): void {
|
|
39
|
+
this.translate.translation$.pipe(
|
|
40
|
+
takeUntil(this.destroy$)
|
|
41
|
+
).subscribe(translation => {
|
|
42
|
+
this.t = translation;
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
this.loadConfig();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
ngOnDestroy(): void {
|
|
49
|
+
this.destroy$.next();
|
|
50
|
+
this.destroy$.complete();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
loadConfig(): void {
|
|
54
|
+
const savedConfig = this.configService.getContextConfig();
|
|
55
|
+
if (savedConfig) {
|
|
56
|
+
this.config = { ...DEFAULT_CONTEXT_CONFIG, ...savedConfig };
|
|
57
|
+
}
|
|
58
|
+
this.autoCompactEnabled = this.configService.isAutoCompactEnabled();
|
|
59
|
+
|
|
60
|
+
// 获取当前供应商的上下文限制
|
|
61
|
+
this.activeProviderContextWindow = this.configService.getActiveProviderContextWindow();
|
|
62
|
+
|
|
63
|
+
// 确保配置的 maxContextTokens 不超过供应商限制
|
|
64
|
+
if (this.config.maxContextTokens > this.activeProviderContextWindow) {
|
|
65
|
+
this.config.maxContextTokens = this.activeProviderContextWindow;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
saveConfig(): void {
|
|
70
|
+
this.configService.setContextConfig(this.config);
|
|
71
|
+
this.contextManager.updateConfig(this.config);
|
|
72
|
+
this.toast.success(this.t?.contextSettings?.configSaved || '上下文配置已保存');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
toggleAutoCompact(): void {
|
|
76
|
+
this.autoCompactEnabled = !this.autoCompactEnabled;
|
|
77
|
+
this.configService.setAutoCompactEnabled(this.autoCompactEnabled);
|
|
78
|
+
this.toast.info(
|
|
79
|
+
this.autoCompactEnabled
|
|
80
|
+
? (this.t?.contextSettings?.autoCompactEnabled || '自动压缩已启用')
|
|
81
|
+
: (this.t?.contextSettings?.autoCompactDisabled || '自动压缩已禁用')
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
resetToDefaults(): void {
|
|
86
|
+
this.config = { ...DEFAULT_CONTEXT_CONFIG };
|
|
87
|
+
this.autoCompactEnabled = true;
|
|
88
|
+
this.saveConfig();
|
|
89
|
+
this.toast.info(this.t?.common?.resetToDefault || '已重置为默认配置');
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
<div class="form-row">
|
|
38
38
|
<div class="form-group">
|
|
39
39
|
<label>{{ t.providers.displayName }}</label>
|
|
40
|
-
<input type="text" class="form-control" [(ngModel)]="configs[providerName].displayName">
|
|
40
|
+
<input type="text" class="form-control" [(ngModel)]="configs[providerName].displayName" *ngIf="configs[providerName]">
|
|
41
41
|
</div>
|
|
42
42
|
<div class="form-group">
|
|
43
43
|
<label>{{ t.providers.status }}</label>
|
|
@@ -57,11 +57,28 @@
|
|
|
57
57
|
{{ t.providers[field.key] || field.label }}
|
|
58
58
|
<span *ngIf="isRequired(field)" class="required">*</span>
|
|
59
59
|
</label>
|
|
60
|
-
<input *ngIf="getFieldType(field) === 'text' || getFieldType(field) === 'password'"
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
60
|
+
<div class="input-with-toggle" *ngIf="configs[providerName] && (getFieldType(field) === 'text' || getFieldType(field) === 'password')">
|
|
61
|
+
<input
|
|
62
|
+
[type]="isPasswordField(field) && !isPasswordVisible(providerName, field.key) ? 'password' : 'text'"
|
|
63
|
+
class="form-control"
|
|
64
|
+
[class]="getInputValidationClass(providerName, field.key)"
|
|
65
|
+
[placeholder]="field.placeholder || ''"
|
|
66
|
+
[(ngModel)]="configs[providerName][field.key]">
|
|
67
|
+
<button *ngIf="isPasswordField(field)"
|
|
68
|
+
type="button"
|
|
69
|
+
class="btn-toggle-visibility"
|
|
70
|
+
(click)="togglePasswordVisibility(providerName, field.key)">
|
|
71
|
+
<i class="fa" [ngClass]="isPasswordVisible(providerName, field.key) ? 'fa-eye-slash' : 'fa-eye'"></i>
|
|
72
|
+
</button>
|
|
73
|
+
</div>
|
|
74
|
+
<!-- API Key 验证提示 -->
|
|
75
|
+
<div *ngIf="field.key === 'apiKey' && configs[providerName]?.[field.key]"
|
|
76
|
+
class="validation-feedback"
|
|
77
|
+
[class.valid]="validateApiKeyFormat(providerName, configs[providerName][field.key]).valid"
|
|
78
|
+
[class.invalid]="!validateApiKeyFormat(providerName, configs[providerName][field.key]).valid">
|
|
79
|
+
<i class="fa" [ngClass]="validateApiKeyFormat(providerName, configs[providerName][field.key]).valid ? 'fa-check-circle' : 'fa-exclamation-circle'"></i>
|
|
80
|
+
{{ validateApiKeyFormat(providerName, configs[providerName][field.key]).message || '格式正确' }}
|
|
81
|
+
</div>
|
|
65
82
|
</div>
|
|
66
83
|
|
|
67
84
|
<div class="form-actions">
|
|
@@ -121,7 +138,7 @@
|
|
|
121
138
|
<div class="form-row">
|
|
122
139
|
<div class="form-group">
|
|
123
140
|
<label>{{ t.providers.displayName }}</label>
|
|
124
|
-
<input type="text" class="form-control" [(ngModel)]="configs[providerName].displayName">
|
|
141
|
+
<input type="text" class="form-control" [(ngModel)]="configs[providerName].displayName" *ngIf="configs[providerName]">
|
|
125
142
|
</div>
|
|
126
143
|
<div class="form-group">
|
|
127
144
|
<label>{{ t.providers.status }}</label>
|
|
@@ -141,11 +158,28 @@
|
|
|
141
158
|
{{ t.providers[field.key] || field.label }}
|
|
142
159
|
<span *ngIf="isRequired(field)" class="required">*</span>
|
|
143
160
|
</label>
|
|
144
|
-
<input *ngIf="getFieldType(field) === 'text' || getFieldType(field) === 'password'"
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
161
|
+
<div class="input-with-toggle" *ngIf="configs[providerName] && (getFieldType(field) === 'text' || getFieldType(field) === 'password')">
|
|
162
|
+
<input
|
|
163
|
+
[type]="isPasswordField(field) && !isPasswordVisible(providerName, field.key) ? 'password' : 'text'"
|
|
164
|
+
class="form-control"
|
|
165
|
+
[class]="getInputValidationClass(providerName, field.key)"
|
|
166
|
+
[placeholder]="field.placeholder || ''"
|
|
167
|
+
[(ngModel)]="configs[providerName][field.key]">
|
|
168
|
+
<button *ngIf="isPasswordField(field)"
|
|
169
|
+
type="button"
|
|
170
|
+
class="btn-toggle-visibility"
|
|
171
|
+
(click)="togglePasswordVisibility(providerName, field.key)">
|
|
172
|
+
<i class="fa" [ngClass]="isPasswordVisible(providerName, field.key) ? 'fa-eye-slash' : 'fa-eye'"></i>
|
|
173
|
+
</button>
|
|
174
|
+
</div>
|
|
175
|
+
<!-- API Key 验证提示 -->
|
|
176
|
+
<div *ngIf="field.key === 'apiKey' && configs[providerName]?.[field.key]"
|
|
177
|
+
class="validation-feedback"
|
|
178
|
+
[class.valid]="validateApiKeyFormat(providerName, configs[providerName][field.key]).valid"
|
|
179
|
+
[class.invalid]="!validateApiKeyFormat(providerName, configs[providerName][field.key]).valid">
|
|
180
|
+
<i class="fa" [ngClass]="validateApiKeyFormat(providerName, configs[providerName][field.key]).valid ? 'fa-check-circle' : 'fa-exclamation-circle'"></i>
|
|
181
|
+
{{ validateApiKeyFormat(providerName, configs[providerName][field.key]).message || '格式正确' }}
|
|
182
|
+
</div>
|
|
149
183
|
</div>
|
|
150
184
|
|
|
151
185
|
<div class="form-actions">
|
|
@@ -302,6 +302,65 @@
|
|
|
302
302
|
}
|
|
303
303
|
}
|
|
304
304
|
|
|
305
|
+
/* 输入框密码显示切换 */
|
|
306
|
+
.input-with-toggle {
|
|
307
|
+
position: relative;
|
|
308
|
+
display: flex;
|
|
309
|
+
align-items: center;
|
|
310
|
+
|
|
311
|
+
.form-control {
|
|
312
|
+
padding-right: 40px;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
.btn-toggle-visibility {
|
|
316
|
+
position: absolute;
|
|
317
|
+
right: 8px;
|
|
318
|
+
background: transparent;
|
|
319
|
+
border: none;
|
|
320
|
+
color: var(--ai-text-secondary);
|
|
321
|
+
cursor: pointer;
|
|
322
|
+
padding: 4px 8px;
|
|
323
|
+
display: flex;
|
|
324
|
+
align-items: center;
|
|
325
|
+
justify-content: center;
|
|
326
|
+
|
|
327
|
+
&:hover {
|
|
328
|
+
color: var(--ai-text-primary);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/* 表单验证状态 */
|
|
334
|
+
.form-control {
|
|
335
|
+
&.is-valid {
|
|
336
|
+
border-color: var(--ai-success, #4caf50);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
&.is-invalid {
|
|
340
|
+
border-color: var(--ai-warning, #ff9800);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
.validation-feedback {
|
|
345
|
+
font-size: 0.85em;
|
|
346
|
+
margin-top: 4px;
|
|
347
|
+
display: flex;
|
|
348
|
+
align-items: center;
|
|
349
|
+
gap: 4px;
|
|
350
|
+
|
|
351
|
+
&.valid {
|
|
352
|
+
color: var(--ai-success, #4caf50);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
&.invalid {
|
|
356
|
+
color: var(--ai-warning, #ff9800);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
i {
|
|
360
|
+
font-size: 0.9em;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
305
364
|
/* 响应式设计 */
|
|
306
365
|
@media (max-width: 768px) {
|
|
307
366
|
.providers-grid {
|
|
@@ -3,6 +3,7 @@ import { Subject } from 'rxjs';
|
|
|
3
3
|
import { takeUntil } from 'rxjs/operators';
|
|
4
4
|
import { ConfigProviderService } from '../../services/core/config-provider.service';
|
|
5
5
|
import { LoggerService } from '../../services/core/logger.service';
|
|
6
|
+
import { ToastService } from '../../services/core/toast.service';
|
|
6
7
|
import { TranslateService } from '../../i18n';
|
|
7
8
|
|
|
8
9
|
@Component({
|
|
@@ -23,10 +24,19 @@ export class ProviderConfigComponent implements OnInit, OnDestroy {
|
|
|
23
24
|
configs: { [key: string]: any } = {};
|
|
24
25
|
expandedProvider: string = '';
|
|
25
26
|
localStatus: { [key: string]: boolean } = {};
|
|
27
|
+
passwordVisibility: { [key: string]: { [fieldKey: string]: boolean } } = {};
|
|
26
28
|
|
|
27
29
|
// 翻译对象
|
|
28
30
|
t: any;
|
|
29
31
|
|
|
32
|
+
// API Key 格式校验规则
|
|
33
|
+
private apiKeyPatterns: { [key: string]: RegExp } = {
|
|
34
|
+
'openai': /^sk-[a-zA-Z0-9]{32,}$/,
|
|
35
|
+
'anthropic': /^sk-ant-[a-zA-Z0-9-]+$/,
|
|
36
|
+
'minimax': /^[a-zA-Z0-9]{32,}$/,
|
|
37
|
+
'glm': /^[a-zA-Z0-9._-]+$/
|
|
38
|
+
};
|
|
39
|
+
|
|
30
40
|
private destroy$ = new Subject<void>();
|
|
31
41
|
|
|
32
42
|
// 云端提供商模板
|
|
@@ -38,7 +48,8 @@ export class ProviderConfigComponent implements OnInit, OnDestroy {
|
|
|
38
48
|
fields: [
|
|
39
49
|
{ key: 'apiKey', label: 'API Key', type: 'password', required: true },
|
|
40
50
|
{ key: 'baseURL', label: 'Base URL', type: 'text', default: 'https://api.openai.com/v1', required: false },
|
|
41
|
-
{ key: 'model', label: 'Model', type: 'text', default: 'gpt-4', required: false, placeholder: '例如: gpt-4, gpt-4-turbo, gpt-3.5-turbo' }
|
|
51
|
+
{ key: 'model', label: 'Model', type: 'text', default: 'gpt-4', required: false, placeholder: '例如: gpt-4, gpt-4-turbo, gpt-3.5-turbo' },
|
|
52
|
+
{ key: 'contextWindow', label: '上下文限制', type: 'number', default: 128000, required: false, placeholder: 'GPT-4: 128000, GPT-3.5: 16385' }
|
|
42
53
|
]
|
|
43
54
|
},
|
|
44
55
|
'anthropic': {
|
|
@@ -48,7 +59,8 @@ export class ProviderConfigComponent implements OnInit, OnDestroy {
|
|
|
48
59
|
fields: [
|
|
49
60
|
{ key: 'apiKey', label: 'API Key', type: 'password', required: true },
|
|
50
61
|
{ key: 'baseURL', label: 'Base URL', type: 'text', default: 'https://api.anthropic.com', required: false },
|
|
51
|
-
{ key: 'model', label: 'Model', type: 'text', default: 'claude-3-sonnet-20240229', required: false, placeholder: '例如: claude-3-opus, claude-3-sonnet' }
|
|
62
|
+
{ key: 'model', label: 'Model', type: 'text', default: 'claude-3-sonnet-20240229', required: false, placeholder: '例如: claude-3-opus, claude-3-sonnet' },
|
|
63
|
+
{ key: 'contextWindow', label: '上下文限制', type: 'number', default: 200000, required: false, placeholder: 'Claude 3: 200000' }
|
|
52
64
|
]
|
|
53
65
|
},
|
|
54
66
|
'minimax': {
|
|
@@ -58,7 +70,8 @@ export class ProviderConfigComponent implements OnInit, OnDestroy {
|
|
|
58
70
|
fields: [
|
|
59
71
|
{ key: 'apiKey', label: 'API Key', type: 'password', required: true },
|
|
60
72
|
{ key: 'baseURL', label: 'Base URL', type: 'text', default: 'https://api.minimaxi.com/anthropic', required: false },
|
|
61
|
-
{ key: 'model', label: 'Model', type: 'text', default: 'MiniMax-M2', required: false, placeholder: '例如: MiniMax-M2, MiniMax-M2.1' }
|
|
73
|
+
{ key: 'model', label: 'Model', type: 'text', default: 'MiniMax-M2', required: false, placeholder: '例如: MiniMax-M2, MiniMax-M2.1' },
|
|
74
|
+
{ key: 'contextWindow', label: '上下文限制', type: 'number', default: 128000, required: false, placeholder: 'MiniMax-M2: 128000' }
|
|
62
75
|
]
|
|
63
76
|
},
|
|
64
77
|
'glm': {
|
|
@@ -68,7 +81,8 @@ export class ProviderConfigComponent implements OnInit, OnDestroy {
|
|
|
68
81
|
fields: [
|
|
69
82
|
{ key: 'apiKey', label: 'API Key', type: 'password', required: true },
|
|
70
83
|
{ key: 'baseURL', label: 'Base URL', type: 'text', default: 'https://open.bigmodel.cn/api/paas/v4', required: false },
|
|
71
|
-
{ key: 'model', label: 'Model', type: 'text', default: 'glm-4', required: false, placeholder: '例如: glm-4, glm-4-air, glm-4-flash' }
|
|
84
|
+
{ key: 'model', label: 'Model', type: 'text', default: 'glm-4', required: false, placeholder: '例如: glm-4, glm-4-air, glm-4-flash' },
|
|
85
|
+
{ key: 'contextWindow', label: '上下文限制', type: 'number', default: 128000, required: false, placeholder: 'GLM-4: 128000' }
|
|
72
86
|
]
|
|
73
87
|
}
|
|
74
88
|
};
|
|
@@ -82,7 +96,8 @@ export class ProviderConfigComponent implements OnInit, OnDestroy {
|
|
|
82
96
|
defaultURL: 'http://localhost:11434/v1',
|
|
83
97
|
fields: [
|
|
84
98
|
{ 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' }
|
|
99
|
+
{ key: 'model', label: 'Model', type: 'text', default: 'llama3.1', required: false, placeholder: '例如: llama3.1, qwen2.5, mistral' },
|
|
100
|
+
{ key: 'contextWindow', label: '上下文限制', type: 'number', default: 8192, required: false, placeholder: 'Llama 3.1: 8192' }
|
|
86
101
|
]
|
|
87
102
|
},
|
|
88
103
|
'vllm': {
|
|
@@ -93,7 +108,8 @@ export class ProviderConfigComponent implements OnInit, OnDestroy {
|
|
|
93
108
|
fields: [
|
|
94
109
|
{ key: 'baseURL', label: 'Base URL', type: 'text', default: 'http://localhost:8000/v1', required: true, placeholder: '例如: http://localhost:8000/v1' },
|
|
95
110
|
{ 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 模型路径' }
|
|
111
|
+
{ key: 'model', label: 'Model', type: 'text', default: 'meta-llama/Llama-3.1-8B', required: false, placeholder: 'HuggingFace 模型路径' },
|
|
112
|
+
{ key: 'contextWindow', label: '上下文限制', type: 'number', default: 8192, required: false, placeholder: '根据模型实际配置设置' }
|
|
97
113
|
]
|
|
98
114
|
}
|
|
99
115
|
};
|
|
@@ -101,6 +117,7 @@ export class ProviderConfigComponent implements OnInit, OnDestroy {
|
|
|
101
117
|
constructor(
|
|
102
118
|
private config: ConfigProviderService,
|
|
103
119
|
private logger: LoggerService,
|
|
120
|
+
private toast: ToastService,
|
|
104
121
|
private translate: TranslateService
|
|
105
122
|
) {
|
|
106
123
|
this.t = this.translate.t;
|
|
@@ -129,6 +146,33 @@ export class ProviderConfigComponent implements OnInit, OnDestroy {
|
|
|
129
146
|
*/
|
|
130
147
|
private loadConfigs(): void {
|
|
131
148
|
const allConfigs = this.config.getAllProviderConfigs();
|
|
149
|
+
|
|
150
|
+
// 为所有云端供应商初始化默认配置
|
|
151
|
+
for (const providerName of Object.keys(this.cloudProviderTemplates)) {
|
|
152
|
+
if (!allConfigs[providerName]) {
|
|
153
|
+
const template = this.cloudProviderTemplates[providerName];
|
|
154
|
+
allConfigs[providerName] = {
|
|
155
|
+
name: providerName,
|
|
156
|
+
displayName: template.name,
|
|
157
|
+
enabled: false,
|
|
158
|
+
...this.createDefaultConfig(template.fields)
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// 为所有本地供应商初始化默认配置
|
|
164
|
+
for (const providerName of Object.keys(this.localProviderTemplates)) {
|
|
165
|
+
if (!allConfigs[providerName]) {
|
|
166
|
+
const template = this.localProviderTemplates[providerName];
|
|
167
|
+
allConfigs[providerName] = {
|
|
168
|
+
name: providerName,
|
|
169
|
+
displayName: template.name,
|
|
170
|
+
enabled: false,
|
|
171
|
+
...this.createDefaultConfig(template.fields)
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
132
176
|
this.configs = allConfigs;
|
|
133
177
|
this.selectedProvider = this.config.getDefaultProvider();
|
|
134
178
|
}
|
|
@@ -187,7 +231,7 @@ export class ProviderConfigComponent implements OnInit, OnDestroy {
|
|
|
187
231
|
const baseURL = this.configs[providerName]?.baseURL || template?.defaultURL;
|
|
188
232
|
|
|
189
233
|
if (!baseURL) {
|
|
190
|
-
|
|
234
|
+
this.toast.error(this.t.providers.baseURL + ': ' + this.t.providers.testError);
|
|
191
235
|
return;
|
|
192
236
|
}
|
|
193
237
|
|
|
@@ -201,16 +245,16 @@ export class ProviderConfigComponent implements OnInit, OnDestroy {
|
|
|
201
245
|
});
|
|
202
246
|
|
|
203
247
|
if (response.ok) {
|
|
204
|
-
|
|
248
|
+
this.toast.success(`${template.name}: ${this.t.providers.testSuccess}`);
|
|
205
249
|
this.localStatus[providerName] = true;
|
|
206
250
|
this.logger.info('Local provider test successful', { provider: providerName });
|
|
207
251
|
} else {
|
|
208
|
-
|
|
252
|
+
this.toast.error(`${this.t.providers.testFail}: ${response.status}`);
|
|
209
253
|
this.localStatus[providerName] = false;
|
|
210
254
|
}
|
|
211
255
|
} catch (error) {
|
|
212
256
|
const errorMessage = error instanceof Error ? error.message : this.t.providers.testError;
|
|
213
|
-
|
|
257
|
+
this.toast.error(`${template.name}\n\n${this.t.providers.testError}\n${errorMessage}`);
|
|
214
258
|
this.localStatus[providerName] = false;
|
|
215
259
|
this.logger.error('Local provider test failed', { provider: providerName, error: errorMessage });
|
|
216
260
|
}
|
|
@@ -224,6 +268,7 @@ export class ProviderConfigComponent implements OnInit, OnDestroy {
|
|
|
224
268
|
if (providerConfig) {
|
|
225
269
|
this.config.setProviderConfig(providerName, providerConfig);
|
|
226
270
|
this.logger.info('Provider config saved', { provider: providerName });
|
|
271
|
+
this.toast.success(`${this.getProviderTemplate(providerName)?.name || providerName} ${this.t.providers.configSaved || '配置已保存'}`);
|
|
227
272
|
}
|
|
228
273
|
}
|
|
229
274
|
|
|
@@ -277,7 +322,7 @@ export class ProviderConfigComponent implements OnInit, OnDestroy {
|
|
|
277
322
|
async testConnection(providerName: string): Promise<void> {
|
|
278
323
|
const providerConfig = this.configs[providerName];
|
|
279
324
|
if (!providerConfig) {
|
|
280
|
-
|
|
325
|
+
this.toast.error(this.t.providers.testError);
|
|
281
326
|
return;
|
|
282
327
|
}
|
|
283
328
|
|
|
@@ -291,7 +336,7 @@ export class ProviderConfigComponent implements OnInit, OnDestroy {
|
|
|
291
336
|
const baseURL = providerConfig.baseURL;
|
|
292
337
|
|
|
293
338
|
if (!apiKey) {
|
|
294
|
-
|
|
339
|
+
this.toast.error(this.t.providers.apiKey + ': ' + this.t.providers.testError);
|
|
295
340
|
return;
|
|
296
341
|
}
|
|
297
342
|
|
|
@@ -314,16 +359,16 @@ export class ProviderConfigComponent implements OnInit, OnDestroy {
|
|
|
314
359
|
});
|
|
315
360
|
|
|
316
361
|
if (response.ok) {
|
|
317
|
-
|
|
362
|
+
this.toast.success(this.t.providers.testSuccess);
|
|
318
363
|
this.logger.info('Connection test successful', { provider: providerName });
|
|
319
364
|
} else {
|
|
320
365
|
const errorData = await response.text();
|
|
321
|
-
|
|
366
|
+
this.toast.error(`${this.t.providers.testFail}\n\nStatus: ${response.status}\n${errorData.substring(0, 200)}`);
|
|
322
367
|
this.logger.error('Connection test failed', { provider: providerName, status: response.status });
|
|
323
368
|
}
|
|
324
369
|
} catch (error) {
|
|
325
370
|
const errorMessage = error instanceof Error ? error.message : this.t.providers.testError;
|
|
326
|
-
|
|
371
|
+
this.toast.error(`${this.t.providers.testFail}\n\n${errorMessage}`);
|
|
327
372
|
this.logger.error('Connection test error', { provider: providerName, error: errorMessage });
|
|
328
373
|
}
|
|
329
374
|
}
|
|
@@ -473,4 +518,56 @@ export class ProviderConfigComponent implements OnInit, OnDestroy {
|
|
|
473
518
|
}
|
|
474
519
|
this.configs[providerName][key] = value;
|
|
475
520
|
}
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* 切换密码字段可见性
|
|
524
|
+
*/
|
|
525
|
+
togglePasswordVisibility(providerName: string, fieldKey: string): void {
|
|
526
|
+
if (!this.passwordVisibility[providerName]) {
|
|
527
|
+
this.passwordVisibility[providerName] = {};
|
|
528
|
+
}
|
|
529
|
+
this.passwordVisibility[providerName][fieldKey] = !this.passwordVisibility[providerName][fieldKey];
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* 获取密码字段可见性状态
|
|
534
|
+
*/
|
|
535
|
+
isPasswordVisible(providerName: string, fieldKey: string): boolean {
|
|
536
|
+
return this.passwordVisibility[providerName]?.[fieldKey] ?? false;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* 验证 API Key 格式
|
|
541
|
+
*/
|
|
542
|
+
validateApiKeyFormat(providerName: string, apiKey: string): { valid: boolean; message: string } {
|
|
543
|
+
if (!apiKey || apiKey.trim().length === 0) {
|
|
544
|
+
return { valid: false, message: this.t?.providers?.apiKeyRequired || 'API Key 不能为空' };
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
const pattern = this.apiKeyPatterns[providerName];
|
|
548
|
+
if (pattern && !pattern.test(apiKey)) {
|
|
549
|
+
const hints: { [key: string]: string } = {
|
|
550
|
+
'openai': 'OpenAI API Key 应以 sk- 开头',
|
|
551
|
+
'anthropic': 'Anthropic API Key 应以 sk-ant- 开头',
|
|
552
|
+
'minimax': 'Minimax API Key 应为 32 位以上的字母数字',
|
|
553
|
+
'glm': 'GLM API Key 格式不正确'
|
|
554
|
+
};
|
|
555
|
+
return { valid: false, message: hints[providerName] || 'API Key 格式可能不正确' };
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
return { valid: true, message: '' };
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* 获取输入框的验证状态类
|
|
563
|
+
*/
|
|
564
|
+
getInputValidationClass(providerName: string, fieldKey: string): string {
|
|
565
|
+
if (fieldKey !== 'apiKey') return '';
|
|
566
|
+
|
|
567
|
+
const value = this.configs[providerName]?.[fieldKey];
|
|
568
|
+
if (!value || value.trim().length === 0) return '';
|
|
569
|
+
|
|
570
|
+
const result = this.validateApiKeyFormat(providerName, value);
|
|
571
|
+
return result.valid ? 'is-valid' : 'is-invalid';
|
|
572
|
+
}
|
|
476
573
|
}
|
|
@@ -31,6 +31,7 @@ export const enUS: TranslationKeys = {
|
|
|
31
31
|
title: 'Settings',
|
|
32
32
|
generalTab: 'General',
|
|
33
33
|
providersTab: 'AI Providers',
|
|
34
|
+
contextTab: 'Context',
|
|
34
35
|
securityTab: 'Security',
|
|
35
36
|
chatTab: 'Chat',
|
|
36
37
|
advancedTab: 'Advanced'
|
|
@@ -135,7 +136,9 @@ export const enUS: TranslationKeys = {
|
|
|
135
136
|
testFail: 'Connection test failed',
|
|
136
137
|
testError: 'Cannot connect to service, please ensure it is running',
|
|
137
138
|
configSaved: 'Configuration saved',
|
|
138
|
-
configDeleted: 'Configuration deleted'
|
|
139
|
+
configDeleted: 'Configuration deleted',
|
|
140
|
+
contextWindow: 'Context Window',
|
|
141
|
+
contextWindowDesc: 'Maximum context tokens for this model'
|
|
139
142
|
},
|
|
140
143
|
|
|
141
144
|
providerNames: {
|
|
@@ -189,5 +192,28 @@ export const enUS: TranslationKeys = {
|
|
|
189
192
|
pluginVersion: 'Plugin Version',
|
|
190
193
|
supportedProviders: 'Supported Providers',
|
|
191
194
|
currentProvider: 'Current Provider'
|
|
195
|
+
},
|
|
196
|
+
|
|
197
|
+
contextSettings: {
|
|
198
|
+
title: 'Context Management',
|
|
199
|
+
autoCompact: 'Auto Compact',
|
|
200
|
+
enableAutoCompact: 'Enable Auto Compact',
|
|
201
|
+
autoCompactDesc: 'Automatically compress history when context exceeds threshold',
|
|
202
|
+
autoCompactEnabled: 'Auto Compact Enabled',
|
|
203
|
+
autoCompactDisabled: 'Auto Compact Disabled',
|
|
204
|
+
tokenConfig: 'Token Configuration',
|
|
205
|
+
currentProviderLimit: 'Current Provider Context Limit',
|
|
206
|
+
maxContextTokens: 'Max Context Tokens',
|
|
207
|
+
maxContextTokensDesc: 'Cannot exceed current provider context limit',
|
|
208
|
+
reservedOutputTokens: 'Reserved Output Tokens',
|
|
209
|
+
reservedOutputTokensDesc: 'Token count reserved for AI response',
|
|
210
|
+
thresholdConfig: 'Compression Thresholds',
|
|
211
|
+
pruneThreshold: 'Prune Threshold',
|
|
212
|
+
pruneThresholdDesc: 'Prune redundant content when usage exceeds this (default: 70%)',
|
|
213
|
+
compactThreshold: 'Compact Threshold',
|
|
214
|
+
compactThresholdDesc: 'Generate summary when usage exceeds this (default: 85%)',
|
|
215
|
+
messagesToKeep: 'Messages to Keep',
|
|
216
|
+
messagesToKeepDesc: 'Recent messages to always preserve (default: 3)',
|
|
217
|
+
configSaved: 'Context config saved'
|
|
192
218
|
}
|
|
193
219
|
};
|
|
@@ -31,6 +31,7 @@ export const jaJP: TranslationKeys = {
|
|
|
31
31
|
title: '設定',
|
|
32
32
|
generalTab: '基本設定',
|
|
33
33
|
providersTab: 'AIプロバイダー',
|
|
34
|
+
contextTab: 'コンテキスト',
|
|
34
35
|
securityTab: 'セキュリティ',
|
|
35
36
|
chatTab: 'チャット',
|
|
36
37
|
advancedTab: '詳細設定'
|
|
@@ -135,7 +136,9 @@ export const jaJP: TranslationKeys = {
|
|
|
135
136
|
testFail: '接続テスト失敗',
|
|
136
137
|
testError: 'サービスに接続できません。サービスが起動していることを確認してください。',
|
|
137
138
|
configSaved: '設定が保存されました',
|
|
138
|
-
configDeleted: '設定が削除されました'
|
|
139
|
+
configDeleted: '設定が削除されました',
|
|
140
|
+
contextWindow: 'コンテキストウィンドウ',
|
|
141
|
+
contextWindowDesc: 'モデルの最大コンテキストトークン数'
|
|
139
142
|
},
|
|
140
143
|
|
|
141
144
|
providerNames: {
|
|
@@ -189,5 +192,28 @@ export const jaJP: TranslationKeys = {
|
|
|
189
192
|
pluginVersion: 'プラグインVersion',
|
|
190
193
|
supportedProviders: 'サポートされているプロバイダー',
|
|
191
194
|
currentProvider: '現在のリプロバイダー'
|
|
195
|
+
},
|
|
196
|
+
|
|
197
|
+
contextSettings: {
|
|
198
|
+
title: 'コンテキスト管理',
|
|
199
|
+
autoCompact: '自動圧縮',
|
|
200
|
+
enableAutoCompact: '自動圧縮を有効にする',
|
|
201
|
+
autoCompactDesc: 'コンテキストがしきい値を超えた場合に履歴を自動的に圧縮',
|
|
202
|
+
autoCompactEnabled: '自動圧縮が有効',
|
|
203
|
+
autoCompactDisabled: '自動圧縮が無効',
|
|
204
|
+
tokenConfig: 'トークン設定',
|
|
205
|
+
currentProviderLimit: '現在のリプロバイダーのコンテキスト制限',
|
|
206
|
+
maxContextTokens: '最大コンテキストトークン数',
|
|
207
|
+
maxContextTokensDesc: '現在のリプロバイダーのコンテキスト制限を超えないこと',
|
|
208
|
+
reservedOutputTokens: '出力予約トークン数',
|
|
209
|
+
reservedOutputTokensDesc: 'AI応答に予約するトークン数',
|
|
210
|
+
thresholdConfig: '圧縮しきい値',
|
|
211
|
+
pruneThreshold: 'プルーンしきい値',
|
|
212
|
+
pruneThresholdDesc: '使用率がこれを超えた場合に冗長コンテンツを削除(デフォルト:70%)',
|
|
213
|
+
compactThreshold: '圧縮しきい値',
|
|
214
|
+
compactThresholdDesc: '使用率がこれを超えた場合にサマリーを生成(デフォルト:85%)',
|
|
215
|
+
messagesToKeep: '保持するメッセージ数',
|
|
216
|
+
messagesToKeepDesc: '圧縮時に常に保持する最新メッセージ数(デフォルト:3)',
|
|
217
|
+
configSaved: 'コンテキスト設定が保存されました'
|
|
192
218
|
}
|
|
193
219
|
};
|
|
@@ -31,6 +31,7 @@ export const zhCN: TranslationKeys = {
|
|
|
31
31
|
title: '设置',
|
|
32
32
|
generalTab: '基本设置',
|
|
33
33
|
providersTab: 'AI提供商',
|
|
34
|
+
contextTab: '上下文设置',
|
|
34
35
|
securityTab: '安全设置',
|
|
35
36
|
chatTab: '聊天设置',
|
|
36
37
|
advancedTab: '高级设置'
|
|
@@ -135,7 +136,9 @@ export const zhCN: TranslationKeys = {
|
|
|
135
136
|
testFail: '连接测试失败',
|
|
136
137
|
testError: '无法连接到服务,请确保服务已启动',
|
|
137
138
|
configSaved: '配置已保存',
|
|
138
|
-
configDeleted: '配置已删除'
|
|
139
|
+
configDeleted: '配置已删除',
|
|
140
|
+
contextWindow: '上下文限制',
|
|
141
|
+
contextWindowDesc: '模型的最大上下文Token数'
|
|
139
142
|
},
|
|
140
143
|
|
|
141
144
|
providerNames: {
|
|
@@ -189,5 +192,28 @@ export const zhCN: TranslationKeys = {
|
|
|
189
192
|
pluginVersion: '插件版本',
|
|
190
193
|
supportedProviders: '支持的提供商',
|
|
191
194
|
currentProvider: '当前提供商'
|
|
195
|
+
},
|
|
196
|
+
|
|
197
|
+
contextSettings: {
|
|
198
|
+
title: '上下文管理',
|
|
199
|
+
autoCompact: '自动压缩',
|
|
200
|
+
enableAutoCompact: '启用自动压缩',
|
|
201
|
+
autoCompactDesc: '当上下文超过阈值时自动压缩历史消息',
|
|
202
|
+
autoCompactEnabled: '自动压缩已启用',
|
|
203
|
+
autoCompactDisabled: '自动压缩已禁用',
|
|
204
|
+
tokenConfig: 'Token 配置',
|
|
205
|
+
currentProviderLimit: '当前供应商上下文限制',
|
|
206
|
+
maxContextTokens: '最大上下文 Token 数',
|
|
207
|
+
maxContextTokensDesc: '不能超过当前供应商的上下文限制',
|
|
208
|
+
reservedOutputTokens: '输出预留 Token 数',
|
|
209
|
+
reservedOutputTokensDesc: '为 AI 回复预留的 Token 数量',
|
|
210
|
+
thresholdConfig: '压缩阈值',
|
|
211
|
+
pruneThreshold: '裁剪阈值',
|
|
212
|
+
pruneThresholdDesc: '使用率超过此阈值时裁剪冗余内容(默认:70%)',
|
|
213
|
+
compactThreshold: '压缩阈值',
|
|
214
|
+
compactThresholdDesc: '使用率超过此阈值时生成摘要压缩(默认:85%)',
|
|
215
|
+
messagesToKeep: '保留消息数',
|
|
216
|
+
messagesToKeepDesc: '压缩时始终保留的最近消息数量(默认:3)',
|
|
217
|
+
configSaved: '上下文配置已保存'
|
|
192
218
|
}
|
|
193
219
|
};
|