tabby-ai-assistant 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/README.md +232 -0
  2. package/dist/components/chat/chat-input.component.d.ts +65 -0
  3. package/dist/components/chat/chat-interface.component.d.ts +71 -0
  4. package/dist/components/chat/chat-message.component.d.ts +53 -0
  5. package/dist/components/chat/chat-settings.component.d.ts +62 -0
  6. package/dist/components/common/error-message.component.d.ts +11 -0
  7. package/dist/components/common/loading-spinner.component.d.ts +4 -0
  8. package/dist/components/security/consent-dialog.component.d.ts +11 -0
  9. package/dist/components/security/password-prompt.component.d.ts +10 -0
  10. package/dist/components/security/risk-confirm-dialog.component.d.ts +36 -0
  11. package/dist/components/settings/ai-settings-tab.component.d.ts +72 -0
  12. package/dist/components/settings/general-settings.component.d.ts +60 -0
  13. package/dist/components/settings/provider-config.component.d.ts +182 -0
  14. package/dist/components/settings/security-settings.component.d.ts +23 -0
  15. package/dist/components/terminal/ai-toolbar-button.component.d.ts +10 -0
  16. package/dist/components/terminal/command-preview.component.d.ts +15 -0
  17. package/dist/components/terminal/command-suggestion.component.d.ts +16 -0
  18. package/dist/index.d.ts +8 -0
  19. package/dist/index.js +2 -0
  20. package/dist/index.js.LICENSE.txt +18 -0
  21. package/dist/main.d.ts +8 -0
  22. package/dist/providers/tabby/ai-config.provider.d.ts +18 -0
  23. package/dist/providers/tabby/ai-hotkey.provider.d.ts +21 -0
  24. package/dist/providers/tabby/ai-settings-tab.provider.d.ts +11 -0
  25. package/dist/providers/tabby/ai-toolbar-button.provider.d.ts +17 -0
  26. package/dist/services/chat/chat-history.service.d.ts +67 -0
  27. package/dist/services/chat/chat-session.service.d.ts +58 -0
  28. package/dist/services/chat/command-generator.service.d.ts +49 -0
  29. package/dist/services/core/ai-assistant.service.d.ts +88 -0
  30. package/dist/services/core/ai-provider-manager.service.d.ts +119 -0
  31. package/dist/services/core/config-provider.service.d.ts +137 -0
  32. package/dist/services/core/logger.service.d.ts +21 -0
  33. package/dist/services/providers/anthropic-provider.service.d.ts +39 -0
  34. package/dist/services/providers/base-provider.service.d.ts +137 -0
  35. package/dist/services/providers/glm-provider.service.d.ts +91 -0
  36. package/dist/services/providers/minimax-provider.service.d.ts +93 -0
  37. package/dist/services/providers/openai-compatible.service.d.ts +39 -0
  38. package/dist/services/providers/openai-provider.service.d.ts +38 -0
  39. package/dist/services/security/consent-manager.service.d.ts +65 -0
  40. package/dist/services/security/password-manager.service.d.ts +67 -0
  41. package/dist/services/security/risk-assessment.service.d.ts +65 -0
  42. package/dist/services/security/security-validator.service.d.ts +36 -0
  43. package/dist/services/terminal/command-analyzer.service.d.ts +20 -0
  44. package/dist/services/terminal/context-menu.service.d.ts +24 -0
  45. package/dist/services/terminal/hotkey.service.d.ts +28 -0
  46. package/dist/services/terminal/terminal-context.service.d.ts +100 -0
  47. package/dist/types/ai.types.d.ts +107 -0
  48. package/dist/types/provider.types.d.ts +105 -0
  49. package/dist/types/security.types.d.ts +85 -0
  50. package/dist/types/terminal.types.d.ts +150 -0
  51. package/dist/utils/encryption.utils.d.ts +83 -0
  52. package/dist/utils/formatting.utils.d.ts +106 -0
  53. package/dist/utils/validation.utils.d.ts +83 -0
  54. package/integration-test-output.txt +50 -0
  55. package/integration-tests/api-integration.test.ts +183 -0
  56. package/jest.config.js +47 -0
  57. package/package.json +73 -0
  58. package/setup-jest.ts +37 -0
  59. package/src/components/chat/chat-input.component.html +61 -0
  60. package/src/components/chat/chat-input.component.scss +183 -0
  61. package/src/components/chat/chat-input.component.ts +149 -0
  62. package/src/components/chat/chat-interface.component.html +119 -0
  63. package/src/components/chat/chat-interface.component.scss +354 -0
  64. package/src/components/chat/chat-interface.component.ts +224 -0
  65. package/src/components/chat/chat-message.component.html +65 -0
  66. package/src/components/chat/chat-message.component.scss +178 -0
  67. package/src/components/chat/chat-message.component.ts +93 -0
  68. package/src/components/chat/chat-settings.component.html +132 -0
  69. package/src/components/chat/chat-settings.component.scss +172 -0
  70. package/src/components/chat/chat-settings.component.ts +168 -0
  71. package/src/components/common/error-message.component.ts +124 -0
  72. package/src/components/common/loading-spinner.component.ts +72 -0
  73. package/src/components/security/consent-dialog.component.ts +77 -0
  74. package/src/components/security/password-prompt.component.ts +79 -0
  75. package/src/components/security/risk-confirm-dialog.component.html +87 -0
  76. package/src/components/security/risk-confirm-dialog.component.scss +360 -0
  77. package/src/components/security/risk-confirm-dialog.component.ts +96 -0
  78. package/src/components/settings/ai-settings-tab.component.html +140 -0
  79. package/src/components/settings/ai-settings-tab.component.scss +371 -0
  80. package/src/components/settings/ai-settings-tab.component.ts +193 -0
  81. package/src/components/settings/general-settings.component.html +103 -0
  82. package/src/components/settings/general-settings.component.scss +285 -0
  83. package/src/components/settings/general-settings.component.ts +123 -0
  84. package/src/components/settings/provider-config.component.html +95 -0
  85. package/src/components/settings/provider-config.component.scss +60 -0
  86. package/src/components/settings/provider-config.component.ts +206 -0
  87. package/src/components/settings/security-settings.component.html +51 -0
  88. package/src/components/settings/security-settings.component.scss +66 -0
  89. package/src/components/settings/security-settings.component.ts +71 -0
  90. package/src/components/terminal/ai-toolbar-button.component.ts +49 -0
  91. package/src/components/terminal/command-preview.component.ts +185 -0
  92. package/src/components/terminal/command-suggestion.component.ts +128 -0
  93. package/src/index.ts +163 -0
  94. package/src/main.ts +16 -0
  95. package/src/providers/tabby/ai-config.provider.ts +70 -0
  96. package/src/providers/tabby/ai-hotkey.provider.ts +55 -0
  97. package/src/providers/tabby/ai-settings-tab.provider.ts +18 -0
  98. package/src/providers/tabby/ai-toolbar-button.provider.ts +49 -0
  99. package/src/services/chat/chat-history.service.ts +239 -0
  100. package/src/services/chat/chat-session.service.spec.ts +249 -0
  101. package/src/services/chat/chat-session.service.ts +180 -0
  102. package/src/services/chat/command-generator.service.ts +301 -0
  103. package/src/services/core/ai-assistant.service.ts +334 -0
  104. package/src/services/core/ai-provider-manager.service.ts +314 -0
  105. package/src/services/core/config-provider.service.ts +347 -0
  106. package/src/services/core/logger.service.ts +104 -0
  107. package/src/services/providers/anthropic-provider.service.ts +373 -0
  108. package/src/services/providers/base-provider.service.ts +369 -0
  109. package/src/services/providers/glm-provider.service.ts +467 -0
  110. package/src/services/providers/minimax-provider.service.ts +427 -0
  111. package/src/services/providers/openai-compatible.service.ts +394 -0
  112. package/src/services/providers/openai-provider.service.ts +376 -0
  113. package/src/services/security/consent-manager.service.ts +332 -0
  114. package/src/services/security/password-manager.service.ts +188 -0
  115. package/src/services/security/risk-assessment.service.ts +340 -0
  116. package/src/services/security/security-validator.service.ts +143 -0
  117. package/src/services/terminal/command-analyzer.service.ts +43 -0
  118. package/src/services/terminal/context-menu.service.ts +45 -0
  119. package/src/services/terminal/hotkey.service.ts +53 -0
  120. package/src/services/terminal/terminal-context.service.ts +317 -0
  121. package/src/styles/ai-assistant.scss +449 -0
  122. package/src/types/ai.types.ts +133 -0
  123. package/src/types/provider.types.ts +147 -0
  124. package/src/types/security.types.ts +103 -0
  125. package/src/types/terminal.types.ts +186 -0
  126. package/src/utils/encryption.utils.spec.ts +250 -0
  127. package/src/utils/encryption.utils.ts +271 -0
  128. package/src/utils/formatting.utils.ts +359 -0
  129. package/src/utils/validation.utils.spec.ts +225 -0
  130. package/src/utils/validation.utils.ts +314 -0
  131. package/tsconfig.json +45 -0
  132. package/webpack-docker.config.js +42 -0
  133. package/webpack.config.js +59 -0
  134. package/webpack.config.js.backup +57 -0
