@veltra/ai 1.4.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 (71) hide show
  1. package/dist/chat/index.d.ts +5 -0
  2. package/dist/chat/transports/index.d.ts +2 -0
  3. package/dist/chat/transports/openai.d.ts +23 -0
  4. package/dist/chat/transports/openai.js +176 -0
  5. package/dist/chat/transports/openai.js.map +1 -0
  6. package/dist/chat/types.d.ts +125 -0
  7. package/dist/chat/use-chat.d.ts +25 -0
  8. package/dist/chat/use-chat.js +214 -0
  9. package/dist/chat/use-chat.js.map +1 -0
  10. package/dist/components/ai-chat/ai-chat.js +88 -0
  11. package/dist/components/ai-chat/ai-chat.js.map +1 -0
  12. package/dist/components/ai-chat/ai-chat.vue.d.ts +36 -0
  13. package/dist/components/ai-chat/ask-question.js +187 -0
  14. package/dist/components/ai-chat/ask-question.js.map +1 -0
  15. package/dist/components/ai-chat/chat-input.js +143 -0
  16. package/dist/components/ai-chat/chat-input.js.map +1 -0
  17. package/dist/components/ai-chat/di.js +6 -0
  18. package/dist/components/ai-chat/di.js.map +1 -0
  19. package/dist/components/ai-chat/index.d.ts +2 -0
  20. package/dist/components/ai-chat/message-item.js +120 -0
  21. package/dist/components/ai-chat/message-item.js.map +1 -0
  22. package/dist/components/ai-chat/message-list.js +75 -0
  23. package/dist/components/ai-chat/message-list.js.map +1 -0
  24. package/dist/components/ai-chat/style.d.ts +5 -0
  25. package/dist/components/ai-chat/style.js +6 -0
  26. package/dist/components/ai-chat/style2.css +416 -0
  27. package/dist/components/ai-chat/tool-call.js +151 -0
  28. package/dist/components/ai-chat/tool-call.js.map +1 -0
  29. package/dist/components/index.d.ts +3 -0
  30. package/dist/index.d.ts +11 -0
  31. package/dist/index.js +5 -0
  32. package/dist/style.d.ts +1 -0
  33. package/dist/style.js +1 -0
  34. package/dist/tools/ask-question/ask-question.d.ts +45 -0
  35. package/dist/tools/ask-question/ask-question.js +65 -0
  36. package/dist/tools/ask-question/ask-question.js.map +1 -0
  37. package/dist/tools/ask-question/deferred.js +22 -0
  38. package/dist/tools/ask-question/deferred.js.map +1 -0
  39. package/dist/tools/ask-question/index.d.ts +2 -0
  40. package/dist/tools/index.d.ts +3 -0
  41. package/dist/types/ai-chat.d.ts +49 -0
  42. package/dist/types/index.d.ts +2 -0
  43. package/package.json +40 -0
  44. package/src/chat/__test__/use-chat.test.ts +246 -0
  45. package/src/chat/index.ts +3 -0
  46. package/src/chat/transports/index.ts +1 -0
  47. package/src/chat/transports/openai.ts +237 -0
  48. package/src/chat/types.ts +136 -0
  49. package/src/chat/use-chat.ts +245 -0
  50. package/src/components/ai-chat/__test__/ai-chat.test.ts +316 -0
  51. package/src/components/ai-chat/ai-chat.vue +67 -0
  52. package/src/components/ai-chat/ask-question.vue +232 -0
  53. package/src/components/ai-chat/chat-input.vue +127 -0
  54. package/src/components/ai-chat/di.ts +15 -0
  55. package/src/components/ai-chat/index.ts +1 -0
  56. package/src/components/ai-chat/message-item.vue +120 -0
  57. package/src/components/ai-chat/message-list.vue +81 -0
  58. package/src/components/ai-chat/style.scss +439 -0
  59. package/src/components/ai-chat/style.ts +6 -0
  60. package/src/components/ai-chat/tool-call.vue +147 -0
  61. package/src/components/index.ts +1 -0
  62. package/src/env.d.ts +12 -0
  63. package/src/index.ts +4 -0
  64. package/src/style.ts +1 -0
  65. package/src/tools/ask-question/__test__/ask-question.test.ts +198 -0
  66. package/src/tools/ask-question/ask-question.ts +114 -0
  67. package/src/tools/ask-question/deferred.ts +27 -0
  68. package/src/tools/ask-question/index.ts +1 -0
  69. package/src/tools/index.ts +1 -0
  70. package/src/types/ai-chat.ts +56 -0
  71. package/src/types/index.ts +1 -0
