@huyooo/ai-chat-frontend-vue 0.1.2

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 (45) hide show
  1. package/dist/adapter.d.ts +87 -0
  2. package/dist/adapter.d.ts.map +1 -0
  3. package/dist/components/ChatInput.vue.d.ts +54 -0
  4. package/dist/components/ChatInput.vue.d.ts.map +1 -0
  5. package/dist/components/ChatPanel.vue.d.ts +38 -0
  6. package/dist/components/ChatPanel.vue.d.ts.map +1 -0
  7. package/dist/components/chat/SearchResultBlock.vue.d.ts +8 -0
  8. package/dist/components/chat/SearchResultBlock.vue.d.ts.map +1 -0
  9. package/dist/components/chat/ThinkingBlock.vue.d.ts +7 -0
  10. package/dist/components/chat/ThinkingBlock.vue.d.ts.map +1 -0
  11. package/dist/components/chat/ToolCallBlock.vue.d.ts +9 -0
  12. package/dist/components/chat/ToolCallBlock.vue.d.ts.map +1 -0
  13. package/dist/components/chat/messages/ExecutionSteps.vue.d.ts +13 -0
  14. package/dist/components/chat/messages/ExecutionSteps.vue.d.ts.map +1 -0
  15. package/dist/components/chat/messages/MessageBubble.vue.d.ts +28 -0
  16. package/dist/components/chat/messages/MessageBubble.vue.d.ts.map +1 -0
  17. package/dist/components/chat/ui/ChatHeader.vue.d.ts +34 -0
  18. package/dist/components/chat/ui/ChatHeader.vue.d.ts.map +1 -0
  19. package/dist/components/chat/ui/WelcomeMessage.vue.d.ts +7 -0
  20. package/dist/components/chat/ui/WelcomeMessage.vue.d.ts.map +1 -0
  21. package/dist/composables/useChat.d.ts +96 -0
  22. package/dist/composables/useChat.d.ts.map +1 -0
  23. package/dist/index.d.ts +37 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +1497 -0
  26. package/dist/preload/preload.d.ts +6 -0
  27. package/dist/preload/preload.d.ts.map +1 -0
  28. package/dist/style.css +1 -0
  29. package/dist/types/index.d.ts +107 -0
  30. package/dist/types/index.d.ts.map +1 -0
  31. package/package.json +59 -0
  32. package/src/adapter.ts +160 -0
  33. package/src/components/ChatInput.vue +649 -0
  34. package/src/components/ChatPanel.vue +309 -0
  35. package/src/components/chat/SearchResultBlock.vue +155 -0
  36. package/src/components/chat/ThinkingBlock.vue +109 -0
  37. package/src/components/chat/ToolCallBlock.vue +213 -0
  38. package/src/components/chat/messages/ExecutionSteps.vue +281 -0
  39. package/src/components/chat/messages/MessageBubble.vue +272 -0
  40. package/src/components/chat/ui/ChatHeader.vue +535 -0
  41. package/src/components/chat/ui/WelcomeMessage.vue +135 -0
  42. package/src/composables/useChat.ts +423 -0
  43. package/src/index.ts +82 -0
  44. package/src/preload/preload.ts +79 -0
  45. package/src/types/index.ts +164 -0