@@ -0,0 +1,354 @@
1
+ .ai-chat-interface {
2
+ display: flex;
3
+ flex-direction: column;
4
+ height: 100%;
5
+ background-color: var(--ai-bg-primary);
6
+ color: var(--ai-dark);
7
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
8
+ }
9
+
10
+ /* 头部 */
11
+ .ai-chat-header {
12
+ padding: 1rem 1.5rem;
13
+ background-color: var(--ai-bg-secondary);
14
+ border-bottom: 1px solid var(--ai-border);
15
+ display: flex;
16
+ align-items: center;
17
+ justify-content: space-between;
18
+ flex-shrink: 0;
19
+
20
+ .header-left {
21
+ display: flex;
22
+ align-items: center;
23
+ gap: 1rem;
24
+
25
+ .ai-title {
26
+ margin: 0;
27
+ font-size: 1.25rem;
28
+ font-weight: 600;
29
+ display: flex;
30
+ align-items: center;
31
+ gap: 0.5rem;
32
+
33
+ .icon-chat {
34
+ font-size: 1.5rem;
35
+ }
36
+ }
37
+
38
+ .provider-badge {
39
+ padding: 0.25rem 0.75rem;
40
+ background-color: var(--ai-primary);
41
+ color: white;
42
+ border-radius: 1rem;
43
+ font-size: 0.875rem;
44
+ font-weight: 500;
45
+ display: flex;
46
+ align-items: center;
47
+ gap: 0.375rem;
48
+ }
49
+ }
50
+
51
+ .header-actions {
52
+ display: flex;
53
+ gap: 0.5rem;
54
+
55
+ .btn-icon {
56
+ width: 36px;
57
+ height: 36px;
58
+ border: none;
59
+ background: transparent;
60
+ border-radius: 0.375rem;
61
+ display: flex;
62
+ align-items: center;
63
+ justify-content: center;
64
+ cursor: pointer;
65
+ transition: all 0.2s;
66
+ color: var(--ai-secondary);
67
+
68
+ &:hover {
69
+ background-color: var(--ai-border);
70
+ color: var(--ai-dark);
71
+ }
72
+
73
+ &.danger:hover {
74
+ background-color: var(--ai-danger);
75
+ color: white;
76
+ }
77
+
78
+ .icon-switch,
79
+ .icon-export,
80
+ .icon-clear {
81
+ font-size: 1.125rem;
82
+ }
83
+ }
84
+ }
85
+ }
86
+
87
+ /* 消息容器 */
88
+ .ai-chat-container {
89
+ flex: 1;
90
+ overflow-y: auto;
91
+ padding: 1rem;
92
+ scroll-behavior: smooth;
93
+
94
+ .messages-wrapper {
95
+ display: flex;
96
+ flex-direction: column;
97
+ gap: 1rem;
98
+ }
99
+
100
+ .date-separator {
101
+ text-align: center;
102
+ margin: 1rem 0;
103
+ position: relative;
104
+
105
+ &::before {
106
+ content: '';
107
+ position: absolute;
108
+ top: 50%;
109
+ left: 0;
110
+ right: 0;
111
+ height: 1px;
112
+ background-color: var(--ai-border);
113
+ }
114
+
115
+ .date-text {
116
+ background-color: var(--ai-bg-primary);
117
+ padding: 0 1rem;
118
+ font-size: 0.875rem;
119
+ color: var(--ai-secondary);
120
+ position: relative;
121
+ }
122
+ }
123
+ }
124
+
125
+ /* 消息项 */
126
+ .message-item {
127
+ display: flex;
128
+ gap: 0.75rem;
129
+ max-width: 85%;
130
+
131
+ &.user-message {
132
+ align-self: flex-end;
133
+ flex-direction: row-reverse;
134
+
135
+ .message-avatar {
136
+ .avatar.user {
137
+ background-color: var(--ai-primary);
138
+ color: white;
139
+ }
140
+ }
141
+
142
+ .message-content {
143
+ .message-bubble {
144
+ background-color: var(--ai-primary);
145
+ color: white;
146
+ border-bottom-right-radius: 0.25rem;
147
+ }
148
+ }
149
+ }
150
+
151
+ &.assistant-message {
152
+ align-self: flex-start;
153
+
154
+ .message-avatar {
155
+ .avatar.assistant {
156
+ background-color: var(--ai-success);
157
+ color: white;
158
+ }
159
+ }
160
+
161
+ .message-content {
162
+ .message-bubble {
163
+ background-color: var(--ai-assistant-message);
164
+ color: var(--ai-dark);
165
+ border-bottom-left-radius: 0.25rem;
166
+ }
167
+ }
168
+ }
169
+
170
+ &.system-message {
171
+ align-self: center;
172
+ max-width: 90%;
173
+
174
+ .message-avatar {
175
+ .avatar.system {
176
+ background-color: var(--ai-warning);
177
+ color: white;
178
+ }
179
+ }
180
+
181
+ .message-content {
182
+ .message-bubble {
183
+ background-color: var(--ai-system-message);
184
+ color: var(--ai-dark);
185
+ border-radius: 0.5rem;
186
+ font-style: italic;
187
+ }
188
+ }
189
+ }
190
+ }
191
+
192
+ /* 头像 */
193
+ .message-avatar {
194
+ flex-shrink: 0;
195
+
196
+ .avatar {
197
+ width: 40px;
198
+ height: 40px;
199
+ border-radius: 50%;
200
+ display: flex;
201
+ align-items: center;
202
+ justify-content: center;
203
+ font-size: 1.25rem;
204
+ }
205
+ }
206
+
207
+ /* 消息内容 */
208
+ .message-content {
209
+ display: flex;
210
+ flex-direction: column;
211
+ gap: 0.25rem;
212
+
213
+ .message-bubble {
214
+ padding: 0.75rem 1rem;
215
+ border-radius: 0.75rem;
216
+ box-shadow: var(--ai-box-shadow);
217
+ word-wrap: break-word;
218
+ white-space: pre-wrap;
219
+
220
+ .message-text {
221
+ margin: 0;
222
+ line-height: 1.6;
223
+
224
+ pre {
225
+ margin: 0;
226
+ white-space: pre-wrap;
227
+ word-wrap: break-word;
228
+ font-family: inherit;
229
+ font-size: inherit;
230
+ }
231
+
232
+ &.ai-response {
233
+ white-space: pre-wrap;
234
+ }
235
+ }
236
+ }
237
+
238
+ .message-time {
239
+ font-size: 0.75rem;
240
+ opacity: 0.6;
241
+ padding: 0 0.5rem;
242
+ }
243
+ }
244
+
245
+ /* 加载指示器 */
246
+ .loading-indicator {
247
+ display: flex;
248
+ align-items: center;
249
+ gap: 0.75rem;
250
+ padding: 0.75rem 1rem;
251
+ background-color: var(--ai-assistant-message);
252
+ border-radius: 0.75rem;
253
+ align-self: flex-start;
254
+ max-width: 200px;
255
+
256
+ .typing-indicator {
257
+ display: flex;
258
+ gap: 0.25rem;
259
+
260
+ span {
261
+ width: 6: 6pxpx;
262
+ height;
263
+ border-radius: 50%;
264
+ background-color: var(--ai-secondary);
265
+ animation: typing 1.4s infinite ease-in-out;
266
+
267
+ &:nth-child(1) {
268
+ animation-delay: -0.32s;
269
+ }
270
+
271
+ &:nth-child(2) {
272
+ animation-delay: -0.16s;
273
+ }
274
+ }
275
+ }
276
+
277
+ .loading-text {
278
+ font-size: 0.875rem;
279
+ color: var(--ai-secondary);
280
+ }
281
+ }
282
+
283
+ @keyframes typing {
284
+ 0%, 80%, 100% {
285
+ transform: scale(0.8);
286
+ opacity: 0.5;
287
+ }
288
+ 40% {
289
+ transform: scale(1);
290
+ opacity: 1;
291
+ }
292
+ }
293
+
294
+ /* 输入区域 */
295
+ .ai-chat-input {
296
+ flex-shrink: 0;
297
+ border-top: 1px solid var(--ai-border);
298
+ background-color: var(--ai-bg-secondary);
299
+ }
300
+
301
+ /* 提示信息 */
302
+ .chat-tips {
303
+ padding: 0.75rem 1.5rem;
304
+ background-color: rgba(0, 123, 255, 0.05);
305
+ border-top: 1px solid var(--ai-border);
306
+
307
+ .tip-item {
308
+ display: flex;
309
+ align-items: center;
310
+ gap: 0.5rem;
311
+ margin: 0.375rem 0;
312
+ font-size: 0.875rem;
313
+ color: var(--ai-info);
314
+
315
+ i {
316
+ font-size: 1rem;
317
+ }
318
+ }
319
+ }
320
+
321
+ /* 响应式设计 */
322
+ @media (max-width: 768px) {
323
+ .ai-chat-header {
324
+ padding: 0.75rem 1rem;
325
+
326
+ .header-left {
327
+ .ai-title {
328
+ font-size: 1rem;
329
+ }
330
+
331
+ .provider-badge {
332
+ font-size: 0.75rem;
333
+ padding: 0.2rem 0.6rem;
334
+ }
335
+ }
336
+
337
+ .header-actions .btn-icon {
338
+ width: 32px;
339
+ height: 32px;
340
+ }
341
+ }
342
+
343
+ .message-item {
344
+ max-width: 95%;
345
+ }
346
+
347
+ .chat-tips {
348
+ padding: 0.5rem 1rem;
349
+
350
+ .tip-item {
351
+ font-size: 0.8rem;
352
+ }
353
+ }
354
+ }
@@ -0,0 +1,224 @@
1
+ import { Component, OnInit, OnDestroy } from '@angular/core';
2
+ import { Subject } from 'rxjs';
3
+ import { takeUntil } from 'rxjs/operators';
4
+ import { ChatMessage, MessageRole } from '../../types/ai.types';
5
+ import { AiAssistantService } from '../../services/core/ai-assistant.service';
6
+ import { ConfigProviderService } from '../../services/core/config-provider.service';
7
+ import { LoggerService } from '../../services/core/logger.service';
8
+ import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
9
+
10
+ @Component({
11
+ selector: 'app-chat-interface',
12
+ templateUrl: './chat-interface.component.html',
13
+ styleUrls: ['./chat-interface.component.scss']
14
+ })
15
+ export class ChatInterfaceComponent implements OnInit, OnDestroy {
16
+ messages: ChatMessage[] = [];
17
+ isLoading = false;
18
+ currentProvider: string = '';
19
+ private destroy$ = new Subject<void>();
20
+
21
+ constructor(
22
+ private aiService: AiAssistantService,
23
+ private config: ConfigProviderService,
24
+ private logger: LoggerService,
25
+ private modal: NgbModal
26
+ ) {}
27
+
28
+ ngOnInit(): void {
29
+ // 加载当前提供商信息
30
+ this.loadCurrentProvider();
31
+
32
+ // 加载聊天历史
33
+ this.loadChatHistory();
34
+
35
+ // 发送欢迎消息
36
+ this.sendWelcomeMessage();
37
+ }
38
+
39
+ ngOnDestroy(): void {
40
+ this.destroy$.next();
41
+ this.destroy$.complete();
42
+ }
43
+
44
+ /**
45
+ * 加载当前提供商信息
46
+ */
47
+ private loadCurrentProvider(): void {
48
+ const status = this.aiService.getProviderStatus();
49
+ this.currentProvider = status.active?.displayName || 'Unknown';
50
+ }
51
+
52
+ /**
53
+ * 加载聊天历史
54
+ */
55
+ private loadChatHistory(): void {
56
+ // TODO: 从本地存储或数据库加载历史消息
57
+ // 暂时使用空数组
58
+ this.messages = [];
59
+ }
60
+
61
+ /**
62
+ * 发送欢迎消息
63
+ */
64
+ private sendWelcomeMessage(): void {
65
+ const welcomeMessage: ChatMessage = {
66
+ id: this.generateId(),
67
+ role: MessageRole.ASSISTANT,
68
+ content: `您好!我是AI助手。\n\n我可以帮助您:\n• 将自然语言转换为终端命令\n• 解释复杂的命令\n• 分析命令执行结果\n• 提供错误修复建议\n\n当前使用:${this.currentProvider}\n\n请输入您的问题或描述您想执行的命令。`,
69
+ timestamp: new Date()
70
+ };
71
+ this.messages.push(welcomeMessage);
72
+ }
73
+
74
+ /**
75
+ * 处理发送消息
76
+ */
77
+ async onSendMessage(content: string): Promise<void> {
78
+ if (!content.trim() || this.isLoading) {
79
+ return;
80
+ }
81
+
82
+ // 添加用户消息
83
+ const userMessage: ChatMessage = {
84
+ id: this.generateId(),
85
+ role: MessageRole.USER,
86
+ content: content.trim(),
87
+ timestamp: new Date()
88
+ };
89
+ this.messages.push(userMessage);
90
+
91
+ // 滚动到底部
92
+ setTimeout(() => this.scrollToBottom(), 0);
93
+
94
+ // 清空输入框
95
+ content = '';
96
+
97
+ // 显示加载状态
98
+ this.isLoading = true;
99
+
100
+ try {
101
+ // 发送请求到AI
102
+ const response = await this.aiService.chat({
103
+ messages: this.messages,
104
+ maxTokens: 1000,
105
+ temperature: 0.7
106
+ });
107
+
108
+ // 添加AI响应
109
+ this.messages.push(response.message);
110
+
111
+ // 保存聊天历史
112
+ this.saveChatHistory();
113
+
114
+ } catch (error) {
115
+ this.logger.error('Failed to send message', error);
116
+
117
+ // 添加错误消息
118
+ const errorMessage: ChatMessage = {
119
+ id: this.generateId(),
120
+ role: MessageRole.ASSISTANT,
121
+ content: `抱歉,我遇到了一些问题:${error instanceof Error ? error.message : 'Unknown error'}\n\n请稍后重试。`,
122
+ timestamp: new Date()
123
+ };
124
+ this.messages.push(errorMessage);
125
+ } finally {
126
+ this.isLoading = false;
127
+ // 滚动到底部
128
+ setTimeout(() => this.scrollToBottom(), 0);
129
+ }
130
+ }
131
+
132
+ /**
133
+ * 清空聊天记录
134
+ */
135
+ clearChat(): void {
136
+ if (confirm('确定要清空聊天记录吗?')) {
137
+ this.messages = [];
138
+ this.sendWelcomeMessage();
139
+ localStorage.removeItem('ai-assistant-chat-history');
140
+ }
141
+ }
142
+
143
+ /**
144
+ * 导出聊天记录
145
+ */
146
+ exportChat(): void {
147
+ const chatData = {
148
+ provider: this.currentProvider,
149
+ exportTime: new Date().toISOString(),
150
+ messages: this.messages
151
+ };
152
+
153
+ const blob = new Blob([JSON.stringify(chatData, null, 2)], { type: 'application/json' });
154
+ const url = window.URL.createObjectURL(blob);
155
+ const a = document.createElement('a');
156
+ a.href = url;
157
+ a.download = `ai-chat-${new Date().toISOString().slice(0, 10)}.json`;
158
+ a.click();
159
+ window.URL.revokeObjectURL(url);
160
+ }
161
+
162
+ /**
163
+ * 切换提供商
164
+ */
165
+ switchProvider(): void {
166
+ // TODO: 打开提供商选择对话框
167
+ this.logger.info('Switch provider requested');
168
+ }
169
+
170
+ /**
171
+ * 滚动到底部
172
+ */
173
+ private scrollToBottom(): void {
174
+ const chatContainer = document.querySelector('.ai-chat-container');
175
+ if (chatContainer) {
176
+ chatContainer.scrollTop = chatContainer.scrollHeight;
177
+ }
178
+ }
179
+
180
+ /**
181
+ * 保存聊天历史
182
+ */
183
+ private saveChatHistory(): void {
184
+ try {
185
+ // 限制历史记录数量
186
+ const recentMessages = this.messages.slice(-100);
187
+ localStorage.setItem('ai-assistant-chat-history', JSON.stringify(recentMessages));
188
+ } catch (error) {
189
+ this.logger.error('Failed to save chat history', error);
190
+ }
191
+ }
192
+
193
+ /**
194
+ * 生成唯一ID
195
+ */
196
+ private generateId(): string {
197
+ return `${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
198
+ }
199
+
200
+ /**
201
+ * 获取消息时间格式
202
+ */
203
+ formatTimestamp(timestamp: Date): string {
204
+ return timestamp.toLocaleTimeString('zh-CN', {
205
+ hour: '2-digit',
206
+ minute: '2-digit'
207
+ });
208
+ }
209
+
210
+ /**
211
+ * 检查是否为今天的消息
212
+ */
213
+ isToday(date: Date): boolean {
214
+ const today = new Date();
215
+ return date.toDateString() === today.toDateString();
216
+ }
217
+
218
+ /**
219
+ * 检查是否为同一天的消息
220
+ */
221
+ isSameDay(date1: Date, date2: Date): boolean {
222
+ return date1.toDateString() === date2.toDateString();
223
+ }
224
+ }
@@ -0,0 +1,65 @@
1
+ <div class="chat-message"
2
+ [ngClass]="{
3
+ 'user-message': isUserMessage(),
4
+ 'assistant-message': isAssistantMessage(),
5
+ 'system-message': isSystemMessage(),
6
+ 'grouped': isGrouped,
7
+ 'with-avatar': showAvatar
8
+ }"
9
+ (click)="onMessageClick()">
10
+
11
+ <!-- 头像 -->
12
+ <div *ngIf="showAvatar" class="message-avatar">
13
+ <div *ngIf="isUserMessage()" class="avatar user">
14
+ <i class="icon-user"></i>
15
+ </div>
16
+ <div *ngIf="isAssistantMessage()" class="avatar assistant">
17
+ <i class="icon-bot"></i>
18
+ </div>
19
+ <div *ngIf="isSystemMessage()" class="avatar system">
20
+ <i class="icon-info"></i>
21
+ </div>
22
+ </div>
23
+
24
+ <!-- 消息内容 -->
25
+ <div class="message-content">
26
+ <div class="message-bubble">
27
+ <!-- 用户消息 -->
28
+ <div *ngIf="isUserMessage()" class="message-text">
29
+ {{ message.content }}
30
+ </div>
31
+
32
+ <!-- AI消息 -->
33
+ <div *ngIf="isAssistantMessage()" class="message-text ai-response">
34
+ <pre>{{ message.content }}</pre>
35
+ </div>
36
+
37
+ <!-- 系统消息 -->
38
+ <div *ngIf="isSystemMessage()" class="message-text system">
39
+ <i class="icon-warning"></i>
40
+ {{ message.content }}
41
+ </div>
42
+
43
+ <!-- 操作按钮(仅AI消息显示) -->
44
+ <div *ngIf="isAssistantMessage()" class="message-actions">
45
+ <button class="action-btn" (click)="copyMessage()" title="复制">
46
+ <i class="icon-copy"></i>
47
+ </button>
48
+ <button class="action-btn" (click)="regenerateResponse()" title="重新生成">
49
+ <i class="icon-refresh"></i>
50
+ </button>
51
+ <button class="action-btn" (click)="markAsHelpful()" title="有用">
52
+ <i class="icon-thumbs-up"></i>
53
+ </button>
54
+ <button class="action-btn" (click)="markAsNotHelpful()" title="无用">
55
+ <i class="icon-thumbs-down"></i>
56
+ </button>
57
+ </div>
58
+ </div>
59
+
60
+ <!-- 时间戳 -->
61
+ <div *ngIf="showTimestamp" class="message-time">
62
+ {{ formatTime(message.timestamp) }}
63
+ </div>
64
+ </div>
65
+ </div>