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.
Files changed (54) hide show
  1. package/dist/components/chat/ai-sidebar.component.d.ts +16 -3
  2. package/dist/components/chat/chat-input.component.d.ts +4 -0
  3. package/dist/components/chat/chat-interface.component.d.ts +22 -1
  4. package/dist/components/chat/chat-settings.component.d.ts +21 -11
  5. package/dist/components/settings/ai-settings-tab.component.d.ts +14 -4
  6. package/dist/components/settings/general-settings.component.d.ts +43 -12
  7. package/dist/components/settings/provider-config.component.d.ts +110 -5
  8. package/dist/components/settings/security-settings.component.d.ts +14 -4
  9. package/dist/i18n/index.d.ts +48 -0
  10. package/dist/i18n/translations/en-US.d.ts +5 -0
  11. package/dist/i18n/translations/ja-JP.d.ts +5 -0
  12. package/dist/i18n/translations/zh-CN.d.ts +5 -0
  13. package/dist/i18n/types.d.ts +198 -0
  14. package/dist/index.js +1 -1
  15. package/dist/services/chat/ai-sidebar.service.d.ts +23 -1
  16. package/dist/services/core/theme.service.d.ts +53 -0
  17. package/dist/services/core/toast.service.d.ts +15 -0
  18. package/package.json +1 -1
  19. package/src/components/chat/ai-sidebar.component.scss +468 -0
  20. package/src/components/chat/ai-sidebar.component.ts +47 -344
  21. package/src/components/chat/chat-input.component.scss +2 -2
  22. package/src/components/chat/chat-input.component.ts +16 -5
  23. package/src/components/chat/chat-interface.component.html +11 -11
  24. package/src/components/chat/chat-interface.component.scss +410 -4
  25. package/src/components/chat/chat-interface.component.ts +105 -14
  26. package/src/components/chat/chat-message.component.scss +3 -3
  27. package/src/components/chat/chat-message.component.ts +3 -2
  28. package/src/components/chat/chat-settings.component.html +95 -61
  29. package/src/components/chat/chat-settings.component.scss +224 -50
  30. package/src/components/chat/chat-settings.component.ts +56 -30
  31. package/src/components/security/risk-confirm-dialog.component.scss +7 -7
  32. package/src/components/settings/ai-settings-tab.component.html +27 -27
  33. package/src/components/settings/ai-settings-tab.component.scss +34 -20
  34. package/src/components/settings/ai-settings-tab.component.ts +59 -20
  35. package/src/components/settings/general-settings.component.html +69 -40
  36. package/src/components/settings/general-settings.component.scss +151 -58
  37. package/src/components/settings/general-settings.component.ts +168 -55
  38. package/src/components/settings/provider-config.component.html +183 -60
  39. package/src/components/settings/provider-config.component.scss +332 -153
  40. package/src/components/settings/provider-config.component.ts +268 -19
  41. package/src/components/settings/security-settings.component.html +70 -39
  42. package/src/components/settings/security-settings.component.scss +104 -8
  43. package/src/components/settings/security-settings.component.ts +48 -10
  44. package/src/i18n/index.ts +129 -0
  45. package/src/i18n/translations/en-US.ts +193 -0
  46. package/src/i18n/translations/ja-JP.ts +193 -0
  47. package/src/i18n/translations/zh-CN.ts +193 -0
  48. package/src/i18n/types.ts +224 -0
  49. package/src/index.ts +6 -0
  50. package/src/services/chat/ai-sidebar.service.ts +157 -5
  51. package/src/services/core/theme.service.ts +480 -0
  52. package/src/services/core/toast.service.ts +36 -0
  53. package/src/styles/ai-assistant.scss +8 -88
  54. package/src/styles/themes.scss +161 -0
@@ -1,11 +1,13 @@
1
- import { Component, OnInit, OnDestroy, ViewChild, ElementRef, AfterViewChecked } from '@angular/core';
1
+ import { Component, OnInit, OnDestroy, ViewChild, ElementRef, AfterViewChecked, AfterViewInit, ViewEncapsulation, HostBinding } from '@angular/core';
2
2
  import { Subject } from 'rxjs';