@@ -0,0 +1,535 @@
1
+ <template>
2
+ <div class="chat-header">
3
+ <!-- 左侧:Tabs 可滚动区域 -->
4
+ <div class="tabs-container">
5
+ <span v-if="visibleSessions.length === 0" class="tab-item active">
6
+ New Chat
7
+ </span>
8
+ <template v-else>
9
+ <div
10
+ v-for="session in visibleSessions"
11
+ :key="session.id"
12
+ :class="['tab-item', { active: session.id === currentSessionId }]"
13
+ :title="session.title"
14
+ @click="$emit('switch-session', session.id)"
15
+ >
16
+ <span class="tab-title">{{ getDisplayTitle(session.title) }}</span>
17
+ <button
18
+ class="tab-close"
19
+ @click.stop="handleHideTab(session.id)"
20
+ title="关闭标签"
21
+ >
22
+ <X :size="12" />
23
+ </button>
24
+ </div>
25
+ </template>
26
+ </div>
27
+
28
+ <!-- 右侧:操作按钮 -->
29
+ <div class="header-actions">
30
+ <!-- 新建会话 -->
31
+ <button
32
+ class="icon-btn"
33
+ title="新建对话"
34
+ @click="$emit('new-session')"
35
+ >
36
+ <Plus :size="14" />
37
+ </button>
38
+
39
+ <!-- 历史记录 -->
40
+ <div ref="historyRef" class="dropdown-container">
41
+ <button
42
+ :class="['icon-btn', { active: historyOpen }]"
43
+ title="历史记录"
44
+ @click.stop="toggleHistory"
45
+ >
46
+ <Clock :size="14" />
47
+ </button>
48
+
49
+ <!-- 历史记录面板 -->
50
+ <div v-if="historyOpen" class="dropdown-panel history-panel">
51
+ <div class="panel-header">
52
+ <span>历史记录</span>
53
+ <button class="icon-btn small" title="新建对话" @click="handleNewFromHistory">
54
+ <Plus :size="12" />
55
+ </button>
56
+ </div>
57
+ <div class="panel-content">
58
+ <div
59
+ v-for="session in sessions"
60
+ :key="session.id"
61
+ :class="['history-item', { active: session.id === currentSessionId }]"
62
+ @click="handleSelectHistory(session.id)"
63
+ >
64
+ <span class="history-title">{{ session.title }}</span>
65
+ <span class="history-date">{{ formatDate(session.updatedAt) }}</span>
66
+ <button
67
+ class="history-delete"
68
+ title="删除"
69
+ @click.stop="$emit('delete-session', session.id)"
70
+ >
71
+ <Trash2 :size="12" />
72
+ </button>
73
+ </div>
74
+ <div v-if="sessions.length === 0" class="empty-state">
75
+ 暂无历史对话
76
+ </div>
77
+ </div>
78
+ </div>
79
+ </div>
80
+
81
+ <!-- 更多选项 -->
82
+ <div ref="moreRef" class="dropdown-container">
83
+ <button
84
+ :class="['icon-btn', { active: moreOpen }]"
85
+ title="更多选项"
86
+ @click.stop="toggleMore"
87
+ >
88
+ <MoreHorizontal :size="14" />
89
+ </button>
90
+
91
+ <!-- 更多选项菜单 -->
92
+ <div v-if="moreOpen" class="dropdown-panel more-panel">
93
+ <button class="menu-item" @click="handleMenuAction('clear-all')">
94
+ <Trash2 :size="14" />
95
+ <span>清空所有对话</span>
96
+ </button>
97
+ <button class="menu-item" @click="handleMenuAction('close-others')">
98
+ <XCircle :size="14" />
99
+ <span>关闭其他对话</span>
100
+ </button>
101
+ <div class="menu-divider"></div>
102
+ <button class="menu-item" @click="handleMenuAction('export')">
103
+ <Download :size="14" />
104
+ <span>导出对话</span>
105
+ </button>
106
+ <button class="menu-item" @click="handleMenuAction('copy-id')">
107
+ <Copy :size="14" />
108
+ <span>复制请求 ID</span>
109
+ </button>
110
+ <div class="menu-divider"></div>
111
+ <button class="menu-item" @click="handleMenuAction('feedback')">
112
+ <MessageSquare :size="14" />
113
+ <span>反馈</span>
114
+ </button>
115
+ <button class="menu-item" @click="handleMenuAction('settings')">
116
+ <Settings :size="14" />
117
+ <span>Agent 设置</span>
118
+ </button>
119
+ </div>
120
+ </div>
121
+
122
+ <!-- 关闭按钮 -->
123
+ <button
124
+ v-if="showClose"
125
+ class="icon-btn"
126
+ title="关闭"
127
+ @click="$emit('close')"
128
+ >
129
+ <X :size="14" />
130
+ </button>
131
+ </div>
132
+ </div>
133
+ </template>
134
+
135
+ <script setup lang="ts">
136
+ import { ref, computed, onMounted, onUnmounted } from 'vue';
137
+ import {
138
+ X,
139
+ Plus,
140
+ Clock,
141
+ MoreHorizontal,
142
+ Trash2,
143
+ XCircle,
144
+ Download,
145
+ Copy,
146
+ MessageSquare,
147
+ Settings,
148
+ } from 'lucide-vue-next';
149
+ import type { SessionRecord } from '../../../types';
150
+
151
+ const props = defineProps<{
152
+ sessions: SessionRecord[];
153
+ currentSessionId?: string | null;
154
+ showClose?: boolean;
155
+ }>();
156
+
157
+ const emit = defineEmits<{
158
+ 'new-session': [];
159
+ 'switch-session': [sessionId: string];
160
+ 'delete-session': [sessionId: string];
161
+ 'close': [];
162
+ 'clear-all': [];
163
+ 'close-others': [];
164
+ 'export': [];
165
+ 'copy-id': [];
166
+ 'feedback': [];
167
+ 'settings': [];
168
+ }>();
169
+
170
+ // 隐藏的 tab(关闭但不删除)
171
+ const hiddenTabs = ref<Set<string>>(new Set());
172
+
173
+ // 可见的会话(过滤掉隐藏的)
174
+ const visibleSessions = computed(() =>
175
+ props.sessions.filter((s) => !hiddenTabs.value.has(s.id))
176
+ );
177
+
178
+ // 下拉菜单状态
179
+ const historyOpen = ref(false);
180
+ const moreOpen = ref(false);
181
+ const historyRef = ref<HTMLDivElement | null>(null);
182
+ const moreRef = ref<HTMLDivElement | null>(null);
183
+
184
+ // 显示标题
185
+ function getDisplayTitle(title: string) {
186
+ return title === '新对话' ? 'New Chat' : title;
187
+ }
188
+
189
+ // 格式化日期
190
+ function formatDate(date: Date) {
191
+ const d = new Date(date);
192
+ const now = new Date();
193
+ const diff = now.getTime() - d.getTime();
194
+ const days = Math.floor(diff / (1000 * 60 * 60 * 24));
195
+
196
+ if (days === 0) return '今天';
197
+ if (days === 1) return '昨天';
198
+ if (days < 7) return `${days}天前`;
199
+ return d.toLocaleDateString();
200
+ }
201
+
202
+ // 隐藏 tab
203
+ function handleHideTab(sessionId: string) {
204
+ hiddenTabs.value = new Set([...hiddenTabs.value, sessionId]);
205
+ if (sessionId === props.currentSessionId) {
206
+ const remaining = props.sessions.filter(
207
+ (s) => s.id !== sessionId && !hiddenTabs.value.has(s.id)
208
+ );
209
+ if (remaining.length > 0) {
210
+ emit('switch-session', remaining[0].id);
211
+ }
212
+ }
213
+ }
214
+
215
+ // 切换历史面板
216
+ function toggleHistory() {
217
+ historyOpen.value = !historyOpen.value;
218
+ moreOpen.value = false;
219
+ }
220
+
221
+ // 切换更多菜单
222
+ function toggleMore() {
223
+ moreOpen.value = !moreOpen.value;
224
+ historyOpen.value = false;
225
+ }
226
+
227
+ // 从历史新建
228
+ function handleNewFromHistory() {
229
+ emit('new-session');
230
+ historyOpen.value = false;
231
+ }
232
+
233
+ // 选择历史
234
+ function handleSelectHistory(sessionId: string) {
235
+ emit('switch-session', sessionId);
236
+ // 如果被隐藏了,恢复显示
237
+ if (hiddenTabs.value.has(sessionId)) {
238
+ const newHidden = new Set(hiddenTabs.value);
239
+ newHidden.delete(sessionId);
240
+ hiddenTabs.value = newHidden;
241
+ }
242
+ historyOpen.value = false;
243
+ }
244
+
245
+ // 菜单操作
246
+ function handleMenuAction(action: string) {
247
+ moreOpen.value = false;
248
+ switch (action) {
249
+ case 'clear-all':
250
+ emit('clear-all');
251
+ break;
252
+ case 'close-others':
253
+ emit('close-others');
254
+ break;
255
+ case 'export':
256
+ emit('export');
257
+ break;
258
+ case 'copy-id':
259
+ emit('copy-id');
260
+ break;
261
+ case 'feedback':
262
+ emit('feedback');
263
+ break;
264
+ case 'settings':
265
+ emit('settings');
266
+ break;
267
+ }
268
+ }
269
+
270
+ // 点击外部关闭菜单
271
+ function handleClickOutside(event: MouseEvent) {
272
+ const target = event.target as HTMLElement;
273
+ if (historyRef.value && !historyRef.value.contains(target)) {
274
+ historyOpen.value = false;
275
+ }
276
+ if (moreRef.value && !moreRef.value.contains(target)) {
277
+ moreOpen.value = false;
278
+ }
279
+ }
280
+
281
+ onMounted(() => {
282
+ document.addEventListener('click', handleClickOutside);
283
+ });
284
+
285
+ onUnmounted(() => {
286
+ document.removeEventListener('click', handleClickOutside);
287
+ });
288
+ </script>
289
+
290
+ <style scoped>
291
+ .chat-header {
292
+ display: flex;
293
+ align-items: center;
294
+ height: 40px;
295
+ padding: 0 12px;
296
+ background: var(--chat-header-bg, #1e1e1e);
297
+ border-bottom: 1px solid var(--chat-border, #333);
298
+ flex-shrink: 0;
299
+ }
300
+
301
+ .tabs-container {
302
+ display: flex;
303
+ align-items: center;
304
+ gap: 4px;
305
+ flex: 1;
306
+ overflow-x: auto;
307
+ padding-right: 8px;
308
+ }
309
+
310
+ .tabs-container::-webkit-scrollbar {
311
+ display: none;
312
+ }
313
+
314
+ .tab-item {
315
+ position: relative;
316
+ display: flex;
317
+ align-items: center;
318
+ gap: 4px;
319
+ padding: 4px 8px;
320
+ background: transparent;
321
+ border: none;
322
+ border-radius: 4px;
323
+ color: var(--chat-text-muted, #888);
324
+ font-size: 13px;
325
+ font-weight: 500;
326
+ cursor: pointer;
327
+ transition: all 0.15s;
328
+ white-space: nowrap;
329
+ max-width: 120px;
330
+ flex-shrink: 0;
331
+ }
332
+
333
+ .tab-item:hover {
334
+ color: var(--chat-text, #ccc);
335
+ }
336
+
337
+ .tab-item.active {
338
+ background: var(--chat-muted, #3c3c3c);
339
+ color: var(--chat-text, #fff);
340
+ }
341
+
342
+ .tab-title {
343
+ overflow: hidden;
344
+ text-overflow: ellipsis;
345
+ white-space: nowrap;
346
+ }
347
+
348
+ .tab-close {
349
+ display: none;
350
+ width: 16px;
351
+ height: 16px;
352
+ padding: 0;
353
+ background: transparent;
354
+ border: none;
355
+ border-radius: 2px;
356
+ color: var(--chat-text-muted, #888);
357
+ cursor: pointer;
358
+ align-items: center;
359
+ justify-content: center;
360
+ flex-shrink: 0;
361
+ }
362
+
363
+ .tab-item:hover .tab-close {
364
+ display: flex;
365
+ }
366
+
367
+ .tab-close:hover {
368
+ color: var(--chat-text, #fff);
369
+ background: var(--chat-muted, #444);
370
+ }
371
+
372
+ .header-actions {
373
+ display: flex;
374
+ align-items: center;
375
+ gap: 2px;
376
+ flex-shrink: 0;
377
+ }
378
+
379
+ .icon-btn {
380
+ display: flex;
381
+ align-items: center;
382
+ justify-content: center;
383
+ width: 24px;
384
+ height: 24px;
385
+ padding: 0;
386
+ background: transparent;
387
+ border: none;
388
+ border-radius: 4px;
389
+ color: var(--chat-text-muted, #888);
390
+ cursor: pointer;
391
+ transition: all 0.15s;
392
+ }
393
+
394
+ .icon-btn:hover,
395
+ .icon-btn.active {
396
+ color: var(--chat-text, #fff);
397
+ }
398
+
399
+ .icon-btn.small {
400
+ width: 20px;
401
+ height: 20px;
402
+ }
403
+
404
+ .dropdown-container {
405
+ position: relative;
406
+ }
407
+
408
+ .dropdown-panel {
409
+ position: absolute;
410
+ top: 100%;
411
+ right: 0;
412
+ margin-top: 4px;
413
+ background: var(--chat-dropdown-bg, #252526);
414
+ border: 1px solid rgba(255, 255, 255, 0.1);
415
+ border-radius: 8px;
416
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
417
+ z-index: 100;
418
+ overflow: hidden;
419
+ }
420
+
421
+ .history-panel {
422
+ width: 280px;
423
+ }
424
+
425
+ .more-panel {
426
+ width: 200px;
427
+ padding: 4px;
428
+ }
429
+
430
+ .panel-header {
431
+ display: flex;
432
+ align-items: center;
433
+ justify-content: space-between;
434
+ padding: 10px 12px;
435
+ border-bottom: 1px solid var(--chat-border, #333);
436
+ font-size: 12px;
437
+ font-weight: 500;
438
+ color: var(--chat-text-muted, #888);
439
+ }
440
+
441
+ .panel-content {
442
+ max-height: 300px;
443
+ overflow-y: auto;
444
+ }
445
+
446
+ .history-item {
447
+ display: flex;
448
+ align-items: center;
449
+ gap: 8px;
450
+ padding: 10px 12px;
451
+ cursor: pointer;
452
+ transition: background 0.15s;
453
+ }
454
+
455
+ .history-item:hover {
456
+ background: rgba(255, 255, 255, 0.08);
457
+ }
458
+
459
+ .history-item.active {
460
+ background: rgba(255, 255, 255, 0.1);
461
+ }
462
+
463
+ .history-title {
464
+ flex: 1;
465
+ font-size: 13px;
466
+ color: var(--chat-text, #ccc);
467
+ overflow: hidden;
468
+ text-overflow: ellipsis;
469
+ white-space: nowrap;
470
+ }
471
+
472
+ .history-date {
473
+ font-size: 11px;
474
+ color: var(--chat-text-muted, #666);
475
+ flex-shrink: 0;
476
+ }
477
+
478
+ .history-delete {
479
+ display: none;
480
+ width: 20px;
481
+ height: 20px;
482
+ padding: 0;
483
+ background: transparent;
484
+ border: none;
485
+ border-radius: 4px;
486
+ color: var(--chat-text-muted, #666);
487
+ cursor: pointer;
488
+ align-items: center;
489
+ justify-content: center;
490
+ flex-shrink: 0;
491
+ }
492
+
493
+ .history-item:hover .history-delete {
494
+ display: flex;
495
+ }
496
+
497
+ .history-delete:hover {
498
+ color: var(--chat-destructive, #ef4444);
499
+ background: var(--chat-destructive, #ef444420);
500
+ }
501
+
502
+ .empty-state {
503
+ padding: 20px;
504
+ text-align: center;
505
+ font-size: 13px;
506
+ color: var(--chat-text-muted, #666);
507
+ }
508
+
509
+ .menu-item {
510
+ display: flex;
511
+ align-items: center;
512
+ gap: 8px;
513
+ width: 100%;
514
+ padding: 8px 10px;
515
+ background: transparent;
516
+ border: none;
517
+ border-radius: 4px;
518
+ font-size: 13px;
519
+ color: var(--chat-text-muted, #999);
520
+ cursor: pointer;
521
+ transition: all 0.15s;
522
+ text-align: left;
523
+ }
524
+
525
+ .menu-item:hover {
526
+ background: rgba(255, 255, 255, 0.08);
527
+ color: var(--chat-text, #ccc);
528
+ }
529
+
530
+ .menu-divider {
531
+ height: 1px;
532
+ background: var(--chat-border, #333);
533
+ margin: 4px 0;
534
+ }
535
+ </style>
@@ -0,0 +1,135 @@
1
+ <template>
2
+ <div class="welcome-message">
3
+ <div class="welcome-icon">
4
+ <Sparkles :size="48" />
5
+ </div>
6
+ <h3 class="welcome-title">AI Assistant</h3>
7
+ <p class="welcome-desc">我可以帮你完成各种任务,试试下面的快捷操作:</p>
8
+
9
+ <div class="quick-actions">
10
+ <button
11
+ v-for="action in quickActions"
12
+ :key="action.id"
13
+ class="quick-btn"
14
+ @click="$emit('quick-action', action.prompt)"
15
+ >
16
+ <component :is="action.icon" :size="14" class="quick-icon" />
17
+ <span>{{ action.label }}</span>
18
+ </button>
19
+ </div>
20
+ </div>
21
+ </template>
22
+
23
+ <script setup lang="ts">
24
+ import { markRaw } from 'vue';
25
+ import {
26
+ Sparkles,
27
+ Code,
28
+ FileText,
29
+ Search,
30
+ MessageCircle,
31
+ } from 'lucide-vue-next';
32
+
33
+ defineEmits<{
34
+ 'quick-action': [prompt: string];
35
+ }>();
36
+
37
+ const quickActions = [
38
+ {
39
+ id: 'code',
40
+ label: '写代码',
41
+ prompt: '帮我写一段代码',
42
+ icon: markRaw(Code),
43
+ },
44
+ {
45
+ id: 'explain',
46
+ label: '解释代码',
47
+ prompt: '帮我解释这段代码',
48
+ icon: markRaw(FileText),
49
+ },
50
+ {
51
+ id: 'search',
52
+ label: '搜索信息',
53
+ prompt: '帮我搜索一下',
54
+ icon: markRaw(Search),
55
+ },
56
+ {
57
+ id: 'chat',
58
+ label: '聊聊天',
59
+ prompt: '我们聊聊天吧',
60
+ icon: markRaw(MessageCircle),
61
+ },
62
+ ];
63
+ </script>
64
+
65
+ <style scoped>
66
+ .welcome-message {
67
+ display: flex;
68
+ flex-direction: column;
69
+ align-items: center;
70
+ justify-content: center;
71
+ height: 100%;
72
+ padding: 32px 20px;
73
+ text-align: center;
74
+ }
75
+
76
+ .welcome-icon {
77
+ display: flex;
78
+ align-items: center;
79
+ justify-content: center;
80
+ width: 80px;
81
+ height: 80px;
82
+ background: rgba(255, 255, 255, 0.1);
83
+ border-radius: 20px;
84
+ color: var(--chat-text, #ccc);
85
+ margin-bottom: 20px;
86
+ }
87
+
88
+ .welcome-title {
89
+ font-size: 20px;
90
+ font-weight: 600;
91
+ color: var(--chat-text, #ccc);
92
+ margin-bottom: 8px;
93
+ }
94
+
95
+ .welcome-desc {
96
+ font-size: 14px;
97
+ color: var(--chat-text-muted, #888);
98
+ margin-bottom: 24px;
99
+ }
100
+
101
+ .quick-actions {
102
+ display: flex;
103
+ flex-wrap: wrap;
104
+ gap: 8px;
105
+ justify-content: center;
106
+ max-width: 320px;
107
+ }
108
+
109
+ .quick-btn {
110
+ display: flex;
111
+ align-items: center;
112
+ gap: 6px;
113
+ padding: 8px 14px;
114
+ background: var(--chat-muted, #2d2d2d);
115
+ border: 1px solid var(--chat-border, #444);
116
+ border-radius: 8px;
117
+ color: var(--chat-text, #ccc);
118
+ font-size: 13px;
119
+ cursor: pointer;
120
+ transition: all 0.15s;
121
+ }
122
+
123
+ .quick-btn:hover {
124
+ background: rgba(255, 255, 255, 0.1);
125
+ border-color: rgba(255, 255, 255, 0.2);
126
+ }
127
+
128
+ .quick-icon {
129
+ color: var(--chat-text-muted, #888);
130
+ }
131
+
132
+ .quick-btn:hover .quick-icon {
133
+ color: var(--chat-text, #fff);
134
+ }
135
+ </style>