tabby-ai-assistant 1.0.5 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/dist/components/chat/ai-sidebar.component.d.ts +147 -0
  2. package/dist/components/chat/chat-interface.component.d.ts +38 -6
  3. package/dist/components/settings/general-settings.component.d.ts +6 -3
  4. package/dist/components/settings/provider-config.component.d.ts +25 -12
  5. package/dist/components/terminal/command-preview.component.d.ts +38 -0
  6. package/dist/index-full.d.ts +8 -0
  7. package/dist/index-minimal.d.ts +3 -0
  8. package/dist/index.d.ts +7 -3
  9. package/dist/index.js +1 -2
  10. package/dist/providers/tabby/ai-config.provider.d.ts +57 -5
  11. package/dist/providers/tabby/ai-hotkey.provider.d.ts +8 -14
  12. package/dist/providers/tabby/ai-toolbar-button.provider.d.ts +8 -9
  13. package/dist/services/chat/ai-sidebar.service.d.ts +89 -0
  14. package/dist/services/chat/chat-history.service.d.ts +78 -0
  15. package/dist/services/chat/chat-session.service.d.ts +57 -2
  16. package/dist/services/context/compaction.d.ts +90 -0
  17. package/dist/services/context/manager.d.ts +69 -0
  18. package/dist/services/context/memory.d.ts +116 -0
  19. package/dist/services/context/token-budget.d.ts +105 -0
  20. package/dist/services/core/ai-assistant.service.d.ts +40 -1
  21. package/dist/services/core/checkpoint.service.d.ts +130 -0
  22. package/dist/services/platform/escape-sequence.service.d.ts +132 -0
  23. package/dist/services/platform/platform-detection.service.d.ts +146 -0
  24. package/dist/services/providers/anthropic-provider.service.d.ts +5 -0
  25. package/dist/services/providers/base-provider.service.d.ts +6 -1
  26. package/dist/services/providers/glm-provider.service.d.ts +5 -0
  27. package/dist/services/providers/minimax-provider.service.d.ts +10 -1
  28. package/dist/services/providers/ollama-provider.service.d.ts +76 -0
  29. package/dist/services/providers/openai-compatible.service.d.ts +5 -0
  30. package/dist/services/providers/openai-provider.service.d.ts +5 -0
  31. package/dist/services/providers/vllm-provider.service.d.ts +82 -0
  32. package/dist/services/terminal/buffer-analyzer.service.d.ts +128 -0
  33. package/dist/services/terminal/terminal-manager.service.d.ts +185 -0
  34. package/dist/services/terminal/terminal-tools.service.d.ts +79 -0
  35. package/dist/types/ai.types.d.ts +92 -0
  36. package/dist/types/provider.types.d.ts +1 -1
  37. package/package.json +7 -10
  38. package/src/components/chat/ai-sidebar.component.ts +945 -0
  39. package/src/components/chat/chat-input.component.html +9 -24
  40. package/src/components/chat/chat-input.component.scss +3 -2
  41. package/src/components/chat/chat-interface.component.html +77 -69
  42. package/src/components/chat/chat-interface.component.scss +54 -4
  43. package/src/components/chat/chat-interface.component.ts +250 -34
  44. package/src/components/chat/chat-settings.component.scss +4 -4
  45. package/src/components/chat/chat-settings.component.ts +22 -11
  46. package/src/components/common/error-message.component.html +15 -0
  47. package/src/components/common/error-message.component.scss +77 -0
  48. package/src/components/common/error-message.component.ts +2 -96
  49. package/src/components/common/loading-spinner.component.html +4 -0
  50. package/src/components/common/loading-spinner.component.scss +57 -0
  51. package/src/components/common/loading-spinner.component.ts +2 -63
  52. package/src/components/security/consent-dialog.component.html +22 -0
  53. package/src/components/security/consent-dialog.component.scss +34 -0
  54. package/src/components/security/consent-dialog.component.ts +2 -55
  55. package/src/components/security/password-prompt.component.html +19 -0
  56. package/src/components/security/password-prompt.component.scss +30 -0
  57. package/src/components/security/password-prompt.component.ts +2 -54
  58. package/src/components/security/risk-confirm-dialog.component.html +8 -12
  59. package/src/components/security/risk-confirm-dialog.component.scss +8 -5
  60. package/src/components/security/risk-confirm-dialog.component.ts +6 -6
  61. package/src/components/settings/ai-settings-tab.component.html +16 -20
  62. package/src/components/settings/ai-settings-tab.component.scss +8 -5
  63. package/src/components/settings/ai-settings-tab.component.ts +12 -12
  64. package/src/components/settings/general-settings.component.html +8 -17
  65. package/src/components/settings/general-settings.component.scss +6 -3
  66. package/src/components/settings/general-settings.component.ts +62 -22
  67. package/src/components/settings/provider-config.component.html +19 -39
  68. package/src/components/settings/provider-config.component.scss +182 -39
  69. package/src/components/settings/provider-config.component.ts +119 -7
  70. package/src/components/settings/security-settings.component.scss +1 -1
  71. package/src/components/terminal/ai-toolbar-button.component.html +8 -0
  72. package/src/components/terminal/ai-toolbar-button.component.scss +20 -0
  73. package/src/components/terminal/ai-toolbar-button.component.ts +2 -30
  74. package/src/components/terminal/command-preview.component.html +61 -0
  75. package/src/components/terminal/command-preview.component.scss +72 -0
  76. package/src/components/terminal/command-preview.component.ts +127 -140
  77. package/src/components/terminal/command-suggestion.component.html +23 -0
  78. package/src/components/terminal/command-suggestion.component.scss +55 -0
  79. package/src/components/terminal/command-suggestion.component.ts +2 -77
  80. package/src/index-minimal.ts +32 -0
  81. package/src/index.ts +94 -11
  82. package/src/index.ts.backup +165 -0
  83. package/src/providers/tabby/ai-config.provider.ts +60 -51
  84. package/src/providers/tabby/ai-hotkey.provider.ts +23 -39
  85. package/src/providers/tabby/ai-settings-tab.provider.ts +2 -2
  86. package/src/providers/tabby/ai-toolbar-button.provider.ts +29 -24
  87. package/src/services/chat/ai-sidebar.service.ts +258 -0
  88. package/src/services/chat/chat-history.service.ts +308 -0
  89. package/src/services/chat/chat-history.service.ts.backup +239 -0
  90. package/src/services/chat/chat-session.service.ts +276 -3
  91. package/src/services/context/compaction.ts +483 -0
  92. package/src/services/context/manager.ts +442 -0
  93. package/src/services/context/memory.ts +519 -0
  94. package/src/services/context/token-budget.ts +422 -0
  95. package/src/services/core/ai-assistant.service.ts +280 -5
  96. package/src/services/core/ai-provider-manager.service.ts +2 -2
  97. package/src/services/core/checkpoint.service.ts +619 -0
  98. package/src/services/platform/escape-sequence.service.ts +499 -0
  99. package/src/services/platform/platform-detection.service.ts +494 -0
  100. package/src/services/providers/anthropic-provider.service.ts +28 -1
  101. package/src/services/providers/base-provider.service.ts +7 -1
  102. package/src/services/providers/glm-provider.service.ts +28 -1
  103. package/src/services/providers/minimax-provider.service.ts +209 -11
  104. package/src/services/providers/ollama-provider.service.ts +445 -0
  105. package/src/services/providers/openai-compatible.service.ts +9 -0
  106. package/src/services/providers/openai-provider.service.ts +9 -0
  107. package/src/services/providers/vllm-provider.service.ts +463 -0
  108. package/src/services/security/risk-assessment.service.ts +6 -2
  109. package/src/services/terminal/buffer-analyzer.service.ts +594 -0
  110. package/src/services/terminal/terminal-manager.service.ts +748 -0
  111. package/src/services/terminal/terminal-tools.service.ts +441 -0
  112. package/src/styles/ai-assistant.scss +78 -6
  113. package/src/types/ai.types.ts +144 -0
  114. package/src/types/provider.types.ts +1 -1
  115. package/tsconfig.json +9 -9
  116. package/webpack.config.js +28 -6
