tabby-ai-assistant 1.0.8 → 1.0.10
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/components/chat/ai-sidebar.component.d.ts +16 -3
- package/dist/components/chat/chat-input.component.d.ts +4 -0
- package/dist/components/chat/chat-interface.component.d.ts +22 -1
- package/dist/components/chat/chat-settings.component.d.ts +21 -11
- package/dist/components/settings/ai-settings-tab.component.d.ts +14 -4
- package/dist/components/settings/general-settings.component.d.ts +43 -12
- package/dist/components/settings/provider-config.component.d.ts +110 -5
- package/dist/components/settings/security-settings.component.d.ts +14 -4
- package/dist/i18n/index.d.ts +48 -0
- package/dist/i18n/translations/en-US.d.ts +5 -0
- package/dist/i18n/translations/ja-JP.d.ts +5 -0
- package/dist/i18n/translations/zh-CN.d.ts +5 -0
- package/dist/i18n/types.d.ts +198 -0
- package/dist/index.js +1 -1
- package/dist/services/chat/ai-sidebar.service.d.ts +23 -1
- package/dist/services/core/theme.service.d.ts +53 -0
- package/dist/services/core/toast.service.d.ts +15 -0
- package/package.json +1 -1
- package/src/components/chat/ai-sidebar.component.scss +468 -0
- package/src/components/chat/ai-sidebar.component.ts +47 -344
- package/src/components/chat/chat-input.component.scss +2 -2
- package/src/components/chat/chat-input.component.ts +16 -5
- package/src/components/chat/chat-interface.component.html +11 -11
- package/src/components/chat/chat-interface.component.scss +410 -4
- package/src/components/chat/chat-interface.component.ts +105 -14
- package/src/components/chat/chat-message.component.scss +3 -3
- package/src/components/chat/chat-message.component.ts +3 -2
- package/src/components/chat/chat-settings.component.html +95 -61
- package/src/components/chat/chat-settings.component.scss +224 -50
- package/src/components/chat/chat-settings.component.ts +56 -30
- package/src/components/security/risk-confirm-dialog.component.scss +7 -7
- package/src/components/settings/ai-settings-tab.component.html +27 -27
- package/src/components/settings/ai-settings-tab.component.scss +34 -20
- package/src/components/settings/ai-settings-tab.component.ts +59 -20
- package/src/components/settings/general-settings.component.html +69 -40
- package/src/components/settings/general-settings.component.scss +151 -58
- package/src/components/settings/general-settings.component.ts +168 -55
- package/src/components/settings/provider-config.component.html +183 -60
- package/src/components/settings/provider-config.component.scss +332 -153
- package/src/components/settings/provider-config.component.ts +268 -19
- package/src/components/settings/security-settings.component.html +70 -39
- package/src/components/settings/security-settings.component.scss +104 -8
- package/src/components/settings/security-settings.component.ts +48 -10
- package/src/i18n/index.ts +129 -0
- package/src/i18n/translations/en-US.ts +193 -0
- package/src/i18n/translations/ja-JP.ts +193 -0
- package/src/i18n/translations/zh-CN.ts +193 -0
- package/src/i18n/types.ts +224 -0
- package/src/index.ts +6 -0
- package/src/services/chat/ai-sidebar.service.ts +157 -5
- package/src/services/core/theme.service.ts +480 -0
- package/src/services/core/toast.service.ts +36 -0
- package/src/styles/ai-assistant.scss +8 -88
- package/src/styles/themes.scss +161 -0
|
@@ -1,13 +1,18 @@
|
|
|
1
|
-
import { Component, OnInit } from '@angular/core';
|
|
1
|
+
import { Component, OnInit, OnDestroy, ViewEncapsulation } from '@angular/core';
|
|
2
|
+
import { Subject } from 'rxjs';
|
|
3
|
+
import { takeUntil } from 'rxjs/operators';
|
|
2
4
|
import { ConfigProviderService } from '../../services/core/config-provider.service';
|
|
3
5
|
import { LoggerService } from '../../services/core/logger.service';
|
|
6
|
+
import { ThemeService, ThemeType } from '../../services/core/theme.service';
|
|
7
|
+
import { TranslateService } from '../../i18n';
|
|
4
8
|
|
|
5
9
|
@Component({
|
|
6
10
|
selector: 'app-chat-settings',
|
|
7
11
|
templateUrl: './chat-settings.component.html',
|
|
8
|
-
styleUrls: ['./chat-settings.component.scss']
|
|
12
|
+
styleUrls: ['./chat-settings.component.scss'],
|
|
13
|
+
encapsulation: ViewEncapsulation.None
|
|
9
14
|
})
|
|
10
|
-
export class ChatSettingsComponent implements OnInit {
|
|
15
|
+
export class ChatSettingsComponent implements OnInit, OnDestroy {
|
|
11
16
|
settings: {
|
|
12
17
|
chatHistoryEnabled: boolean;
|
|
13
18
|
maxChatHistory: number;
|
|
@@ -32,23 +37,59 @@ export class ChatSettingsComponent implements OnInit {
|
|
|
32
37
|
soundEnabled: true
|
|
33
38
|
};
|
|
34
39
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
{ value: 'light', label: '浅色主题' },
|
|
38
|
-
{ value: 'dark', label: '深色主题' }
|
|
39
|
-
];
|
|
40
|
+
// 翻译对象
|
|
41
|
+
t: any;
|
|
40
42
|
|
|
41
43
|
fontSizes = [12, 14, 16, 18, 20];
|
|
42
44
|
|
|
45
|
+
private destroy$ = new Subject<void>();
|
|
46
|
+
|
|
43
47
|
constructor(
|
|
44
48
|
private config: ConfigProviderService,
|
|
45
|
-
private logger: LoggerService
|
|
46
|
-
|
|
49
|
+
private logger: LoggerService,
|
|
50
|
+
private translate: TranslateService,
|
|
51
|
+
private themeService: ThemeService
|
|
52
|
+
) {
|
|
53
|
+
this.t = this.translate.t;
|
|
54
|
+
}
|
|
47
55
|
|
|
48
56
|
ngOnInit(): void {
|
|
57
|
+
// 监听语言变化
|
|
58
|
+
this.translate.translation$.pipe(
|
|
59
|
+
takeUntil(this.destroy$)
|
|
60
|
+
).subscribe(translation => {
|
|
61
|
+
this.t = translation;
|
|
62
|
+
this.updateThemeLabels();
|
|
63
|
+
});
|
|
64
|
+
|
|
49
65
|
this.loadSettings();
|
|
50
66
|
}
|
|
51
67
|
|
|
68
|
+
ngOnDestroy(): void {
|
|
69
|
+
this.destroy$.next();
|
|
70
|
+
this.destroy$.complete();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* 更新主题标签翻译
|
|
75
|
+
*/
|
|
76
|
+
private updateThemeLabels(): void {
|
|
77
|
+
this.settings.theme = this.config.get('theme', 'auto') ?? 'auto';
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* 获取主题选项
|
|
82
|
+
*/
|
|
83
|
+
get themes() {
|
|
84
|
+
return [
|
|
85
|
+
{ value: 'auto', label: this.t.general.themeAuto },
|
|
86
|
+
{ value: 'light', label: this.t.general.themeLight },
|
|
87
|
+
{ value: 'dark', label: this.t.general.themeDark },
|
|
88
|
+
{ value: 'pixel', label: this.t.general.themePixel || '像素复古' },
|
|
89
|
+
{ value: 'tech', label: this.t.general.themeTech || '赛博科技' }
|
|
90
|
+
];
|
|
91
|
+
}
|
|
92
|
+
|
|
52
93
|
/**
|
|
53
94
|
* 加载设置
|
|
54
95
|
*/
|
|
@@ -83,22 +124,7 @@ export class ChatSettingsComponent implements OnInit {
|
|
|
83
124
|
updateTheme(theme: string): void {
|
|
84
125
|
this.settings.theme = theme;
|
|
85
126
|
this.saveSetting('theme', theme);
|
|
86
|
-
this.applyTheme(theme);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* 应用主题
|
|
91
|
-
*/
|
|
92
|
-
private applyTheme(theme: string): void {
|
|
93
|
-
const body = document.body;
|
|
94
|
-
body.classList.remove('light-theme', 'dark-theme');
|
|
95
|
-
|
|
96
|
-
if (theme === 'light') {
|
|
97
|
-
body.classList.add('light-theme');
|
|
98
|
-
} else if (theme === 'dark') {
|
|
99
|
-
body.classList.add('dark-theme');
|
|
100
|
-
}
|
|
101
|
-
// 'auto' 主题由系统决定
|
|
127
|
+
this.themeService.applyTheme(theme as ThemeType);
|
|
102
128
|
}
|
|
103
129
|
|
|
104
130
|
/**
|
|
@@ -123,10 +149,10 @@ export class ChatSettingsComponent implements OnInit {
|
|
|
123
149
|
* 清空聊天历史
|
|
124
150
|
*/
|
|
125
151
|
clearChatHistory(): void {
|
|
126
|
-
if (confirm(
|
|
152
|
+
if (confirm(this.t.chatSettings.clearHistoryConfirm)) {
|
|
127
153
|
localStorage.removeItem('ai-assistant-chat-history');
|
|
128
154
|
this.logger.info('Chat history cleared');
|
|
129
|
-
alert(
|
|
155
|
+
alert(this.t.providers.configDeleted);
|
|
130
156
|
}
|
|
131
157
|
}
|
|
132
158
|
|
|
@@ -152,7 +178,7 @@ export class ChatSettingsComponent implements OnInit {
|
|
|
152
178
|
* 重置为默认设置
|
|
153
179
|
*/
|
|
154
180
|
resetToDefaults(): void {
|
|
155
|
-
if (confirm(
|
|
181
|
+
if (confirm(this.t.chatSettings.resetConfirm)) {
|
|
156
182
|
this.settings = {
|
|
157
183
|
chatHistoryEnabled: true,
|
|
158
184
|
maxChatHistory: 100,
|
|
@@ -173,7 +199,7 @@ export class ChatSettingsComponent implements OnInit {
|
|
|
173
199
|
});
|
|
174
200
|
|
|
175
201
|
this.logger.info('Chat settings reset to defaults');
|
|
176
|
-
alert(
|
|
202
|
+
alert(this.t.providers.configSaved);
|
|
177
203
|
}
|
|
178
204
|
}
|
|
179
205
|
}
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
span {
|
|
29
|
-
color: var(--ai-
|
|
29
|
+
color: var(--ai-text-primary);
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
|
|
46
46
|
&:hover {
|
|
47
47
|
background-color: var(--ai-border);
|
|
48
|
-
color: var(--ai-
|
|
48
|
+
color: var(--ai-text-primary);
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
}
|
|
@@ -120,7 +120,7 @@
|
|
|
120
120
|
display: block;
|
|
121
121
|
font-weight: 600;
|
|
122
122
|
margin-bottom: 0.5rem;
|
|
123
|
-
color: var(--ai-
|
|
123
|
+
color: var(--ai-text-primary);
|
|
124
124
|
font-size: 0.875rem;
|
|
125
125
|
}
|
|
126
126
|
|
|
@@ -137,7 +137,7 @@
|
|
|
137
137
|
flex: 1;
|
|
138
138
|
font-family: 'Courier New', Courier, monospace;
|
|
139
139
|
font-size: 0.875rem;
|
|
140
|
-
color: var(--ai-
|
|
140
|
+
color: var(--ai-text-primary);
|
|
141
141
|
word-break: break-all;
|
|
142
142
|
}
|
|
143
143
|
|
|
@@ -156,7 +156,7 @@
|
|
|
156
156
|
|
|
157
157
|
&:hover {
|
|
158
158
|
background-color: var(--ai-border);
|
|
159
|
-
color: var(--ai-
|
|
159
|
+
color: var(--ai-text-primary);
|
|
160
160
|
}
|
|
161
161
|
}
|
|
162
162
|
}
|
|
@@ -169,7 +169,7 @@
|
|
|
169
169
|
border-radius: 0.375rem;
|
|
170
170
|
font-size: 0.875rem;
|
|
171
171
|
line-height: 1.6;
|
|
172
|
-
color: var(--ai-
|
|
172
|
+
color: var(--ai-text-primary);
|
|
173
173
|
}
|
|
174
174
|
|
|
175
175
|
.suggestions-list {
|
|
@@ -201,7 +201,7 @@
|
|
|
201
201
|
flex: 1;
|
|
202
202
|
font-size: 0.875rem;
|
|
203
203
|
line-height: 1.5;
|
|
204
|
-
color: var(--ai-
|
|
204
|
+
color: var(--ai-text-primary);
|
|
205
205
|
}
|
|
206
206
|
}
|
|
207
207
|
}
|
|
@@ -4,30 +4,30 @@
|
|
|
4
4
|
<div class="header-content">
|
|
5
5
|
<h2>
|
|
6
6
|
<i class="fa fa-robot"></i>
|
|
7
|
-
|
|
7
|
+
{{ t.chatInterface.title }}{{ t.settings.title }}
|
|
8
8
|
</h2>
|
|
9
9
|
<div class="header-status">
|
|
10
10
|
<div class="status-indicator" [class.enabled]="isEnabled">
|
|
11
11
|
<i class="fa fa-power-off"></i>
|
|
12
|
-
<span>{{ isEnabled ?
|
|
12
|
+
<span>{{ isEnabled ? t.common.enabled : t.common.disabled }}</span>
|
|
13
13
|
</div>
|
|
14
14
|
<div class="provider-info" *ngIf="isEnabled">
|
|
15
15
|
<i class="fa fa-cloud"></i>
|
|
16
|
-
<span
|
|
16
|
+
<span>{{ t.advancedSettings.currentProvider }}: {{ currentProvider }}</span>
|
|
17
17
|
</div>
|
|
18
18
|
</div>
|
|
19
19
|
</div>
|
|
20
20
|
<div class="header-actions">
|
|
21
|
-
<button class="btn-icon" (click)="toggleEnabled()" [title]="isEnabled ?
|
|
21
|
+
<button class="btn-icon" (click)="toggleEnabled()" [title]="isEnabled ? t.common.disabled : t.common.enabled">
|
|
22
22
|
<i [class]="isEnabled ? 'fa fa-pause' : 'fa fa-play'"></i>
|
|
23
23
|
</button>
|
|
24
|
-
<button class="btn-icon" (click)="refreshProviderStatus()" title="
|
|
24
|
+
<button class="btn-icon" (click)="refreshProviderStatus()" [title]="t.general.shortcutGenerate">
|
|
25
25
|
<i class="fa fa-refresh"></i>
|
|
26
26
|
</button>
|
|
27
|
-
<button class="btn-icon" (click)="exportConfig()" title="
|
|
27
|
+
<button class="btn-icon" (click)="exportConfig()" [title]="t.chatInterface.exportChat">
|
|
28
28
|
<i class="fa fa-download"></i>
|
|
29
29
|
</button>
|
|
30
|
-
<button class="btn-icon" (click)="importConfig()" title="
|
|
30
|
+
<button class="btn-icon" (click)="importConfig()" [title]="t.common.add">
|
|
31
31
|
<i class="fa fa-upload"></i>
|
|
32
32
|
</button>
|
|
33
33
|
</div>
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
<button *ngFor="let tab of tabs" class="tab-button" [class.active]="activeTab === tab.id"
|
|
39
39
|
(click)="switchTab(tab.id)">
|
|
40
40
|
<i [class]="tab.icon"></i>
|
|
41
|
-
<span>{{ tab.
|
|
41
|
+
<span>{{ t[tab.labelKey.split('.')[0]][tab.labelKey.split('.')[1]] }}</span>
|
|
42
42
|
</button>
|
|
43
43
|
</div>
|
|
44
44
|
|
|
@@ -69,50 +69,50 @@
|
|
|
69
69
|
<!-- 高级设置 -->
|
|
70
70
|
<div *ngIf="activeTab === 'advanced'" class="tab-content">
|
|
71
71
|
<div class="advanced-settings">
|
|
72
|
-
<h3
|
|
72
|
+
<h3>{{ t.advancedSettings.title }}</h3>
|
|
73
73
|
|
|
74
74
|
<!-- 配置验证 -->
|
|
75
75
|
<div class="settings-section">
|
|
76
|
-
<h4
|
|
76
|
+
<h4>{{ t.advancedSettings.configManagement }}</h4>
|
|
77
77
|
<button class="btn btn-primary" (click)="validateConfig()">
|
|
78
78
|
<i class="fa fa-check"></i>
|
|
79
|
-
|
|
79
|
+
{{ t.advancedSettings.validateConfig }}
|
|
80
80
|
</button>
|
|
81
81
|
<button class="btn btn-warning" (click)="resetToDefaults()">
|
|
82
82
|
<i class="fa fa-refresh"></i>
|
|
83
|
-
|
|
83
|
+
{{ t.advancedSettings.resetDefaults }}
|
|
84
84
|
</button>
|
|
85
85
|
</div>
|
|
86
86
|
|
|
87
87
|
<!-- 日志设置 -->
|
|
88
88
|
<div class="settings-section">
|
|
89
|
-
<h4
|
|
89
|
+
<h4>{{ t.advancedSettings.logSettings }}</h4>
|
|
90
90
|
<div class="log-level-selector">
|
|
91
|
-
<label
|
|
91
|
+
<label>{{ t.advancedSettings.logLevel }}</label>
|
|
92
92
|
<select class="form-control" [(ngModel)]="config.logLevel"
|
|
93
93
|
(change)="config.setLogLevel(config.logLevel)">
|
|
94
|
-
<option value="debug">
|
|
95
|
-
<option value="info">
|
|
96
|
-
<option value="warn">
|
|
97
|
-
<option value="error">
|
|
94
|
+
<option value="debug">{{ t.advancedSettings.logLevels.debug }}</option>
|
|
95
|
+
<option value="info">{{ t.advancedSettings.logLevels.info }}</option>
|
|
96
|
+
<option value="warn">{{ t.advancedSettings.logLevels.warn }}</option>
|
|
97
|
+
<option value="error">{{ t.advancedSettings.logLevels.error }}</option>
|
|
98
98
|
</select>
|
|
99
99
|
</div>
|
|
100
100
|
</div>
|
|
101
101
|
|
|
102
102
|
<!-- 系统信息 -->
|
|
103
103
|
<div class="settings-section">
|
|
104
|
-
<h4
|
|
104
|
+
<h4>{{ t.advancedSettings.systemInfo }}</h4>
|
|
105
105
|
<div class="system-info">
|
|
106
106
|
<div class="info-item">
|
|
107
|
-
<label
|
|
108
|
-
<span>
|
|
107
|
+
<label>{{ t.advancedSettings.pluginVersion }}:</label>
|
|
108
|
+
<span>{{ pluginVersion }}</span>
|
|
109
109
|
</div>
|
|
110
110
|
<div class="info-item">
|
|
111
|
-
<label
|
|
111
|
+
<label>{{ t.advancedSettings.supportedProviders }}:</label>
|
|
112
112
|
<span>{{ providerStatus.count }}</span>
|
|
113
113
|
</div>
|
|
114
114
|
<div class="info-item">
|
|
115
|
-
<label
|
|
115
|
+
<label>{{ t.advancedSettings.currentProvider }}:</label>
|
|
116
116
|
<span>{{ currentProvider }}</span>
|
|
117
117
|
</div>
|
|
118
118
|
</div>
|
|
@@ -125,12 +125,12 @@
|
|
|
125
125
|
<div class="disabled-message" *ngIf="!isEnabled">
|
|
126
126
|
<div class="message-content">
|
|
127
127
|
<i class="fa fa-pause-circle fa-3x"></i>
|
|
128
|
-
<h3>
|
|
129
|
-
<p
|
|
128
|
+
<h3>{{ t.chatInterface.title }} {{ t.common.disabled }}</h3>
|
|
129
|
+
<p>{{ t.general.enableAssistantDesc }}</p>
|
|
130
130
|
<button class="btn btn-primary" (click)="toggleEnabled()">
|
|
131
131
|
<i class="fa fa-play"></i>
|
|
132
|
-
|
|
132
|
+
{{ t.general.enableAssistant }}
|
|
133
133
|
</button>
|
|
134
134
|
</div>
|
|
135
135
|
</div>
|
|
136
|
-
</div>
|
|
136
|
+
</div>
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
display: flex;
|
|
25
25
|
align-items: center;
|
|
26
26
|
gap: 0.5rem;
|
|
27
|
-
color: var(--ai-
|
|
27
|
+
color: var(--ai-text-primary);
|
|
28
28
|
|
|
29
29
|
.icon-robot {
|
|
30
30
|
font-size: 1.75rem;
|
|
@@ -58,12 +58,12 @@
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
&:not(.enabled) {
|
|
61
|
-
color: var(--ai-secondary);
|
|
61
|
+
color: var(--ai-text-secondary);
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
.provider-info {
|
|
66
|
-
color: var(--ai-secondary);
|
|
66
|
+
color: var(--ai-text-secondary);
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
}
|
|
@@ -83,11 +83,11 @@
|
|
|
83
83
|
justify-content: center;
|
|
84
84
|
cursor: pointer;
|
|
85
85
|
transition: all 0.2s;
|
|
86
|
-
color: var(--ai-secondary);
|
|
86
|
+
color: var(--ai-text-secondary);
|
|
87
87
|
|
|
88
88
|
&:hover {
|
|
89
89
|
background-color: var(--ai-border);
|
|
90
|
-
color: var(--ai-
|
|
90
|
+
color: var(--ai-text-primary);
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
.icon-refresh {
|
|
@@ -114,13 +114,13 @@
|
|
|
114
114
|
gap: 0.5rem;
|
|
115
115
|
cursor: pointer;
|
|
116
116
|
transition: all 0.2s;
|
|
117
|
-
color: var(--ai-secondary);
|
|
117
|
+
color: var(--ai-text-secondary);
|
|
118
118
|
white-space: nowrap;
|
|
119
119
|
font-size: 0.875rem;
|
|
120
120
|
font-weight: 500;
|
|
121
121
|
|
|
122
122
|
&:hover {
|
|
123
|
-
color: var(--ai-
|
|
123
|
+
color: var(--ai-text-primary);
|
|
124
124
|
background-color: rgba(0, 0, 0, 0.05);
|
|
125
125
|
}
|
|
126
126
|
|
|
@@ -158,7 +158,7 @@
|
|
|
158
158
|
margin: 0 0 1.5rem 0;
|
|
159
159
|
font-size: 1.25rem;
|
|
160
160
|
font-weight: 600;
|
|
161
|
-
color: var(--ai-
|
|
161
|
+
color: var(--ai-text-primary);
|
|
162
162
|
}
|
|
163
163
|
|
|
164
164
|
.settings-section {
|
|
@@ -172,7 +172,7 @@
|
|
|
172
172
|
margin: 0 0 1rem 0;
|
|
173
173
|
font-size: 1.125rem;
|
|
174
174
|
font-weight: 600;
|
|
175
|
-
color: var(--ai-
|
|
175
|
+
color: var(--ai-text-primary);
|
|
176
176
|
}
|
|
177
177
|
|
|
178
178
|
.btn {
|
|
@@ -200,7 +200,7 @@
|
|
|
200
200
|
|
|
201
201
|
&.btn-warning {
|
|
202
202
|
background-color: var(--ai-warning);
|
|
203
|
-
color: var(--ai-
|
|
203
|
+
color: var(--ai-text-primary);
|
|
204
204
|
|
|
205
205
|
&:hover {
|
|
206
206
|
background-color: #e0a800;
|
|
@@ -213,20 +213,34 @@
|
|
|
213
213
|
display: block;
|
|
214
214
|
font-weight: 500;
|
|
215
215
|
margin-bottom: 0.5rem;
|
|
216
|
-
color: var(--ai-
|
|
216
|
+
color: var(--ai-text-primary);
|
|
217
217
|
}
|
|
218
218
|
|
|
219
219
|
.form-control {
|
|
220
220
|
width: 200px;
|
|
221
|
-
padding: 0.5rem 0.75rem;
|
|
222
|
-
border:
|
|
221
|
+
padding: 0.5rem 2rem 0.5rem 0.75rem;
|
|
222
|
+
border: 2px solid var(--ai-border);
|
|
223
223
|
border-radius: 0.375rem;
|
|
224
|
-
background-color: var(--ai-bg-
|
|
225
|
-
color: var(--ai-
|
|
224
|
+
background-color: var(--ai-bg-secondary);
|
|
225
|
+
color: var(--ai-text-primary);
|
|
226
|
+
font-size: 0.875rem;
|
|
227
|
+
appearance: none;
|
|
228
|
+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23adb5bd' d='M6 8L1 3h10z'/%3E%3C/svg%3E");
|
|
229
|
+
background-repeat: no-repeat;
|
|
230
|
+
background-position: right 0.75rem center;
|
|
231
|
+
cursor: pointer;
|
|
226
232
|
|
|
227
233
|
&:focus {
|
|
228
234
|
outline: none;
|
|
229
235
|
border-color: var(--ai-primary);
|
|
236
|
+
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// 下拉选项样式
|
|
240
|
+
option {
|
|
241
|
+
background-color: var(--ai-bg-secondary);
|
|
242
|
+
color: var(--ai-text-primary);
|
|
243
|
+
padding: 0.5rem;
|
|
230
244
|
}
|
|
231
245
|
}
|
|
232
246
|
}
|
|
@@ -244,11 +258,11 @@
|
|
|
244
258
|
|
|
245
259
|
label {
|
|
246
260
|
font-weight: 500;
|
|
247
|
-
color: var(--ai-
|
|
261
|
+
color: var(--ai-text-primary);
|
|
248
262
|
}
|
|
249
263
|
|
|
250
264
|
span {
|
|
251
|
-
color: var(--ai-secondary);
|
|
265
|
+
color: var(--ai-text-secondary);
|
|
252
266
|
}
|
|
253
267
|
}
|
|
254
268
|
}
|
|
@@ -269,7 +283,7 @@
|
|
|
269
283
|
|
|
270
284
|
.icon-pause-circle {
|
|
271
285
|
font-size: 4rem;
|
|
272
|
-
color: var(--ai-secondary);
|
|
286
|
+
color: var(--ai-text-secondary);
|
|
273
287
|
margin-bottom: 1rem;
|
|
274
288
|
}
|
|
275
289
|
|
|
@@ -277,12 +291,12 @@
|
|
|
277
291
|
margin: 0 0 0.75rem 0;
|
|
278
292
|
font-size: 1.5rem;
|
|
279
293
|
font-weight: 600;
|
|
280
|
-
color: var(--ai-
|
|
294
|
+
color: var(--ai-text-primary);
|
|
281
295
|
}
|
|
282
296
|
|
|
283
297
|
p {
|
|
284
298
|
margin: 0 0 2rem 0;
|
|
285
|
-
color: var(--ai-secondary);
|
|
299
|
+
color: var(--ai-text-secondary);
|
|
286
300
|
font-size: 1rem;
|
|
287
301
|
}
|
|
288
302
|
|
|
@@ -1,38 +1,77 @@
|
|
|
1
|
-
import { Component, OnInit } from '@angular/core';
|
|
1
|
+
import { Component, OnInit, OnDestroy, ViewEncapsulation } from '@angular/core';
|
|
2
|
+
import { Subject } from 'rxjs';
|
|
3
|
+
import { takeUntil } from 'rxjs/operators';
|
|
2
4
|
import { AiAssistantService } from '../../services/core/ai-assistant.service';
|
|
3
5
|
import { ConfigProviderService } from '../../services/core/config-provider.service';
|
|
4
6
|
import { LoggerService } from '../../services/core/logger.service';
|
|
7
|
+
import { TranslateService } from '../../i18n';
|
|
8
|
+
|
|
9
|
+
// 动态读取 package.json 中的版本号
|
|
10
|
+
declare const require: (path: string) => any;
|
|
11
|
+
const packageJson = require('../../../package.json');
|
|
12
|
+
const PLUGIN_VERSION = packageJson.version;
|
|
5
13
|
|
|
6
14
|
@Component({
|
|
7
15
|
selector: 'app-ai-settings-tab',
|
|
8
16
|
templateUrl: './ai-settings-tab.component.html',
|
|
9
|
-
styleUrls: ['./ai-settings-tab.component.scss']
|
|
17
|
+
styleUrls: ['./ai-settings-tab.component.scss'],
|
|
18
|
+
encapsulation: ViewEncapsulation.None
|
|
10
19
|
})
|
|
11
|
-
export class AiSettingsTabComponent implements OnInit {
|
|
20
|
+
export class AiSettingsTabComponent implements OnInit, OnDestroy {
|
|
12
21
|
activeTab = 'general';
|
|
13
22
|
isEnabled = true;
|
|
14
23
|
currentProvider = '';
|
|
15
24
|
providerStatus: any = {};
|
|
25
|
+
pluginVersion: string = PLUGIN_VERSION;
|
|
26
|
+
|
|
27
|
+
// 翻译对象
|
|
28
|
+
t: any;
|
|
29
|
+
|
|
30
|
+
// Tab 定义(使用翻译 key)
|
|
31
|
+
tabs: { id: string; labelKey: string; icon: string }[] = [];
|
|
16
32
|
|
|
17
|
-
|
|
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
|
-
];
|
|
33
|
+
private destroy$ = new Subject<void>();
|
|
24
34
|
|
|
25
35
|
constructor(
|
|
26
36
|
private aiService: AiAssistantService,
|
|
27
37
|
private config: ConfigProviderService,
|
|
28
|
-
private logger: LoggerService
|
|
29
|
-
|
|
38
|
+
private logger: LoggerService,
|
|
39
|
+
private translate: TranslateService
|
|
40
|
+
) {
|
|
41
|
+
this.t = this.translate.t;
|
|
42
|
+
}
|
|
30
43
|
|
|
31
44
|
ngOnInit(): void {
|
|
45
|
+
// 监听语言变化
|
|
46
|
+
this.translate.translation$.pipe(
|
|
47
|
+
takeUntil(this.destroy$)
|
|
48
|
+
).subscribe(translation => {
|
|
49
|
+
this.t = translation;
|
|
50
|
+
this.updateTabLabels();
|
|
51
|
+
});
|
|
52
|
+
|
|
32
53
|
this.loadSettings();
|
|
33
54
|
this.loadProviderStatus();
|
|
34
55
|
}
|
|
35
56
|
|
|
57
|
+
ngOnDestroy(): void {
|
|
58
|
+
this.destroy$.next();
|
|
59
|
+
this.destroy$.complete();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 更新 Tab 标签
|
|
64
|
+
*/
|
|
65
|
+
private updateTabLabels(): void {
|
|
66
|
+
this.tabs = [
|
|
67
|
+
{ id: 'general', labelKey: 'settings.generalTab', icon: 'fa fa-cog' },
|
|
68
|
+
{ id: 'providers', labelKey: 'settings.providersTab', icon: 'fa fa-cloud' },
|
|
69
|
+
{ id: 'security', labelKey: 'settings.securityTab', icon: 'fa fa-shield' },
|
|
70
|
+
{ id: 'chat', labelKey: 'settings.chatTab', icon: 'fa fa-comments' },
|
|
71
|
+
{ id: 'advanced', labelKey: 'settings.advancedTab', icon: 'fa fa-sliders' }
|
|
72
|
+
];
|
|
73
|
+
}
|
|
74
|
+
|
|
36
75
|
/**
|
|
37
76
|
* 加载设置
|
|
38
77
|
*/
|
|
@@ -115,7 +154,7 @@ export class AiSettingsTabComponent implements OnInit {
|
|
|
115
154
|
* 获取提供商状态文本
|
|
116
155
|
*/
|
|
117
156
|
getProviderStatusText(healthy: boolean): string {
|
|
118
|
-
return healthy ?
|
|
157
|
+
return healthy ? this.t.common.enabled : this.t.common.disabled;
|
|
119
158
|
}
|
|
120
159
|
|
|
121
160
|
/**
|
|
@@ -127,13 +166,13 @@ export class AiSettingsTabComponent implements OnInit {
|
|
|
127
166
|
const invalidProviders = results.filter(r => !r.valid);
|
|
128
167
|
|
|
129
168
|
if (invalidProviders.length > 0) {
|
|
130
|
-
alert(
|
|
169
|
+
alert(`${this.t.advancedSettings.validateConfig}: ${invalidProviders.length} ${this.t.chatSettings.resetConfirm}`);
|
|
131
170
|
} else {
|
|
132
|
-
alert(
|
|
171
|
+
alert(this.t.providers.testSuccess);
|
|
133
172
|
}
|
|
134
173
|
} catch (error) {
|
|
135
174
|
this.logger.error('Failed to validate config', error);
|
|
136
|
-
alert(
|
|
175
|
+
alert(this.t.chatInterface.errorPrefix);
|
|
137
176
|
}
|
|
138
177
|
}
|
|
139
178
|
|
|
@@ -166,11 +205,11 @@ export class AiSettingsTabComponent implements OnInit {
|
|
|
166
205
|
try {
|
|
167
206
|
const config = e.target?.result as string;
|
|
168
207
|
this.config.importConfig(config);
|
|
169
|
-
alert(
|
|
208
|
+
alert(this.t.providers.configSaved);
|
|
170
209
|
this.loadSettings();
|
|
171
210
|
this.loadProviderStatus();
|
|
172
211
|
} catch (error) {
|
|
173
|
-
alert(
|
|
212
|
+
alert(this.t.chatInterface.errorPrefix);
|
|
174
213
|
}
|
|
175
214
|
};
|
|
176
215
|
reader.readAsText(file);
|
|
@@ -183,11 +222,11 @@ export class AiSettingsTabComponent implements OnInit {
|
|
|
183
222
|
* 重置为默认配置
|
|
184
223
|
*/
|
|
185
224
|
resetToDefaults(): void {
|
|
186
|
-
if (confirm(
|
|
225
|
+
if (confirm(this.t.chatSettings.resetConfirm)) {
|
|
187
226
|
this.config.reset();
|
|
188
227
|
this.loadSettings();
|
|
189
228
|
this.loadProviderStatus();
|
|
190
|
-
alert(
|
|
229
|
+
alert(this.t.providers.configSaved);
|
|
191
230
|
}
|
|
192
231
|
}
|
|
193
232
|
}
|