3
+ import { takeUntil } from 'rxjs/operators';
3
4
  import { ChatMessage, MessageRole } from '../../types/ai.types';
4
5
  import { AiAssistantService } from '../../services/core/ai-assistant.service';
5
6
  import { ConfigProviderService } from '../../services/core/config-provider.service';
6
7
  import { LoggerService } from '../../services/core/logger.service';
7
8
  import { ChatHistoryService } from '../../services/chat/chat-history.service';
8
9
  import { AiSidebarService } from '../../services/chat/ai-sidebar.service';
10
+ import { ThemeService, ThemeType } from '../../services/core/theme.service';
9
11
 
10
12
  /**
11
13
  * AI Sidebar 组件 - 替代 ChatInterfaceComponent
@@ -137,349 +139,17 @@ import { AiSidebarService } from '../../services/chat/ai-sidebar.service';
137
139
  </div>
138
140
  </div>
139
141
  `,
140
- styles: [`
141
- :host {
142
- display: block;
143
- height: 100%;
144
- width: 100%;
145
- }
146
-
147
- .ai-sidebar-container {
148
- display: flex;
149
- flex-direction: column;
150
- height: 100%;
151
- background: var(--bs-body-bg, #1e1e1e);
152
- color: var(--bs-body-color, #e0e0e0);
153
- overflow: hidden;
154
- }
155
-
156
- .ai-sidebar-header {
157
- display: flex;
158
- justify-content: space-between;
159
- align-items: center;
160
- padding: 12px 16px;
161
- border-bottom: 1px solid var(--bs-border-color, #333);
162
- background: var(--bs-tertiary-bg, #252525);
163
- }
164
-
165
- .header-title {
166
- display: flex;
167
- align-items: center;
168
- gap: 8px;
169
- font-weight: 600;
170
- font-size: 14px;
171
- }
172
-
173
- .header-icon {
174
- color: var(--bs-primary, #0d6efd);
175
- }
176
-
177
- .provider-badge {
178
- padding: 2px 6px;
179
- background: var(--bs-secondary-bg, #2a2a2a);
180
- border-radius: 4px;
181
- font-size: 11px;
182
- color: var(--bs-secondary-color, #aaa);
183
- font-weight: normal;
184
- }
185
-
186
- .header-actions {
187
- display: flex;
188
- gap: 4px;
189
- }
190
-
191
- .header-actions .btn-link {
192
- padding: 4px 8px;
193
- color: var(--bs-secondary-color, #aaa);
194
- border: none;
195
- background: transparent;
196
- cursor: pointer;
197
- border-radius: 4px;
198
- display: flex;
199
- align-items: center;
200
- justify-content: center;
201
- transition: all 0.2s;
202
- }
203
-
204
- .header-actions .btn-link:hover {
205
- color: var(--bs-primary, #0d6efd);
206
- background: var(--bs-hover-bg, rgba(255, 255, 255, 0.05));
207
- }
208
-
209
- .header-actions .btn-close-sidebar {
210
- margin-right: 4px;
211
- padding-right: 8px;
212
- border-right: 1px solid var(--bs-border-color, #333);
213
- }
214
-
215
- .header-actions .btn-close-sidebar:hover {
216
- color: var(--bs-danger, #dc3545);
217
- }
218
-
219
- .ai-sidebar-messages {
220
- flex: 1;
221
- overflow-y: auto;
222
- overflow-x: hidden;
223
- padding: 16px;
224
- scroll-behavior: smooth;
225
- }
226
-
227
- .ai-sidebar-messages::-webkit-scrollbar {
228
- width: 8px;
229
- }
230
-
231
- .ai-sidebar-messages::-webkit-scrollbar-track {
232
- background: var(--bs-body-bg, #1e1e1e);
233
- }
234
-
235
- .ai-sidebar-messages::-webkit-scrollbar-thumb {
236
- background: var(--bs-border-color, #333);
237
- border-radius: 4px;
238
- }
239
-
240
- .ai-sidebar-messages::-webkit-scrollbar-thumb:hover {
241
- background: var(--bs-secondary-color, #666);
242
- }
243
-
244
- .message-item {
245
- display: flex;
246
- gap: 12px;
247
- margin-bottom: 16px;
248
- animation: fadeIn 0.3s ease-in;
249
- }
250
-
251
- @keyframes fadeIn {
252
- from {
253
- opacity: 0;
254
- transform: translateY(10px);
255
- }
256
- to {
257
- opacity: 1;
258
- transform: translateY(0);
259
- }
260
- }
261
-
262
- .message-avatar {
263
- flex-shrink: 0;
264
- width: 32px;
265
- height: 32px;
266
- border-radius: 50%;
267
- display: flex;
268
- align-items: center;
269
- justify-content: center;
270
- background: var(--bs-secondary-bg, #2a2a2a);
271
- color: var(--bs-primary, #0d6efd);
272
- }
273
-
274
- .message-item.assistant .message-avatar {
275
- color: var(--bs-primary, #0d6efd);
276
- }
277
-
278
- .message-item.user .message-avatar {
279
- color: var(--bs-success, #28a745);
280
- }
281
-
282
- .message-content {
283
- flex: 1;
284
- min-width: 0;
285
- }
286
-
287
- .message-header {
288
- display: flex;
289
- gap: 8px;
290
- align-items: center;
291
- margin-bottom: 4px;
292
- }
293
-
294
- .message-role {
295
- font-size: 12px;
296
- font-weight: 600;
297
- color: var(--bs-primary, #0d6efd);
298
- }
299
-
300
- .message-time {
301
- font-size: 11px;
302
- color: var(--bs-secondary-color, #999);
303
- }
304
-
305
- .message-text {
306
- font-size: 13px;
307
- line-height: 1.6;
308
- color: var(--bs-body-color, #e0e0e0);
309
- word-wrap: break-word;
310
- white-space: pre-wrap;
311
- }
312
-
313
- .message-item.loading {
314
- opacity: 0.7;
315
- }
316
-
317
- .loading-dots {
318
- display: flex;
319
- gap: 4px;
320
- padding: 8px 0;
321
- }
322
-
323
- .loading-dots span {
324
- width: 6px;
325
- height: 6px;
326
- border-radius: 50%;
327
- background: var(--bs-primary, #0d6efd);
328
- animation: bounce 1.4s infinite ease-in-out;
329
- }
330
-
331
- .loading-dots span:nth-child(1) {
332
- animation-delay: -0.32s;
333
- }
334
-
335
- .loading-dots span:nth-child(2) {
336
- animation-delay: -0.16s;
337
- }
338
-
339
- @keyframes bounce {
340
- 0%, 80%, 100% {
341
- transform: scale(0);
342
- }
343
- 40% {
344
- transform: scale(1);
345
- }
346
- }
347
-
348
- .scroll-btn {
349
- position: absolute;
350
- right: 16px;
351
- width: 32px;
352
- height: 32px;
353
- border-radius: 50%;
354
- border: 1px solid var(--bs-border-color, #333);
355
- background: var(--bs-body-bg, #1e1e1e);
356
- color: var(--bs-primary, #0d6efd);
357
- cursor: pointer;
358
- display: flex;
359
- align-items: center;
360
- justify-content: center;
361
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
362
- transition: all 0.2s;
363
- z-index: 10;
364
- }
365
-
366
- .scroll-btn:hover {
367
- background: var(--bs-hover-bg, rgba(255, 255, 255, 0.05));
368
- transform: translateY(-2px);
369
- }
370
-
371
- .scroll-btn.scroll-top {
372
- bottom: 120px;
373
- }
374
-
375
- .scroll-btn.scroll-bottom {
376
- bottom: 80px;
377
- }
378
-
379
- .ai-sidebar-input {
380
- padding: 12px 16px;
381
- border-top: 1px solid var(--bs-border-color, #333);
382
- background: var(--bs-tertiary-bg, #252525);
383
- }
384
-
385
- .input-container {
386
- position: relative;
387
- display: flex;
388
- gap: 8px;
389
- align-items: flex-end;
390
- }
391
-
392
- .message-input {
393
- flex: 1;
394
- min-height: 38px;
395
- max-height: 120px;
396
- padding: 8px 12px;
397
- border: 1px solid var(--bs-border-color, #333);
398
- border-radius: 8px;
399
- background: var(--bs-body-bg, #1e1e1e);
400
- color: var(--bs-body-color, #e0e0e0);
401
- font-size: 13px;
402
- font-family: inherit;
403
- resize: none;
404
- outline: none;
405
- transition: border-color 0.2s;
406
- }
407
-
408
- .message-input:focus {
409
- border-color: var(--bs-primary, #0d6efd);
410
- }
411
-
412
- .message-input:disabled {
413
- opacity: 0.6;
414
- cursor: not-allowed;
415
- }
416
-
417
- .message-input::placeholder {
418
- color: var(--bs-secondary-color, #999);
419
- }
420
-
421
- .send-btn {
422
- width: 38px;
423
- height: 38px;
424
- border-radius: 8px;
425
- border: 1px solid var(--bs-border-color, #333);
426
- background: var(--bs-primary, #0d6efd);
427
- color: white;
428
- cursor: pointer;
429
- display: flex;
430
- align-items: center;
431
- justify-content: center;
432
- transition: all 0.2s;
433
- flex-shrink: 0;
434
- }
435
-
436
- .send-btn:hover:not(:disabled) {
437
- background: var(--bs-primary-hover, #0b5ed7);
438
- transform: translateY(-1px);
439
- }
440
-
441
- .send-btn:disabled {
442
- opacity: 0.4;
443
- cursor: not-allowed;
444
- background: var(--bs-secondary-bg, #2a2a2a);
445
- }
446
-
447
- .input-footer {
448
- margin-top: 6px;
449
- display: flex;
450
- justify-content: flex-end;
451
- }
452
-
453
- .char-count {
454
- color: var(--bs-secondary-color, #999);
455
- font-size: 11px;
456
- }
457
-
458
- .char-count.warning {
459
- color: var(--bs-warning, #ffc107);
460
- }
461
-
462
- .char-count.danger {
463
- color: var(--bs-danger, #dc3545);
464
- }
465
-
466
- /* Responsive adjustments */
467
- @media (max-width: 768px) {
468
- .ai-sidebar-header {
469
- padding: 10px 12px;
470
- }
471
-
472
- .ai-sidebar-messages {
473
- padding: 12px;
474
- }
475
-
476
- .ai-sidebar-input {
477
- padding: 10px 12px;
478
- }
479
- }
480
- `]
142
+ styleUrls: ['./ai-sidebar.component.scss'],
143
+ encapsulation: ViewEncapsulation.None
481
144
  })
482
- export class AiSidebarComponent implements OnInit, OnDestroy, AfterViewChecked {
145
+ export class AiSidebarComponent implements OnInit, OnDestroy, AfterViewChecked, AfterViewInit {
146
+ // HostBinding 确保样式正确应用
147
+ @HostBinding('style.display') displayStyle = 'flex';
148
+ @HostBinding('style.flex-direction') flexDirection = 'column';
149
+ @HostBinding('style.height') heightStyle = '100%';
150
+ @HostBinding('style.width') widthStyle = '100%';
151
+ @HostBinding('style.overflow') overflowStyle = 'hidden';
152
+
483
153
  @ViewChild('chatContainer') chatContainerRef!: ElementRef;
484
154
  @ViewChild('textInput') textInput!: ElementRef<HTMLTextAreaElement>;
485
155
 
@@ -504,10 +174,18 @@ export class AiSidebarComponent implements OnInit, OnDestroy, AfterViewChecked {
504
174
  private aiService: AiAssistantService,
505
175
  private config: ConfigProviderService,
506
176
  private logger: LoggerService,
507
- private chatHistory: ChatHistoryService
177
+ private chatHistory: ChatHistoryService,
178
+ private themeService: ThemeService
508
179
  ) { }
509
180
 
510
181
  ngOnInit(): void {
182
+ // 监听主题变化
183
+ this.themeService.theme$.pipe(
184
+ takeUntil(this.destroy$)
185
+ ).subscribe(theme => {
186
+ this.logger.debug('Sidebar theme changed', { theme });
187
+ });
188
+
511
189
  // 生成或加载会话 ID
512
190
  this.currentSessionId = this.generateSessionId();
513
191
 
@@ -533,6 +211,31 @@ export class AiSidebarComponent implements OnInit, OnDestroy, AfterViewChecked {
533
211
  this.destroy$.complete();
534
212
  }
535
213
 
214
+ ngAfterViewInit(): void {
215
+ // 强制设置滚动样式 - 绕过 CSS 优先级问题
216
+ this.forceScrollStyles();
217
+ }
218
+
219
+ /**
220
+ * 强制设置滚动容器样式
221
+ * 使用 JavaScript 直接设置,优先级最高
222
+ */
223
+ private forceScrollStyles(): void {
224
+ setTimeout(() => {
225
+ const container = this.chatContainerRef?.nativeElement;
226
+ if (container) {
227
+ // 直接设置内联样式 - 优先级最高
228
+ container.style.flex = '1 1 auto';
229
+ container.style.height = '0';
230
+ container.style.minHeight = '0';
231
+ container.style.overflowY = 'auto';
232
+ container.style.overflowX = 'hidden';
233
+ container.style.display = 'block';
234
+ this.logger.debug('[AI Sidebar] Scroll styles applied via JS');
235
+ }
236
+ }, 100); // 延迟确保 DOM 已渲染
237
+ }
238
+
536
239
  ngAfterViewChecked(): void {
537
240
  if (this.shouldScrollToBottom) {
538
241
  this.performScrollToBottom();
@@ -17,7 +17,7 @@
17
17
  border: 2px solid var(--ai-border);
18
18
  border-radius: 0.75rem;
19
19
  background-color: var(--ai-bg-primary);
20
- color: var(--ai-dark);
20
+ color: var(--ai-text-primary);
21
21
  font-family: inherit;
22
22
  font-size: 14px;
23
23
  line-height: 1.5;
@@ -85,7 +85,7 @@
85
85
 
86
86
  &:hover {
87
87
  background-color: var(--ai-border);
88
- color: var(--ai-dark);
88
+ color: var(--ai-text-primary);
89
89
  }
90
90
 
91
91
  .icon-clear {
@@ -1,11 +1,13 @@
1
- import { Component, Output, EventEmitter, Input, ViewChild, ElementRef, OnInit, OnDestroy } from '@angular/core';
1
+ import { Component, Output, EventEmitter, Input, ViewChild, ElementRef, OnInit, OnDestroy, ViewEncapsulation } from '@angular/core';
2
2
  import { Subject } from 'rxjs';
3
3
  import { debounceTime, takeUntil } from 'rxjs/operators';
4
+ import { ConfigProviderService } from '../../services/core/config-provider.service';
4
5
 
5
6
  @Component({
6
7
  selector: 'app-chat-input',
7
8
  templateUrl: './chat-input.component.html',
8
- styleUrls: ['./chat-input.component.scss']
9
+ styleUrls: ['./chat-input.component.scss'],
10
+ encapsulation: ViewEncapsulation.None
9
11
  })
10
12
  export class ChatInputComponent implements OnInit, OnDestroy {
11
13
  @Input() disabled = false;
@@ -18,8 +20,14 @@ export class ChatInputComponent implements OnInit, OnDestroy {
18
20
  private inputSubject = new Subject<string>();
19
21
  private destroy$ = new Subject<void>();
20
22
  isComposing = false; // 用于处理中文输入法
23
+ enterToSend: boolean = true; // Enter键发送
24
+
25
+ constructor(private config: ConfigProviderService) {}
21
26
 
22
27
  ngOnInit(): void {
28
+ // 读取 Enter 发送设置
29
+ this.enterToSend = this.config.get<boolean>('ui.enterToSend', true) ?? true;
30
+
23
31
  // 监听输入变化,实现防抖
24
32
  this.inputSubject.pipe(
25
33
  debounceTime(300),
@@ -47,10 +55,13 @@ export class ChatInputComponent implements OnInit, OnDestroy {
47
55
  * 处理键盘事件
48
56
  */
49
57
  onKeydown(event: KeyboardEvent): void {
50
- // Enter 发送(不包含Shift)
58
+ // Enter 发送(根据配置决定)
51
59
  if (event.key === 'Enter' && !event.shiftKey && !this.isComposing) {
52
- event.preventDefault();
53
- this.submit();
60
+ if (this.enterToSend) {
61
+ event.preventDefault();
62
+ this.submit();
63
+ }
64
+ // 如果 enterToSend 为 false,Enter 会插入换行符
54
65
  }
55
66
  }
56
67
 
@@ -4,7 +4,7 @@
4
4
  <div class="header-left">
5
5
  <h2 class="ai-title">
6
6
  <i class="fa fa-comments"></i>
7
- AI助手
7
+ {{ t.chatInterface.title }}
8
8
  </h2>
9
9
  <span class="provider-badge">
10
10
  <i class="fa fa-cloud"></i>
@@ -12,13 +12,13 @@
12
12
  </span>
13
13
  </div>
14
14
  <div class="header-actions">
15
- <button class="btn-icon" (click)="switchProvider()" title="切换AI提供商">
15
+ <button class="btn-icon" (click)="switchProvider()" [title]="t.chatInterface.switchProvider">
16
16
  <i class="fa fa-exchange"></i>
17
17
  </button>
18
- <button class="btn-icon" (click)="exportChat()" title="导出聊天记录">
18
+ <button class="btn-icon" (click)="exportChat()" [title]="t.chatInterface.exportChat">
19
19
  <i class="fa fa-download"></i>
20
20
  </button>
21
- <button class="btn-icon danger" (click)="clearChat()" title="清空聊天记录">
21
+ <button class="btn-icon danger" (click)="clearChat()" [title]="t.chatInterface.clearChat">
22
22
  <i class="fa fa-trash"></i>
23
23
  </button>
24
24
  </div>
@@ -35,7 +35,7 @@
35
35
  <div *ngIf="i === 0 || !isSameDay(message.timestamp, messages[i-1].timestamp)"
36
36
  class="date-separator">
37
37
  <span class="date-text">
38
- {{ isToday(message.timestamp) ? '今天' : message.timestamp.toLocaleDateString('zh-CN') }}
38
+ {{ isToday(message.timestamp) ? t.common.today : message.timestamp.toLocaleDateString('zh-CN') }}
39
39
  </span>
40
40
  </div>
41
41
 
@@ -46,7 +46,7 @@
46
46
  'system-message': message.role === 'system'
47
47
  }">
48
48
  <!-- 头像 -->
49
- <div class="message-avatar">
49
+ <div class="message-avatar" *ngIf="showAvatars">
50
50
  <div *ngIf="message.role === 'user'" class="avatar user">
51
51
  <i class="fa fa-user"></i>
52
52
  </div>
@@ -79,7 +79,7 @@
79
79
  </div>
80
80
 
81
81
  <!-- 消息时间 -->
82
- <div class="message-time">
82
+ <div class="message-time" *ngIf="showTimestamps">
83
83
  {{ formatTimestamp(message.timestamp) }}
84
84
  </div>
85
85
  </div>
@@ -93,7 +93,7 @@
93
93
  <span></span>
94
94
  <span></span>
95
95
  </div>
96
- <span class="loading-text">AI正在思考...</span>
96
+ <span class="loading-text">{{ t.chatInterface.thinking }}</span>
97
97
  </div>
98
98
  </div>
99
99
  </div>
@@ -117,11 +117,11 @@
117
117
  <div class="chat-tips" *ngIf="messages.length === 1">
118
118
  <div class="tip-item">
119
119
  <i class="fa fa-lightbulb-o"></i>
120
- <span>提示:您可以描述想执行的命令,例如"查看当前目录的所有文件"</span>
120
+ <span>{{ t.chatInterface.tipCommand }}</span>
121
121
  </div>
122
122
  <div class="tip-item">
123
123
  <i class="fa fa-keyboard-o"></i>
124
- <span>快捷键:Ctrl+Shift+G 生成命令,Ctrl+Shift+E 解释命令</span>
124
+ <span>{{ t.chatInterface.tipShortcut }}</span>
125
125
  </div>
126
126
  </div>
127
- </div>
127
+ </div>