@@ -1,10 +1,11 @@
1
- import { Component, OnInit, OnDestroy } from '@angular/core';
1
+ import { Component, OnInit, OnDestroy, ViewChild, ElementRef, AfterViewChecked } from '@angular/core';
2
2
  import { Subject } from 'rxjs';
3
3
  import { takeUntil } from 'rxjs/operators';
4
- import { ChatMessage, MessageRole } from '../../types/ai.types';
4
+ import { ChatMessage, MessageRole, StreamEvent } from '../../types/ai.types';
5
5
  import { AiAssistantService } from '../../services/core/ai-assistant.service';
6
6
  import { ConfigProviderService } from '../../services/core/config-provider.service';
7
7
  import { LoggerService } from '../../services/core/logger.service';
8
+ import { ChatHistoryService } from '../../services/chat/chat-history.service';
8
9
  import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
9
10
 
10
11
  @Component({
@@ -12,50 +13,105 @@ import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
12
13
  templateUrl: './chat-interface.component.html',
13
14
  styleUrls: ['./chat-interface.component.scss']
14
15
  })
15
- export class ChatInterfaceComponent implements OnInit, OnDestroy {
16
+ export class ChatInterfaceComponent implements OnInit, OnDestroy, AfterViewChecked {
17
+ @ViewChild('chatContainer') chatContainerRef!: ElementRef;
18
+
16
19
  messages: ChatMessage[] = [];
17
20
  isLoading = false;
18
21
  currentProvider: string = '';
22
+ currentSessionId: string = '';
23
+ showScrollTop = false;
24
+ showScrollBottom = false;
19
25
  private destroy$ = new Subject<void>();
26
+ private shouldScrollToBottom = false;
20
27
 
21
28
  constructor(
22
29
  private aiService: AiAssistantService,
23
30
  private config: ConfigProviderService,
24
31
  private logger: LoggerService,
25
- private modal: NgbModal
26
- ) {}
32
+ private modal: NgbModal,
33
+ private chatHistory: ChatHistoryService
34
+ ) { }
27
35
 
28
36
  ngOnInit(): void {
37
+ // 生成或加载会话 ID
38
+ this.currentSessionId = this.generateSessionId();
39
+
29
40
  // 加载当前提供商信息
30
41
  this.loadCurrentProvider();
31
42
 
32
43
  // 加载聊天历史
33
44
  this.loadChatHistory();
34
45
 
35
- // 发送欢迎消息
36
- this.sendWelcomeMessage();
46
+ // 发送欢迎消息(仅在没有历史记录时)
47
+ if (this.messages.length === 0) {
48
+ this.sendWelcomeMessage();
49
+ }
50
+
51
+ // 延迟检查滚动状态(等待 DOM 渲染)
52
+ setTimeout(() => this.checkScrollState(), 100);
37
53
  }
38
54
 
39
55
  ngOnDestroy(): void {
56
+ // 保存当前会话
57
+ this.saveChatHistory();
40
58
  this.destroy$.next();
41
59
  this.destroy$.complete();
42
60
  }
43
61
 
62
+ ngAfterViewChecked(): void {
63
+ if (this.shouldScrollToBottom) {
64
+ this.performScrollToBottom();
65
+ this.shouldScrollToBottom = false;
66
+ }
67
+ }
68
+
44
69
  /**
45
70
  * 加载当前提供商信息
46
71
  */
47
72
  private loadCurrentProvider(): void {
48
- const status = this.aiService.getProviderStatus();
49
- this.currentProvider = status.active?.displayName || 'Unknown';
73
+ const defaultProvider = this.config.getDefaultProvider();
74
+ if (defaultProvider) {
75
+ const providerConfig = this.config.getProviderConfig(defaultProvider);
76
+ this.currentProvider = providerConfig?.displayName || defaultProvider;
77
+ } else {
78
+ // 尝试获取第一个已配置的提供商
79
+ const allConfigs = this.config.getAllProviderConfigs();
80
+ const configuredProviders = Object.keys(allConfigs).filter(k => allConfigs[k]?.apiKey);
81
+ if (configuredProviders.length > 0) {
82
+ const firstProvider = configuredProviders[0];
83
+ const providerConfig = allConfigs[firstProvider];
84
+ this.currentProvider = providerConfig?.displayName || firstProvider;
85
+ this.config.setDefaultProvider(firstProvider);
86
+ } else {
87
+ this.currentProvider = '未配置';
88
+ }
89
+ }
50
90
  }
51
91
 
52
92
  /**
53
93
  * 加载聊天历史
54
94
  */
55
95
  private loadChatHistory(): void {
56
- // TODO: 从本地存储或数据库加载历史消息
57
- // 暂时使用空数组
58
- this.messages = [];
96
+ try {
97
+ // 尝试加载最近的会话
98
+ const recentSessions = this.chatHistory.getRecentSessions(1);
99
+ if (recentSessions.length > 0) {
100
+ const lastSession = recentSessions[0];
101
+ this.currentSessionId = lastSession.sessionId;
102
+ this.messages = lastSession.messages.map(msg => ({
103
+ ...msg,
104
+ timestamp: new Date(msg.timestamp)
105
+ }));
106
+ this.logger.info('Loaded chat history', {
107
+ sessionId: this.currentSessionId,
108
+ messageCount: this.messages.length
109
+ });
110
+ }
111
+ } catch (error) {
112
+ this.logger.error('Failed to load chat history', error);
113
+ this.messages = [];
114
+ }
59
115
  }
60
116
 
61
117
  /**
@@ -97,20 +153,59 @@ export class ChatInterfaceComponent implements OnInit, OnDestroy {
97
153
  // 显示加载状态
98
154
  this.isLoading = true;
99
155
 
156
+ // 创建一个临时的 AI 消息用于流式更新
157
+ const aiMessage: ChatMessage = {
158
+ id: this.generateId(),
159
+ role: MessageRole.ASSISTANT,
160
+ content: '', // 初始为空
161
+ timestamp: new Date()
162
+ };
163
+ this.messages.push(aiMessage);
164
+
100
165
  try {
101
- // 发送请求到AI
102
- const response = await this.aiService.chat({
103
- messages: this.messages,
166
+ // 使用流式 API
167
+ this.aiService.chatStream({
168
+ messages: this.messages.slice(0, -1), // 排除刚添加的空 AI 消息
104
169
  maxTokens: 1000,
105
170
  temperature: 0.7
171
+ }).pipe(
172
+ takeUntil(this.destroy$)
173
+ ).subscribe({
174
+ next: (event) => {
175
+ // 文本增量 - 逐字显示
176
+ if (event.type === 'text_delta' && event.textDelta) {
177
+ aiMessage.content += event.textDelta;
178
+ this.shouldScrollToBottom = true;
179
+ }
180
+ // 工具调用开始 - 显示提示
181
+ else if (event.type === 'tool_use_start') {
182
+ aiMessage.content += '\n\n🔧 正在执行工具...';
183
+ this.shouldScrollToBottom = true;
184
+ }
185
+ // 工具调用完成 - 更新状态
186
+ else if (event.type === 'tool_use_end') {
187
+ aiMessage.content = aiMessage.content.replace('🔧 正在执行工具...', '✅ 工具执行完成');
188
+ this.shouldScrollToBottom = true;
189
+ }
190
+ // 消息结束
191
+ else if (event.type === 'message_end') {
192
+ this.logger.info('Stream completed');
193
+ }
194
+ },
195
+ error: (error) => {
196
+ this.logger.error('Stream error', error);
197
+ aiMessage.content += `\n\n❌ 错误: ${error instanceof Error ? error.message : 'Unknown error'}`;
198
+ this.isLoading = false;
199
+ this.shouldScrollToBottom = true;
200
+ this.saveChatHistory();
201
+ },
202
+ complete: () => {
203
+ this.isLoading = false;
204
+ this.saveChatHistory();
205
+ this.shouldScrollToBottom = true;
206
+ }
106
207
  });
107
208
 
108
- // 添加AI响应
109
- this.messages.push(response.message);
110
-
111
- // 保存聊天历史
112
- this.saveChatHistory();
113
-
114
209
  } catch (error) {
115
210
  this.logger.error('Failed to send message', error);
116
211
 
@@ -122,9 +217,7 @@ export class ChatInterfaceComponent implements OnInit, OnDestroy {
122
217
  timestamp: new Date()
123
218
  };
124
219
  this.messages.push(errorMessage);
125
- } finally {
126
220
  this.isLoading = false;
127
- // 滚动到底部
128
221
  setTimeout(() => this.scrollToBottom(), 0);
129
222
  }
130
223
  }
@@ -134,9 +227,29 @@ export class ChatInterfaceComponent implements OnInit, OnDestroy {
134
227
  */
135
228
  clearChat(): void {
136
229
  if (confirm('确定要清空聊天记录吗?')) {
230
+ // 删除当前会话
231
+ if (this.currentSessionId) {
232
+ this.chatHistory.deleteSession(this.currentSessionId);
233
+ }
234
+ // 创建新会话
235
+ this.currentSessionId = this.generateSessionId();
137
236
  this.messages = [];
138
237
  this.sendWelcomeMessage();
139
- localStorage.removeItem('ai-assistant-chat-history');
238
+
239
+ // 确保重置加载状态
240
+ this.isLoading = false;
241
+
242
+ // 延迟滚动和恢复焦点,确保DOM已更新
243
+ setTimeout(() => {
244
+ this.scrollToBottom();
245
+ // 尝试恢复输入框焦点
246
+ const inputElement = document.querySelector('.chat-textarea') as HTMLTextAreaElement;
247
+ if (inputElement) {
248
+ inputElement.focus();
249
+ }
250
+ }, 100);
251
+
252
+ this.logger.info('Chat cleared, new session created', { sessionId: this.currentSessionId });
140
253
  }
141
254
  }
142
255
 
@@ -162,34 +275,136 @@ export class ChatInterfaceComponent implements OnInit, OnDestroy {
162
275
  /**
163
276
  * 切换提供商
164
277
  */
165
- switchProvider(): void {
166
- // TODO: 打开提供商选择对话框
167
- this.logger.info('Switch provider requested');
278
+ async switchProvider(): Promise<void> {
279
+ // 从配置服务获取已配置的提供商
280
+ const allConfigs = this.config.getAllProviderConfigs();
281
+ const configuredProviders = Object.keys(allConfigs)
282
+ .filter(key => allConfigs[key] && allConfigs[key].enabled !== false)
283
+ .map(key => ({
284
+ name: key,
285
+ displayName: allConfigs[key].displayName || key
286
+ }));
287
+
288
+ if (configuredProviders.length === 0) {
289
+ alert('没有可用的AI提供商,请先在设置中配置。');
290
+ return;
291
+ }
292
+
293
+ // 构建提供商列表
294
+ const providerList = configuredProviders.map((p, i) =>
295
+ `${i + 1}. ${p.displayName}`
296
+ ).join('\n');
297
+
298
+ const choice = prompt(
299
+ `当前使用: ${this.currentProvider}\n\n可用的AI提供商:\n${providerList}\n\n请输入序号选择提供商:`,
300
+ '1'
301
+ );
302
+
303
+ if (choice) {
304
+ const index = parseInt(choice, 10) - 1;
305
+ if (index >= 0 && index < configuredProviders.length) {
306
+ const selectedProvider = configuredProviders[index];
307
+ this.config.setDefaultProvider(selectedProvider.name);
308
+ this.currentProvider = selectedProvider.displayName;
309
+ this.logger.info('Provider switched', { provider: selectedProvider.name });
310
+
311
+ // 添加系统消息
312
+ const systemMessage: ChatMessage = {
313
+ id: this.generateId(),
314
+ role: MessageRole.SYSTEM,
315
+ content: `已切换到 ${this.currentProvider}`,
316
+ timestamp: new Date()
317
+ };
318
+ this.messages.push(systemMessage);
319
+ } else {
320
+ alert('无效的选择');
321
+ }
322
+ }
323
+ }
324
+
325
+ /**
326
+ * 滚动到底部(公开方法)
327
+ */
328
+ scrollToBottom(): void {
329
+ this.shouldScrollToBottom = true;
330
+ }
331
+
332
+ /**
333
+ * 滚动到顶部
334
+ */
335
+ scrollToTop(): void {
336
+ const chatContainer = this.chatContainerRef?.nativeElement || document.querySelector('.ai-chat-container');
337
+ if (chatContainer) {
338
+ chatContainer.scrollTo({ top: 0, behavior: 'smooth' });
339
+ }
168
340
  }
169
341
 
170
342
  /**
171
- * 滚动到底部
343
+ * 实际执行滚动到底部
172
344
  */
173
- private scrollToBottom(): void {
174
- const chatContainer = document.querySelector('.ai-chat-container');
345
+ private performScrollToBottom(): void {
346
+ const chatContainer = this.chatContainerRef?.nativeElement || document.querySelector('.ai-chat-container');
175
347
  if (chatContainer) {
176
- chatContainer.scrollTop = chatContainer.scrollHeight;
348
+ chatContainer.scrollTo({ top: chatContainer.scrollHeight, behavior: 'smooth' });
177
349
  }
178
350
  }
179
351
 
352
+ /**
353
+ * 处理滚动事件
354
+ */
355
+ onScroll(event: Event): void {
356
+ const target = event.target as HTMLElement;
357
+ if (!target) return;
358
+ this.updateScrollButtons(target);
359
+ }
360
+
361
+ /**
362
+ * 检查滚动状态(初始化时调用)
363
+ */
364
+ private checkScrollState(): void {
365
+ const chatContainer = this.chatContainerRef?.nativeElement || document.querySelector('.ai-chat-container');
366
+ if (chatContainer) {
367
+ this.updateScrollButtons(chatContainer);
368
+ }
369
+ }
370
+
371
+ /**
372
+ * 更新滚动按钮显示状态
373
+ */
374
+ private updateScrollButtons(container: HTMLElement): void {
375
+ const scrollTop = container.scrollTop;
376
+ const scrollHeight = container.scrollHeight;
377
+ const clientHeight = container.clientHeight;
378
+
379
+ // 判断是否显示滚动按钮
380
+ this.showScrollTop = scrollTop > 50;
381
+ this.showScrollBottom = scrollHeight > clientHeight && scrollTop < scrollHeight - clientHeight - 50;
382
+ }
383
+
180
384
  /**
181
385
  * 保存聊天历史
182
386
  */
183
387
  private saveChatHistory(): void {
184
388
  try {
185
- // 限制历史记录数量
186
- const recentMessages = this.messages.slice(-100);
187
- localStorage.setItem('ai-assistant-chat-history', JSON.stringify(recentMessages));
389
+ if (this.messages.length > 0 && this.currentSessionId) {
390
+ this.chatHistory.saveSession(this.currentSessionId, this.messages);
391
+ this.logger.info('Chat history saved', {
392
+ sessionId: this.currentSessionId,
393
+ messageCount: this.messages.length
394
+ });
395
+ }
188
396
  } catch (error) {
189
397
  this.logger.error('Failed to save chat history', error);
190
398
  }
191
399
  }
192
400
 
401
+ /**
402
+ * 生成会话 ID
403
+ */
404
+ private generateSessionId(): string {
405
+ return `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
406
+ }
407
+
193
408
  /**
194
409
  * 生成唯一ID
195
410
  */
@@ -222,3 +437,4 @@ export class ChatInterfaceComponent implements OnInit, OnDestroy {
222
437
  return date1.toDateString() === date2.toDateString();
223
438
  }
224
439
  }
440
+
@@ -130,7 +130,7 @@
130
130
  color: white;
131
131
 
132
132
  &:hover {
133
- background-color: darken(#6c757d, 7.5%);
133
+ background-color: #5a6268;
134
134
  }
135
135
  }
136
136
 
@@ -139,7 +139,7 @@
139
139
  color: white;
140
140
 
141
141
  &:hover {
142
- background-color: darken(#dc3545, 7.5%);
142
+ background-color: #c82333;
143
143
  }
144
144
  }
145
145
 
@@ -148,7 +148,7 @@
148
148
  color: var(--ai-dark);
149
149
 
150
150
  &:hover {
151
- background-color: darken(#ffc107, 7.5%);
151
+ background-color: #e0a800;
152
152
  }
153
153
  }
154
154
  }
@@ -169,4 +169,4 @@
169
169
  }
170
170
  }
171
171
  }
172
- }
172
+ }
@@ -8,7 +8,18 @@ import { LoggerService } from '../../services/core/logger.service';
8
8
  styleUrls: ['./chat-settings.component.scss']
9
9
  })
10
10
  export class ChatSettingsComponent implements OnInit {
11
- settings = {
11
+ settings: {
12
+ chatHistoryEnabled: boolean;
13
+ maxChatHistory: number;
14
+ autoSaveChat: boolean;
15
+ theme: string;
16
+ fontSize: number;
17
+ compactMode: boolean;
18
+ showTimestamps: boolean;
19
+ showAvatars: boolean;
20
+ enterToSend: boolean;
21
+ soundEnabled: boolean;
22
+ } = {
12
23
  chatHistoryEnabled: true,
13
24
  maxChatHistory: 100,
14
25
  autoSaveChat: true,
@@ -42,16 +53,16 @@ export class ChatSettingsComponent implements OnInit {
42
53
  * 加载设置
43
54
  */
44
55
  private loadSettings(): void {
45
- this.settings.chatHistoryEnabled = this.config.get('chatHistoryEnabled', true);
46
- this.settings.maxChatHistory = this.config.get('maxChatHistory', 100);
47
- this.settings.autoSaveChat = this.config.get('autoSaveChat', true);
48
- this.settings.theme = this.config.get('theme', 'auto');
49
- this.settings.fontSize = this.config.get('ui.fontSize', 14);
50
- this.settings.compactMode = this.config.get('ui.compactMode', false);
51
- this.settings.showTimestamps = this.config.get('ui.showTimestamps', true);
52
- this.settings.showAvatars = this.config.get('ui.showAvatars', true);
53
- this.settings.enterToSend = this.config.get('ui.enterToSend', true);
54
- this.settings.soundEnabled = this.config.get('ui.soundEnabled', true);
56
+ this.settings.chatHistoryEnabled = this.config.get('chatHistoryEnabled', true) ?? true;
57
+ this.settings.maxChatHistory = this.config.get('maxChatHistory', 100) ?? 100;
58
+ this.settings.autoSaveChat = this.config.get('autoSaveChat', true) ?? true;
59
+ this.settings.theme = this.config.get('theme', 'auto') ?? 'auto';
60
+ this.settings.fontSize = this.config.get('ui.fontSize', 14) ?? 14;
61
+ this.settings.compactMode = this.config.get('ui.compactMode', false) ?? false;
62
+ this.settings.showTimestamps = this.config.get('ui.showTimestamps', true) ?? true;
63
+ this.settings.showAvatars = this.config.get('ui.showAvatars', true) ?? true;
64
+ this.settings.enterToSend = this.config.get('ui.enterToSend', true) ?? true;
65
+ this.settings.soundEnabled = this.config.get('ui.soundEnabled', true) ?? true;
55
66
  }
56
67
 
57
68
  /**
@@ -0,0 +1,15 @@
1
+ <div class="error-message" [ngClass]="type">
2
+ <div class="error-icon">
3
+ <i [class]="getIconClass()"></i>
4
+ </div>
5
+ <div class="error-content">
6
+ <h4 *ngIf="title">{{ title }}</h4>
7
+ <p>{{ message }}</p>
8
+ <div *ngIf="details" class="error-details">
9
+ <small>{{ details }}</small>
10
+ </div>
11
+ </div>
12
+ <button *ngIf="dismissible" class="error-close" (click)="onDismiss()">
13
+ <i class="icon-close"></i>
14
+ </button>
15
+ </div>
@@ -0,0 +1,77 @@
1
+ .error-message {
2
+ display: flex;
3
+ align-items: flex-start;
4
+ gap: 0.75rem;
5
+ padding: 1rem;
6
+ border-radius: 0.5rem;
7
+ border: 1px solid;
8
+ margin: 0.5rem 0;
9
+
10
+ &.error {
11
+ background-color: rgba(220, 53, 69, 0.1);
12
+ border-color: var(--ai-danger);
13
+ color: var(--ai-danger);
14
+ }
15
+
16
+ &.warning {
17
+ background-color: rgba(255, 193, 7, 0.1);
18
+ border-color: var(--ai-warning);
19
+ color: #856404;
20
+ }
21
+
22
+ &.info {
23
+ background-color: rgba(23, 162, 184, 0.1);
24
+ border-color: var(--ai-info);
25
+ color: var(--ai-info);
26
+ }
27
+
28
+ &.success {
29
+ background-color: rgba(40, 167, 69, 0.1);
30
+ border-color: var(--ai-success);
31
+ color: var(--ai-success);
32
+ }
33
+ }
34
+
35
+ .error-icon {
36
+ flex-shrink: 0;
37
+ font-size: 1.25rem;
38
+ }
39
+
40
+ .error-content {
41
+ flex: 1;
42
+
43
+ h4 {
44
+ margin: 0 0 0.5rem 0;
45
+ font-size: 1rem;
46
+ font-weight: 600;
47
+ }
48
+
49
+ p {
50
+ margin: 0;
51
+ line-height: 1.5;
52
+ }
53
+
54
+ .error-details {
55
+ margin-top: 0.5rem;
56
+ opacity: 0.8;
57
+ }
58
+ }
59
+
60
+ .error-close {
61
+ flex-shrink: 0;
62
+ width: 24px;
63
+ height: 24px;
64
+ border: none;
65
+ background: transparent;
66
+ border-radius: 0.25rem;
67
+ display: flex;
68
+ align-items: center;
69
+ justify-content: center;
70
+ cursor: pointer;
71
+ opacity: 0.6;
72
+ transition: opacity 0.2s;
73
+
74
+ &:hover {
75
+ opacity: 1;
76
+ }
77
+ }
@@ -2,102 +2,8 @@ import { Component, Input, Output, EventEmitter } from '@angular/core';
2
2
 
3
3
  @Component({
4
4
  selector: 'app-error-message',
5
- template: `
6
- <div class="error-message" [ngClass]="type">
7
- <div class="error-icon">
8
- <i [class]="getIconClass()"></i>
9
- </div>
10
- <div class="error-content">
11
- <h4 *ngIf="title">{{ title }}</h4>
12
- <p>{{ message }}</p>
13
- <div *ngIf="details" class="error-details">
14
- <small>{{ details }}</small>
15
- </div>
16
- </div>
17
- <button *ngIf="dismissible" class="error-close" (click)="onDismiss()">
18
- <i class="icon-close"></i>
19
- </button>
20
- </div>
21
- `,
22
- styles: [`
23
- .error-message {
24
- display: flex;
25
- align-items: flex-start;
26
- gap: 0.75rem;
27
- padding: 1rem;
28
- border-radius: 0.5rem;
29
- border: 1px solid;
30
- margin: 0.5rem 0;
31
-
32
- &.error {
33
- background-color: rgba(220, 53, 69, 0.1);
34
- border-color: var(--ai-danger);
35
- color: var(--ai-danger);
36
- }
37
-
38
- &.warning {
39
- background-color: rgba(255, 193, 7, 0.1);
40
- border-color: var(--ai-warning);
41
- color: #856404;
42
- }
43
-
44
- &.info {
45
- background-color: rgba(23, 162, 184, 0.1);
46
- border-color: var(--ai-info);
47
- color: var(--ai-info);
48
- }
49
-
50
- &.success {
51
- background-color: rgba(40, 167, 69, 0.1);
52
- border-color: var(--ai-success);
53
- color: var(--ai-success);
54
- }
55
- }
56
-
57
- .error-icon {
58
- flex-shrink: 0;
59
- font-size: 1.25rem;
60
- }
61
-
62
- .error-content {
63
- flex: 1;
64
-
65
- h4 {
66
- margin: 0 0 0.5rem 0;
67
- font-size: 1rem;
68
- font-weight: 600;
69
- }
70
-
71
- p {
72
- margin: 0;
73
- line-height: 1.5;
74
- }
75
-
76
- .error-details {
77
- margin-top: 0.5rem;
78
- opacity: 0.8;
79
- }
80
- }
81
-
82
- .error-close {
83
- flex-shrink: 0;
84
- width: 24px;
85
- height: 24px;
86
- border: none;
87
- background: transparent;
88
- border-radius: 0.25rem;
89
- display: flex;
90
- align-items: center;
91
- justify-content: center;
92
- cursor: pointer;
93
- opacity: 0.6;
94
- transition: opacity 0.2s;
95
-
96
- &:hover {
97
- opacity: 1;
98
- }
99
- }
100
- `]
5
+ templateUrl: './error-message.component.html',
6
+ styleUrls: ['./error-message.component.scss']
101
7
  })
102
8
  export class ErrorMessageComponent {
103
9
  @Input() type: 'error' | 'warning' | 'info' | 'success' = 'error';
@@ -0,0 +1,4 @@
1
+ <div class="spinner-container" [ngClass]="size">
2
+ <div class="spinner"></div>
3
+ <p *ngIf="message" class="spinner-message">{{ message }}</p>
4
+ </div>