@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,213 @@
1
+ <template>
2
+ <div :class="['tool-call-block', status]">
3
+ <div class="tool-header" @click="expanded = !expanded">
4
+ <div class="tool-icon">
5
+ <Loader2 v-if="status === 'running'" :size="14" class="spinning" />
6
+ <Check v-else-if="status === 'success'" :size="14" />
7
+ <X v-else-if="status === 'error'" :size="14" />
8
+ <component v-else :is="toolIcon" :size="14" />
9
+ </div>
10
+ <span class="tool-name">{{ displayName }}</span>
11
+ <ChevronDown
12
+ v-if="result"
13
+ :size="14"
14
+ :class="['chevron', { rotated: expanded }]"
15
+ />
16
+ </div>
17
+
18
+ <!-- 参数展示 -->
19
+ <div v-if="args && Object.keys(args).length > 0 && expanded" class="tool-args">
20
+ <div v-for="(value, key) in args" :key="key" class="arg-item">
21
+ <span class="arg-key">{{ key }}:</span>
22
+ <span class="arg-value">{{ formatValue(value) }}</span>
23
+ </div>
24
+ </div>
25
+
26
+ <!-- 结果展示 -->
27
+ <div v-if="result && expanded" class="tool-result">
28
+ <pre>{{ result }}</pre>
29
+ </div>
30
+ </div>
31
+ </template>
32
+
33
+ <script setup lang="ts">
34
+ import { ref, computed, markRaw } from 'vue';
35
+ import {
36
+ Terminal,
37
+ Image,
38
+ Video,
39
+ Search,
40
+ FileText,
41
+ Loader2,
42
+ Check,
43
+ X,
44
+ ChevronDown,
45
+ } from 'lucide-vue-next';
46
+
47
+ const props = defineProps<{
48
+ name: string;
49
+ args?: Record<string, unknown>;
50
+ result?: string;
51
+ status: 'running' | 'success' | 'error';
52
+ }>();
53
+
54
+ const expanded = ref(false);
55
+
56
+ // 工具显示名称
57
+ const displayName = computed(() => {
58
+ const names: Record<string, string> = {
59
+ execute_command: '执行命令',
60
+ analyze_image: '分析图片',
61
+ generate_image: '生成图片',
62
+ analyze_video: '分析视频',
63
+ web_search: '网络搜索',
64
+ read_file: '读取文件',
65
+ write_file: '写入文件',
66
+ list_directory: '列出目录',
67
+ create_directory: '创建目录',
68
+ delete_file: '删除文件',
69
+ move_file: '移动文件',
70
+ search_files: '搜索文件',
71
+ };
72
+ return names[props.name] || props.name;
73
+ });
74
+
75
+ // 工具图标
76
+ const toolIcon = computed(() => {
77
+ const icons: Record<string, unknown> = {
78
+ execute_command: markRaw(Terminal),
79
+ analyze_image: markRaw(Image),
80
+ generate_image: markRaw(Image),
81
+ analyze_video: markRaw(Video),
82
+ web_search: markRaw(Search),
83
+ };
84
+ return icons[props.name] || markRaw(FileText);
85
+ });
86
+
87
+ // 格式化参数值
88
+ function formatValue(value: unknown): string {
89
+ if (typeof value === 'string') {
90
+ return value.length > 50 ? value.substring(0, 50) + '...' : value;
91
+ }
92
+ return JSON.stringify(value);
93
+ }
94
+ </script>
95
+
96
+ <style scoped>
97
+ .tool-call-block {
98
+ margin: 8px 0;
99
+ background: var(--chat-muted, #2d2d2d);
100
+ border-radius: 8px;
101
+ overflow: hidden;
102
+ }
103
+
104
+ .tool-header {
105
+ display: flex;
106
+ align-items: center;
107
+ gap: 8px;
108
+ padding: 10px 12px;
109
+ cursor: pointer;
110
+ transition: background 0.15s;
111
+ }
112
+
113
+ .tool-header:hover {
114
+ background: var(--chat-muted-hover, #3a3a3a);
115
+ }
116
+
117
+ .tool-icon {
118
+ display: flex;
119
+ align-items: center;
120
+ justify-content: center;
121
+ width: 24px;
122
+ height: 24px;
123
+ background: var(--chat-muted, #3c3c3c);
124
+ border-radius: 4px;
125
+ color: var(--chat-text-muted, #888);
126
+ }
127
+
128
+ .running .tool-icon {
129
+ background: rgba(255, 255, 255, 0.1);
130
+ color: var(--chat-text, #ccc);
131
+ }
132
+
133
+ .success .tool-icon {
134
+ background: var(--chat-success, #22c55e20);
135
+ color: var(--chat-success, #22c55e);
136
+ }
137
+
138
+ .error .tool-icon {
139
+ background: var(--chat-destructive, #ef444420);
140
+ color: var(--chat-destructive, #ef4444);
141
+ }
142
+
143
+ .spinning {
144
+ animation: spin 1s linear infinite;
145
+ }
146
+
147
+ @keyframes spin {
148
+ from {
149
+ transform: rotate(0deg);
150
+ }
151
+ to {
152
+ transform: rotate(360deg);
153
+ }
154
+ }
155
+
156
+ .tool-name {
157
+ flex: 1;
158
+ font-size: 13px;
159
+ font-weight: 500;
160
+ color: var(--chat-text, #ccc);
161
+ }
162
+
163
+ .chevron {
164
+ color: var(--chat-text-muted, #666);
165
+ transition: transform 0.2s;
166
+ }
167
+
168
+ .chevron.rotated {
169
+ transform: rotate(180deg);
170
+ }
171
+
172
+ /* 参数展示 */
173
+ .tool-args {
174
+ padding: 8px 12px;
175
+ border-top: 1px solid var(--chat-border, #333);
176
+ }
177
+
178
+ .arg-item {
179
+ display: flex;
180
+ gap: 8px;
181
+ font-size: 12px;
182
+ line-height: 1.6;
183
+ }
184
+
185
+ .arg-key {
186
+ color: var(--chat-text-muted, #888);
187
+ font-weight: 500;
188
+ }
189
+
190
+ .arg-value {
191
+ color: var(--chat-text, #ccc);
192
+ font-family: 'SF Mono', Monaco, monospace;
193
+ word-break: break-all;
194
+ }
195
+
196
+ /* 结果展示 */
197
+ .tool-result {
198
+ border-top: 1px solid var(--chat-border, #333);
199
+ padding: 8px 12px;
200
+ background: var(--chat-bg, #1e1e1e);
201
+ }
202
+
203
+ .tool-result pre {
204
+ margin: 0;
205
+ font-size: 12px;
206
+ color: var(--chat-text-muted, #999);
207
+ white-space: pre-wrap;
208
+ word-break: break-word;
209
+ font-family: 'SF Mono', Monaco, monospace;
210
+ max-height: 200px;
211
+ overflow-y: auto;
212
+ }
213
+ </style>
@@ -0,0 +1,281 @@
1
+ <template>
2
+ <div v-if="hasSteps" class="execution-steps">
3
+ <!-- 思考过程 -->
4
+ <div v-if="thinking || (loading && !hasContent && !toolCalls?.length)" class="step thinking-step">
5
+ <div class="step-header" @click="thinkingExpanded = !thinkingExpanded">
6
+ <div class="step-icon">
7
+ <Loader2 v-if="!thinkingComplete" :size="14" class="animate-spin" />
8
+ <Lightbulb v-else :size="14" />
9
+ </div>
10
+ <span class="step-title">
11
+ {{ thinkingComplete ? '思考完成' : '正在思考...' }}
12
+ </span>
13
+ <ChevronDown
14
+ v-if="thinking"
15
+ :size="14"
16
+ :class="['step-chevron', { expanded: thinkingExpanded }]"
17
+ />
18
+ </div>
19
+ <div v-if="thinking && thinkingExpanded" class="step-content">
20
+ <div class="thinking-content">{{ thinking }}</div>
21
+ </div>
22
+ </div>
23
+
24
+ <!-- 搜索过程 -->
25
+ <div v-if="searching || searchResults?.length" class="step search-step">
26
+ <div class="step-header" @click="searchExpanded = !searchExpanded">
27
+ <div class="step-icon">
28
+ <Loader2 v-if="searching" :size="14" class="animate-spin" />
29
+ <Globe v-else :size="14" />
30
+ </div>
31
+ <span class="step-title">
32
+ {{ searching ? '正在搜索...' : `找到 ${searchResults?.length || 0} 条结果` }}
33
+ </span>
34
+ <ChevronDown
35
+ v-if="searchResults?.length"
36
+ :size="14"
37
+ :class="['step-chevron', { expanded: searchExpanded }]"
38
+ />
39
+ </div>
40
+ <div v-if="searchResults?.length && searchExpanded" class="step-content">
41
+ <div class="search-results">
42
+ <a
43
+ v-for="(result, i) in searchResults"
44
+ :key="i"
45
+ :href="result.url"
46
+ target="_blank"
47
+ class="search-result"
48
+ >
49
+ <span class="result-title">{{ result.title }}</span>
50
+ <span class="result-url">{{ getDomain(result.url) }}</span>
51
+ </a>
52
+ </div>
53
+ </div>
54
+ </div>
55
+
56
+ <!-- 工具调用 -->
57
+ <div v-for="(tool, i) in toolCalls" :key="i" class="step tool-step">
58
+ <div class="step-header" @click="toggleToolExpanded(i)">
59
+ <div class="step-icon">
60
+ <Loader2 v-if="tool.status === 'running'" :size="14" class="animate-spin" />
61
+ <Check v-else-if="tool.status === 'success'" :size="14" class="text-green" />
62
+ <X v-else :size="14" class="text-red" />
63
+ </div>
64
+ <span class="step-title">
65
+ {{ getToolDisplayName(tool.name) }}
66
+ </span>
67
+ <ChevronDown
68
+ v-if="tool.result"
69
+ :size="14"
70
+ :class="['step-chevron', { expanded: toolExpandedMap[i] }]"
71
+ />
72
+ </div>
73
+ <div v-if="tool.result && toolExpandedMap[i]" class="step-content">
74
+ <pre class="tool-result">{{ tool.result }}</pre>
75
+ </div>
76
+ </div>
77
+ </div>
78
+ </template>
79
+
80
+ <script setup lang="ts">
81
+ import { ref, computed, reactive } from 'vue';
82
+ import { Loader2, Lightbulb, Globe, Check, X, ChevronDown } from 'lucide-vue-next';
83
+ import type { ToolCall, SearchResult } from '../../../types';
84
+
85
+ const props = defineProps<{
86
+ loading?: boolean;
87
+ hasContent?: boolean;
88
+ thinking?: string;
89
+ thinkingComplete?: boolean;
90
+ searching?: boolean;
91
+ searchResults?: SearchResult[];
92
+ toolCalls?: ToolCall[];
93
+ }>();
94
+
95
+ // 展开状态
96
+ const thinkingExpanded = ref(false);
97
+ const searchExpanded = ref(true);
98
+ const toolExpandedMap = reactive<Record<number, boolean>>({});
99
+
100
+ // 是否有步骤显示
101
+ const hasSteps = computed(() => {
102
+ return (
103
+ props.thinking ||
104
+ props.searching ||
105
+ props.searchResults?.length ||
106
+ props.toolCalls?.length ||
107
+ (props.loading && !props.hasContent)
108
+ );
109
+ });
110
+
111
+ // 切换工具展开
112
+ function toggleToolExpanded(index: number) {
113
+ toolExpandedMap[index] = !toolExpandedMap[index];
114
+ }
115
+
116
+ // 获取域名
117
+ function getDomain(url: string) {
118
+ try {
119
+ return new URL(url).hostname;
120
+ } catch {
121
+ return url;
122
+ }
123
+ }
124
+
125
+ // 获取工具显示名称
126
+ function getToolDisplayName(name: string) {
127
+ const nameMap: Record<string, string> = {
128
+ web_search: '网页搜索',
129
+ read_file: '读取文件',
130
+ write_file: '写入文件',
131
+ execute_command: '执行命令',
132
+ list_directory: '列出目录',
133
+ create_directory: '创建目录',
134
+ delete_file: '删除文件',
135
+ move_file: '移动文件',
136
+ search_files: '搜索文件',
137
+ analyze_image: '分析图片',
138
+ };
139
+ return nameMap[name] || name;
140
+ }
141
+ </script>
142
+
143
+ <style scoped>
144
+ .execution-steps {
145
+ display: flex;
146
+ flex-direction: column;
147
+ gap: 4px;
148
+ margin-bottom: 8px;
149
+ }
150
+
151
+ .step {
152
+ background: var(--chat-muted, #2d2d2d);
153
+ border-radius: 8px;
154
+ overflow: hidden;
155
+ }
156
+
157
+ .step-header {
158
+ display: flex;
159
+ align-items: center;
160
+ gap: 8px;
161
+ padding: 8px 12px;
162
+ cursor: pointer;
163
+ transition: background 0.15s;
164
+ }
165
+
166
+ .step-header:hover {
167
+ background: var(--chat-muted-hover, #3a3a3a);
168
+ }
169
+
170
+ .step-icon {
171
+ display: flex;
172
+ align-items: center;
173
+ justify-content: center;
174
+ width: 20px;
175
+ height: 20px;
176
+ color: var(--chat-text-muted, #888);
177
+ }
178
+
179
+ .step-title {
180
+ flex: 1;
181
+ font-size: 13px;
182
+ color: var(--chat-text, #ccc);
183
+ }
184
+
185
+ .step-chevron {
186
+ color: var(--chat-text-muted, #666);
187
+ transition: transform 0.2s;
188
+ }
189
+
190
+ .step-chevron.expanded {
191
+ transform: rotate(180deg);
192
+ }
193
+
194
+ .step-content {
195
+ padding: 0 12px 12px;
196
+ border-top: 1px solid var(--chat-border, #333);
197
+ margin-top: 4px;
198
+ padding-top: 8px;
199
+ }
200
+
201
+ .thinking-content {
202
+ font-size: 13px;
203
+ line-height: 1.6;
204
+ color: var(--chat-text-muted, #999);
205
+ white-space: pre-wrap;
206
+ word-break: break-word;
207
+ }
208
+
209
+ .search-results {
210
+ display: flex;
211
+ flex-direction: column;
212
+ gap: 8px;
213
+ }
214
+
215
+ .search-result {
216
+ display: flex;
217
+ flex-direction: column;
218
+ gap: 2px;
219
+ padding: 8px;
220
+ background: var(--chat-bg, #1e1e1e);
221
+ border-radius: 6px;
222
+ text-decoration: none;
223
+ transition: background 0.15s;
224
+ }
225
+
226
+ .search-result:hover {
227
+ background: var(--chat-muted-hover, #333);
228
+ }
229
+
230
+ .result-title {
231
+ font-size: 13px;
232
+ color: var(--chat-text, #ccc);
233
+ overflow: hidden;
234
+ text-overflow: ellipsis;
235
+ white-space: nowrap;
236
+ }
237
+
238
+ .search-result:hover .result-title {
239
+ color: var(--chat-text, #fff);
240
+ }
241
+
242
+ .result-url {
243
+ font-size: 11px;
244
+ color: var(--chat-text-muted, #666);
245
+ }
246
+
247
+ .tool-result {
248
+ font-size: 12px;
249
+ line-height: 1.5;
250
+ color: var(--chat-text-muted, #999);
251
+ background: var(--chat-bg, #1e1e1e);
252
+ padding: 8px;
253
+ border-radius: 6px;
254
+ overflow-x: auto;
255
+ white-space: pre-wrap;
256
+ word-break: break-word;
257
+ font-family: 'SF Mono', Monaco, monospace;
258
+ margin: 0;
259
+ }
260
+
261
+ .animate-spin {
262
+ animation: spin 1s linear infinite;
263
+ }
264
+
265
+ @keyframes spin {
266
+ from {
267
+ transform: rotate(0deg);
268
+ }
269
+ to {
270
+ transform: rotate(360deg);
271
+ }
272
+ }
273
+
274
+ .text-green {
275
+ color: var(--chat-success, #22c55e);
276
+ }
277
+
278
+ .text-red {
279
+ color: var(--chat-destructive, #ef4444);
280
+ }
281
+ </style>