@@ -0,0 +1,439 @@
1
+ @use 'pkg:@veltra/styles/mixins' as m;
2
+ @use 'pkg:@veltra/styles/functions' as fn;
3
+ @use 'pkg:@veltra/styles/vars';
4
+
5
+ $root-name: ai-chat;
6
+
7
+ @include m.b($root-name) {
8
+ display: flex;
9
+ flex-direction: column;
10
+ height: 100%;
11
+ min-height: 0;
12
+ font-size: fn.use-var(font-size-main, default);
13
+ color: fn.use-var(text-color, main);
14
+
15
+ @include m.e(list) {
16
+ flex: 1;
17
+ min-height: 0;
18
+ }
19
+
20
+ @include m.e(welcome) {
21
+ height: 100%;
22
+ @include m.flex(flex, center, center);
23
+ color: fn.use-var(text-color, assist);
24
+ padding: fn.use-var(gap, default);
25
+ text-align: center;
26
+ }
27
+
28
+ @include m.e(message) {
29
+ padding: fn.use-var(gap, small) fn.use-var(gap, default);
30
+
31
+ @include m.m(user) {
32
+ display: flex;
33
+ justify-content: flex-end;
34
+ }
35
+ }
36
+
37
+ @include m.e(message-bubble) {
38
+ max-width: 80%;
39
+ padding: fn.use-var(gap, small) fn.use-var(gap, default);
40
+ border-radius: fn.use-var(radius, large);
41
+ background-color: fn.use-var(color, primary, 'light-9');
42
+ color: fn.use-var(text-color, main);
43
+ overflow: hidden;
44
+ }
45
+
46
+ @include m.e(message-text) {
47
+ white-space: pre-wrap;
48
+ word-break: break-word;
49
+ }
50
+
51
+ @include m.e(message-attachments) {
52
+ display: flex;
53
+ flex-wrap: wrap;
54
+ gap: fn.use-var(gap, small);
55
+ margin-bottom: fn.use-var(gap, small);
56
+ }
57
+
58
+ @include m.e(message-attachment) {
59
+ max-width: 160px;
60
+ max-height: 160px;
61
+ border-radius: fn.use-var(radius, default);
62
+ display: block;
63
+ }
64
+
65
+ @include m.e(message-content) {
66
+ line-height: 1.7;
67
+ }
68
+
69
+ @include m.e(message-status) {
70
+ margin-top: fn.use-var(gap, small);
71
+ color: fn.use-var(text-color, assist);
72
+ font-size: fn.use-var(font-size-main, small);
73
+ }
74
+
75
+ @include m.e(message-actions) {
76
+ margin-top: fn.use-var(gap, small);
77
+ color: fn.use-var(text-color, assist);
78
+ cursor: pointer;
79
+ display: inline-flex;
80
+
81
+ &:hover {
82
+ color: fn.use-var(color, primary);
83
+ }
84
+ }
85
+
86
+ // 思考过程
87
+ @include m.e(reasoning) {
88
+ margin-bottom: fn.use-var(gap, small);
89
+ color: fn.use-var(text-color, assist);
90
+
91
+ @include m.is(expanded) {
92
+ @include m.bem($root-name, 'reasoning-chevron') {
93
+ transform: rotate(180deg);
94
+ }
95
+ }
96
+ }
97
+
98
+ @include m.e(reasoning-header) {
99
+ @include m.flex(flex, flex-start, center);
100
+ gap: fn.use-var(gap, small);
101
+ cursor: pointer;
102
+ user-select: none;
103
+ font-size: fn.use-var(font-size-main, small);
104
+ }
105
+
106
+ @include m.e(reasoning-chevron) {
107
+ transition: transform 0.2s;
108
+ }
109
+
110
+ @include m.e(reasoning-content) {
111
+ margin-top: fn.use-var(gap, small);
112
+ padding-left: fn.use-var(gap, default);
113
+ border-left: fn.use-var(border-muted);
114
+ white-space: pre-wrap;
115
+ word-break: break-word;
116
+ font-size: fn.use-var(font-size-main, small);
117
+ line-height: 1.6;
118
+ }
119
+
120
+ // 工具调用卡片
121
+ @include m.e(tool-call) {
122
+ margin: fn.use-var(gap, small) 0;
123
+ }
124
+
125
+ @include m.e(tool-call-status) {
126
+ flex-shrink: 0;
127
+
128
+ @include m.is(loading) {
129
+ color: fn.use-var(color, primary);
130
+ animation: u-ai-chat-rotate 1s linear infinite;
131
+ }
132
+
133
+ @include m.is(warning) {
134
+ color: fn.use-var(color, warning);
135
+ }
136
+
137
+ @include m.is(success) {
138
+ color: fn.use-var(color, success);
139
+ }
140
+
141
+ @include m.is(danger) {
142
+ color: fn.use-var(color, danger);
143
+ }
144
+ }
145
+
146
+ @include m.e(tool-call-name) {
147
+ font-family: fn.use-var(font-family, code);
148
+ font-size: fn.use-var(font-size-main, small);
149
+ color: fn.use-var(text-color, title);
150
+ flex-shrink: 0;
151
+ }
152
+
153
+ @include m.e(tool-call-summary) {
154
+ flex: 1;
155
+ min-width: 0;
156
+ @include m.ellipsis;
157
+ color: fn.use-var(text-color, assist);
158
+ font-size: fn.use-var(font-size-main, small);
159
+ }
160
+
161
+ @include m.e(tool-call-confirm) {
162
+ flex-shrink: 0;
163
+ @include m.flex(flex, flex-start, center);
164
+ gap: fn.use-var(gap, small);
165
+ }
166
+
167
+ @include m.e(tool-call-section) {
168
+ font-size: fn.use-var(font-size-main, small);
169
+ color: fn.use-var(text-color, assist);
170
+ margin: fn.use-var(gap, small) 0;
171
+ }
172
+
173
+ @include m.e(tool-call-code) {
174
+ margin: 0;
175
+ padding: fn.use-var(gap, small);
176
+ border-radius: fn.use-var(radius, default);
177
+ background-color: fn.use-var(bg-color, bottom);
178
+ font-family: fn.use-var(font-family, code);
179
+ font-size: fn.use-var(font-size-main, small);
180
+ white-space: pre-wrap;
181
+ word-break: break-all;
182
+ max-height: 240px;
183
+ overflow: auto;
184
+ }
185
+
186
+ // 提问工具(分页向导)
187
+ @include m.e(ask-question) {
188
+ display: flex;
189
+ flex-direction: column;
190
+ gap: fn.use-var(gap, small);
191
+ font-size: fn.use-var(font-size-main, small);
192
+ }
193
+
194
+ @include m.e(ask-question-progress) {
195
+ @include m.flex(flex, space-between, center);
196
+ color: fn.use-var(text-color, assist);
197
+ }
198
+
199
+ @include m.e(ask-question-dots) {
200
+ @include m.flex(flex, flex-start, center);
201
+ gap: 6px;
202
+ }
203
+
204
+ @include m.e(ask-question-dot) {
205
+ width: 6px;
206
+ height: 6px;
207
+ border-radius: 50%;
208
+ background-color: fn.use-var(text-color, placeholder);
209
+ cursor: pointer;
210
+ transition:
211
+ background-color 0.2s,
212
+ box-shadow 0.2s;
213
+
214
+ @include m.is(answered) {
215
+ background-color: fn.use-var(color, primary);
216
+ }
217
+
218
+ @include m.is(current) {
219
+ box-shadow: 0 0 0 3px fn.use-var(color, primary, 'light-7');
220
+ }
221
+ }
222
+
223
+ @include m.e(ask-question-q) {
224
+ @include m.flex(flex, flex-start, center);
225
+ gap: fn.use-var(gap, small);
226
+ color: fn.use-var(text-color, title);
227
+ font-weight: 500;
228
+ line-height: 1.5;
229
+ }
230
+
231
+ @include m.e(ask-question-no) {
232
+ flex-shrink: 0;
233
+ width: 18px;
234
+ height: 18px;
235
+ border-radius: 50%;
236
+ @include m.flex(flex, center, center);
237
+ background-color: fn.use-var(color, primary, 'light-9');
238
+ color: fn.use-var(color, primary);
239
+ font-size: 11px;
240
+ }
241
+
242
+ @include m.e(ask-question-options) {
243
+ display: flex;
244
+ flex-direction: column;
245
+ gap: fn.use-var(gap, small);
246
+ }
247
+
248
+ @include m.e(ask-question-opt) {
249
+ @include m.flex(flex, flex-start, center);
250
+ gap: fn.use-var(gap, small);
251
+ padding: fn.use-var(gap, small) fn.use-var(gap, default);
252
+ border: fn.use-var(border-muted);
253
+ border-radius: fn.use-var(radius, default);
254
+ background: none;
255
+ cursor: pointer;
256
+ text-align: left;
257
+ color: fn.use-var(text-color, main);
258
+ font-size: inherit;
259
+ font-family: inherit;
260
+ line-height: 1.5;
261
+ transition:
262
+ border-color 0.2s,
263
+ background-color 0.2s;
264
+
265
+ &:hover {
266
+ border-color: fn.use-var(color, primary);
267
+ }
268
+
269
+ @include m.is(selected) {
270
+ border-color: fn.use-var(color, primary);
271
+ background-color: fn.use-var(color, primary, 'light-9');
272
+ }
273
+ }
274
+
275
+ @include m.e(ask-question-glyph) {
276
+ flex-shrink: 0;
277
+ position: relative;
278
+ width: 14px;
279
+ height: 14px;
280
+ border-radius: 50%;
281
+ border: fn.use-var(border-muted);
282
+ transition: border-color 0.2s;
283
+
284
+ @include m.is(selected) {
285
+ border-color: fn.use-var(color, primary);
286
+
287
+ &::after {
288
+ content: '';
289
+ position: absolute;
290
+ inset: 3px;
291
+ border-radius: 50%;
292
+ background-color: fn.use-var(color, primary);
293
+ }
294
+ }
295
+ }
296
+
297
+ @include m.e(ask-question-actions) {
298
+ @include m.flex(flex, flex-end, center);
299
+ gap: fn.use-var(gap, small);
300
+ }
301
+
302
+ @include m.e(ask-question-result) {
303
+ display: flex;
304
+ flex-direction: column;
305
+ gap: 4px;
306
+ }
307
+
308
+ @include m.e(ask-question-result-q) {
309
+ @include m.flex(flex, flex-start, center);
310
+ gap: 6px;
311
+ color: fn.use-var(text-color, assist);
312
+ }
313
+
314
+ @include m.e(ask-question-result-no) {
315
+ flex-shrink: 0;
316
+ width: 16px;
317
+ height: 16px;
318
+ border-radius: 50%;
319
+ @include m.flex(flex, center, center);
320
+ background-color: fn.use-var(color, primary, 'light-9');
321
+ color: fn.use-var(color, primary);
322
+ font-size: 11px;
323
+ }
324
+
325
+ @include m.e(ask-question-result-a) {
326
+ color: fn.use-var(text-color, main);
327
+ line-height: 1.5;
328
+ }
329
+
330
+ @include m.e(ask-question-chip) {
331
+ display: inline-block;
332
+ padding: 2px fn.use-var(gap, small);
333
+ border-radius: fn.use-var(radius, default);
334
+ background-color: fn.use-var(color, primary, 'light-9');
335
+ color: fn.use-var(color, primary);
336
+ }
337
+
338
+ @include m.e(ask-question-status) {
339
+ color: fn.use-var(text-color, assist);
340
+ }
341
+
342
+ // 输入区
343
+ @include m.e(input-area) {
344
+ flex-shrink: 0;
345
+ margin: fn.use-var(gap, default);
346
+ margin-top: fn.use-var(gap, small);
347
+ border: fn.use-var(border-muted);
348
+ border-radius: fn.use-var(radius, large);
349
+ background-color: fn.use-var(bg-color, top);
350
+ padding: fn.use-var(gap, small);
351
+
352
+ &:focus-within {
353
+ border-color: fn.use-var(color, primary);
354
+ }
355
+ }
356
+
357
+ @include m.e(input) {
358
+ width: 100%;
359
+ border: none;
360
+ outline: none;
361
+ resize: none;
362
+ background: transparent;
363
+ color: fn.use-var(text-color, main);
364
+ font-size: fn.use-var(font-size-main, default);
365
+ font-family: inherit;
366
+ line-height: 1.6;
367
+ padding: fn.use-var(gap, small);
368
+ box-sizing: border-box;
369
+
370
+ &::placeholder {
371
+ color: fn.use-var(text-color, placeholder);
372
+ }
373
+ }
374
+
375
+ @include m.e(input-toolbar) {
376
+ @include m.flex(flex, space-between, center);
377
+ padding: 0 fn.use-var(gap, small);
378
+ }
379
+
380
+ @include m.e(input-attach) {
381
+ color: fn.use-var(text-color, assist);
382
+ cursor: pointer;
383
+ font-size: 18px;
384
+
385
+ &:hover {
386
+ color: fn.use-var(color, primary);
387
+ }
388
+ }
389
+
390
+ @include m.e(input-stop) {
391
+ display: block;
392
+ width: 10px;
393
+ height: 10px;
394
+ border-radius: 2px;
395
+ background-color: currentColor;
396
+ }
397
+
398
+ @include m.e(input-attachments) {
399
+ display: flex;
400
+ flex-wrap: wrap;
401
+ gap: fn.use-var(gap, small);
402
+ padding: fn.use-var(gap, small);
403
+ }
404
+
405
+ @include m.e(input-attachment) {
406
+ position: relative;
407
+ width: 48px;
408
+ height: 48px;
409
+ border-radius: fn.use-var(radius, default);
410
+ overflow: hidden;
411
+ border: fn.use-var(border-muted);
412
+
413
+ img {
414
+ width: 100%;
415
+ height: 100%;
416
+ object-fit: cover;
417
+ display: block;
418
+ }
419
+ }
420
+
421
+ @include m.e(input-attachment-remove) {
422
+ position: absolute;
423
+ top: 0;
424
+ right: 0;
425
+ background-color: fn.color-a(shadow, 52, color);
426
+ color: fn.use-var(text-color, white);
427
+ cursor: pointer;
428
+ font-size: 12px;
429
+ }
430
+ }
431
+
432
+ @keyframes u-ai-chat-rotate {
433
+ from {
434
+ transform: rotate(0deg);
435
+ }
436
+ to {
437
+ transform: rotate(360deg);
438
+ }
439
+ }
@@ -0,0 +1,6 @@
1
+ import '@veltra/desktop/components/button/style'
2
+ import '@veltra/desktop/components/icon/style'
3
+ import '@veltra/desktop/components/input/style'
4
+ import '@veltra/desktop/components/scroll/style'
5
+ import 'markstream-vue/index.css'
6
+ import './style.scss'
@@ -0,0 +1,147 @@
1
+ <template>
2
+ <div :class="[cls.e('tool-call'), bem.is(toolCall.status), bem.is('expanded', isExpanded)]">
3
+ <UCollapseItem v-model="isExpanded" @change="markUserToggled">
4
+ <template #title>
5
+ <UIcon :class="[cls.e('tool-call-status'), statusClass]">
6
+ <component :is="headerIcon" />
7
+ </UIcon>
8
+ <span :class="cls.e('tool-call-name')">{{ tool?.label ?? toolCall.name }}</span>
9
+ <span v-if="summary" :class="cls.e('tool-call-summary')">{{ summary }}</span>
10
+
11
+ <span
12
+ v-if="toolCall.status === 'awaiting-confirm'"
13
+ :class="cls.e('tool-call-confirm')"
14
+ @click.stop
15
+ >
16
+ <UButton size="small" type="primary" @click="emit('respond', true)">允许</UButton>
17
+ <UButton size="small" text @click="emit('respond', false)">拒绝</UButton>
18
+ </span>
19
+ </template>
20
+
21
+ <!-- 自定义渲染:工具定义的 render 优先于 tool-<name> 插槽,均替换整个 body -->
22
+ <component :is="tool?.render" v-if="tool?.render" :tool-call="toolCall" />
23
+ <component :is="slotBody" v-else-if="slotBody && hasResult" />
24
+ <template v-else>
25
+ <template v-if="prettyArguments">
26
+ <div :class="cls.e('tool-call-section')">参数</div>
27
+ <pre :class="cls.e('tool-call-code')">{{ prettyArguments }}</pre>
28
+ </template>
29
+
30
+ <template v-if="hasResult">
31
+ <div :class="cls.e('tool-call-section')">
32
+ {{ toolCall.status === 'error' ? '错误' : '结果' }}
33
+ </div>
34
+ <pre :class="cls.e('tool-call-code')">{{ displayResult }}</pre>
35
+ </template>
36
+ </template>
37
+ </UCollapseItem>
38
+ </div>
39
+ </template>
40
+
41
+ <script lang="ts" setup>
42
+ import { UButton, UCollapseItem, UIcon } from '@veltra/desktop'
43
+ import '@veltra/desktop/components/collapse/style'
44
+ import { CircleCheckFilled, CircleClose, Close, Loading, WarningFilled } from '@veltra/icons/normal'
45
+ import { bem } from '@veltra/utils'
46
+ import { computed, inject, ref, watch, type Component } from 'vue'
47
+
48
+ import type { ChatToolCall } from '../../chat/types'
49
+ import { AiChatDIKey } from './di'
50
+
51
+ defineOptions({ name: 'UAiChatToolCall' })
52
+
53
+ const props = defineProps<{ toolCall: ChatToolCall }>()
54
+
55
+ const emit = defineEmits<{ (e: 'respond', approved: boolean): void }>()
56
+
57
+ const di = inject(AiChatDIKey)
58
+ const cls = di?.cls ?? bem('ai-chat')
59
+
60
+ /** 当前调用对应的工具定义(解析 icon/label/render/autoCollapse) */
61
+ const tool = computed(() => di?.tools.value[props.toolCall.name])
62
+
63
+ const isExpanded = ref(false)
64
+ /** 用户手动切换过后,不再随 status 自动展开/折叠 */
65
+ let userToggled: boolean = false
66
+
67
+ function markUserToggled() {
68
+ userToggled = true
69
+ }
70
+
71
+ /** 完成后是否自动折叠:缺省有 render 时不折叠,否则折叠 */
72
+ const autoCollapse = computed(() => tool.value?.autoCollapse ?? !tool.value?.render)
73
+
74
+ watch(
75
+ () => props.toolCall.status,
76
+ (status) => {
77
+ if (userToggled) return
78
+ if (status === 'running' || status === 'awaiting-confirm') {
79
+ isExpanded.value = true
80
+ } else {
81
+ isExpanded.value = !autoCollapse.value
82
+ }
83
+ },
84
+ { immediate: true }
85
+ )
86
+
87
+ const STATUS_META: Record<string, { icon: Component; class: string }> = {
88
+ pending: { icon: Loading, class: 'is-loading' },
89
+ running: { icon: Loading, class: 'is-loading' },
90
+ 'awaiting-confirm': { icon: WarningFilled, class: 'is-warning' },
91
+ success: { icon: CircleCheckFilled, class: 'is-success' },
92
+ error: { icon: CircleClose, class: 'is-danger' },
93
+ rejected: { icon: Close, class: 'is-danger' }
94
+ }
95
+
96
+ const statusIcon = computed(() => STATUS_META[props.toolCall.status]?.icon ?? Loading)
97
+ const statusClass = computed(() => STATUS_META[props.toolCall.status]?.class ?? '')
98
+
99
+ /** 头部图标:工具自定义图标优先,缺省用状态图标 */
100
+ const headerIcon = computed(() => tool.value?.icon ?? statusIcon.value)
101
+
102
+ const parsedArguments = computed<Record<string, unknown> | null>(() => {
103
+ if (!props.toolCall.arguments) return null
104
+ try {
105
+ return JSON.parse(props.toolCall.arguments)
106
+ } catch {
107
+ return null
108
+ }
109
+ })
110
+
111
+ /** 折叠态参数摘要:取第一个字符串参数值 */
112
+ const summary = computed(() => {
113
+ const args = parsedArguments.value
114
+ if (!args) return ''
115
+ for (const value of Object.values(args)) {
116
+ if (typeof value === 'string' && value) {
117
+ return value.length > 40 ? `${value.slice(0, 40)}…` : value
118
+ }
119
+ }
120
+ return ''
121
+ })
122
+
123
+ const prettyArguments = computed(() => {
124
+ const args = parsedArguments.value
125
+ if (args) return JSON.stringify(args, null, 2)
126
+ return props.toolCall.arguments || ''
127
+ })
128
+
129
+ const hasResult = computed(() => {
130
+ return props.toolCall.result != null || props.toolCall.error != null
131
+ })
132
+
133
+ const displayResult = computed(() => {
134
+ const raw = props.toolCall.error ?? props.toolCall.result ?? ''
135
+ try {
136
+ return JSON.stringify(JSON.parse(raw), null, 2)
137
+ } catch {
138
+ return raw
139
+ }
140
+ })
141
+
142
+ /** 使用方通过 tool-<name> 插槽自定义该工具的展示(有结果时替换整个 body) */
143
+ const slotBody = computed(() => {
144
+ const slot = di?.slots[`tool-${props.toolCall.name}`]
145
+ return slot ? () => slot({ toolCall: props.toolCall }) : undefined
146
+ })
147
+ </script>
@@ -0,0 +1 @@
1
+ export * from './ai-chat'
package/src/env.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ declare module '*.vue' {
2
+ import type { DefineComponent } from 'vue'
3
+ const component: DefineComponent<object, object, any>
4
+ export default component
5
+ }
6
+
7
+ declare module '*.scss' {}
8
+
9
+ declare module '*.css' {}
10
+
11
+ // 跨包样式副作用子路径(veltra-dev → src/* 无扩展名,TS 不做扩展探测)
12
+ declare module '@veltra/desktop/components/*/style' {}
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ export * from './chat'
2
+ export * from './components'
3
+ export * from './tools'
4
+ export type * from './types'
package/src/style.ts ADDED
@@ -0,0 +1 @@
1
+ import './components/ai-chat/style'