@veltra/ai 2.1.9 → 2.1.11
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.
- package/dist/chat/use-chat.d.ts +15 -0
- package/dist/chat/use-chat.js +6 -0
- package/dist/chat/use-chat.js.map +1 -1
- package/dist/components/ai-chat/ai-chat.js +3 -1
- package/dist/components/ai-chat/ai-chat.js.map +1 -1
- package/dist/components/ai-chat/chat-input.js +155 -77
- package/dist/components/ai-chat/chat-input.js.map +1 -1
- package/dist/components/ai-chat/format-duration.js +13 -0
- package/dist/components/ai-chat/format-duration.js.map +1 -0
- package/dist/components/ai-chat/message-item.js +50 -31
- package/dist/components/ai-chat/message-item.js.map +1 -1
- package/dist/components/ai-chat/message-list.js +43 -8
- package/dist/components/ai-chat/message-list.js.map +1 -1
- package/dist/components/ai-chat/style.js +1 -0
- package/dist/components/ai-chat/style2.css +89 -18
- package/dist/components/ai-chat/tool-call.js +18 -11
- package/dist/components/ai-chat/tool-call.js.map +1 -1
- package/dist/index.d.ts +1 -3
- package/dist/index.js +1 -2
- package/dist/providers/types.d.ts +2 -0
- package/dist/style.js +0 -1
- package/dist/tools/ask-question/ask-question.js +2 -1
- package/dist/tools/ask-question/ask-question.js.map +1 -1
- package/dist/types/ai-chat.d.ts +2 -1
- package/dist/types/index.d.ts +1 -2
- package/package.json +1 -1
- package/src/chat/use-chat.ts +6 -0
- package/src/components/ai-chat/__test__/ai-chat.test.ts +131 -24
- package/src/components/ai-chat/ai-chat.vue +2 -0
- package/src/components/ai-chat/chat-input.vue +81 -13
- package/src/components/ai-chat/format-duration.ts +8 -0
- package/src/components/ai-chat/message-item.vue +24 -16
- package/src/components/ai-chat/message-list.vue +56 -5
- package/src/components/ai-chat/style.scss +101 -18
- package/src/components/ai-chat/style.ts +2 -0
- package/src/components/ai-chat/tool-call.vue +27 -9
- package/src/components/index.ts +0 -1
- package/src/providers/types.ts +2 -0
- package/src/style.ts +0 -1
- package/src/tools/ask-question/__test__/ask-question.test.ts +20 -2
- package/src/tools/ask-question/ask-question.ts +2 -1
- package/src/types/ai-chat.ts +2 -1
- package/src/types/index.ts +0 -1
- package/dist/components/ai-orb/style.d.ts +0 -1
- package/dist/components/ai-orb/style.js +0 -1
- package/dist/types/ai-orb.d.ts +0 -26
- package/src/components/ai-orb/index.ts +0 -1
- package/src/components/ai-orb/style.ts +0 -1
- /package/dist/components/ai-orb/{style2.css → style.css} +0 -0
|
@@ -169,13 +169,20 @@
|
|
|
169
169
|
position: relative;
|
|
170
170
|
flex: 1;
|
|
171
171
|
min-height: 0;
|
|
172
|
-
width: 100
|
|
173
|
-
|
|
172
|
+
width: calc(100% + var(--u-gap-default) * 2);
|
|
173
|
+
margin: 0 calc(var(--u-gap-default) * -1);
|
|
174
174
|
display: flex;
|
|
175
175
|
flex-direction: column;
|
|
176
176
|
padding: var(--u-gap-default) 0;
|
|
177
177
|
box-sizing: border-box;
|
|
178
178
|
}
|
|
179
|
+
.u-ai-chat__list-content {
|
|
180
|
+
width: 100%;
|
|
181
|
+
max-width: calc(800px + var(--u-gap-default) * 2);
|
|
182
|
+
margin: 0 auto;
|
|
183
|
+
padding: 0 var(--u-gap-default);
|
|
184
|
+
box-sizing: border-box;
|
|
185
|
+
}
|
|
179
186
|
.u-ai-chat__list {
|
|
180
187
|
flex: 1;
|
|
181
188
|
min-height: 0;
|
|
@@ -183,6 +190,8 @@
|
|
|
183
190
|
.u-ai-chat__welcome {
|
|
184
191
|
flex-shrink: 0;
|
|
185
192
|
width: 100%;
|
|
193
|
+
max-width: 800px;
|
|
194
|
+
margin: 0 auto;
|
|
186
195
|
height: 48px;
|
|
187
196
|
display: flex;
|
|
188
197
|
justify-content: flex-start;
|
|
@@ -271,7 +280,7 @@
|
|
|
271
280
|
align-items: center;
|
|
272
281
|
flex-wrap: nowrap;
|
|
273
282
|
gap: var(--u-gap-small);
|
|
274
|
-
padding: 0
|
|
283
|
+
padding: 0 0 var(--u-gap-small);
|
|
275
284
|
width: fit-content;
|
|
276
285
|
}
|
|
277
286
|
.u-ai-chat__working .u-ai-orb {
|
|
@@ -290,6 +299,11 @@
|
|
|
290
299
|
.u-ai-chat__working-text {
|
|
291
300
|
font-size: var(--u-font-size-main-small);
|
|
292
301
|
}
|
|
302
|
+
.u-ai-chat__working-time {
|
|
303
|
+
font-size: var(--u-font-size-main-small);
|
|
304
|
+
color: var(--u-text-color-assist);
|
|
305
|
+
font-variant-numeric: tabular-nums;
|
|
306
|
+
}
|
|
293
307
|
@media (prefers-reduced-motion: reduce) {
|
|
294
308
|
.u-ai-chat .u-ai-chat__welcome.is-enter .u-ai-orb,
|
|
295
309
|
.u-ai-chat .u-ai-chat__working .u-ai-orb {
|
|
@@ -302,21 +316,22 @@
|
|
|
302
316
|
}
|
|
303
317
|
.u-ai-chat__to-latest {
|
|
304
318
|
position: absolute;
|
|
305
|
-
|
|
319
|
+
bottom: var(--u-gap-small);
|
|
306
320
|
left: 50%;
|
|
307
321
|
transform: translateX(-50%);
|
|
308
322
|
z-index: 1;
|
|
323
|
+
width: 28px;
|
|
324
|
+
height: 28px;
|
|
325
|
+
padding: 0;
|
|
309
326
|
display: flex;
|
|
310
327
|
justify-content: center;
|
|
311
328
|
align-items: center;
|
|
312
329
|
flex-wrap: nowrap;
|
|
313
|
-
gap: 4px;
|
|
314
|
-
padding: 4px 12px;
|
|
315
330
|
border: none;
|
|
316
|
-
border-radius:
|
|
331
|
+
border-radius: 50%;
|
|
317
332
|
background-color: var(--u-bg-color-top);
|
|
318
333
|
color: var(--u-text-color-second);
|
|
319
|
-
font-size:
|
|
334
|
+
font-size: 14px;
|
|
320
335
|
box-shadow: 0 2px 8px var(--u-shadow-color-a-20);
|
|
321
336
|
cursor: pointer;
|
|
322
337
|
transition: color 0.15s ease;
|
|
@@ -332,11 +347,11 @@
|
|
|
332
347
|
}
|
|
333
348
|
.u-ai-chat__to-latest--fade-enter-from {
|
|
334
349
|
opacity: 0;
|
|
335
|
-
transform: translateX(-50%) translateY(
|
|
350
|
+
transform: translateX(-50%) translateY(4px);
|
|
336
351
|
}
|
|
337
352
|
.u-ai-chat__to-latest--fade-leave-to {
|
|
338
353
|
opacity: 0;
|
|
339
|
-
transform: translateX(-50%) translateY(
|
|
354
|
+
transform: translateX(-50%) translateY(4px);
|
|
340
355
|
}
|
|
341
356
|
.u-ai-chat__message {
|
|
342
357
|
padding: var(--u-gap-small) var(--u-gap-default);
|
|
@@ -410,6 +425,12 @@
|
|
|
410
425
|
flex-wrap: wrap;
|
|
411
426
|
gap: var(--u-gap-small);
|
|
412
427
|
}
|
|
428
|
+
.u-ai-chat__message-duration {
|
|
429
|
+
align-self: center;
|
|
430
|
+
color: var(--u-text-color-assist);
|
|
431
|
+
font-size: var(--u-font-size-main-small);
|
|
432
|
+
font-variant-numeric: tabular-nums;
|
|
433
|
+
}
|
|
413
434
|
.u-ai-chat__reasoning {
|
|
414
435
|
margin-bottom: 4px;
|
|
415
436
|
color: var(--u-text-color-second);
|
|
@@ -428,8 +449,18 @@
|
|
|
428
449
|
font-size: var(--u-font-size-main-small);
|
|
429
450
|
}
|
|
430
451
|
.u-ai-chat__reasoning-title {
|
|
452
|
+
flex-shrink: 0;
|
|
431
453
|
line-height: 1.5;
|
|
432
454
|
}
|
|
455
|
+
.u-ai-chat__reasoning-line {
|
|
456
|
+
flex: 1;
|
|
457
|
+
min-width: 0;
|
|
458
|
+
overflow: hidden;
|
|
459
|
+
text-overflow: ellipsis;
|
|
460
|
+
white-space: nowrap;
|
|
461
|
+
line-height: 1.5;
|
|
462
|
+
color: var(--u-text-color-assist);
|
|
463
|
+
}
|
|
433
464
|
.u-ai-chat__reasoning-chevron {
|
|
434
465
|
font-size: 12px;
|
|
435
466
|
transition: transform 0.2s;
|
|
@@ -1021,14 +1052,32 @@
|
|
|
1021
1052
|
.u-ai-chat__input-clear:hover {
|
|
1022
1053
|
color: var(--u-color-danger);
|
|
1023
1054
|
}
|
|
1024
|
-
.u-ai-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1055
|
+
.u-ai-chat__usage-trigger {
|
|
1056
|
+
display: inline-flex;
|
|
1057
|
+
justify-content: center;
|
|
1058
|
+
align-items: center;
|
|
1059
|
+
flex-wrap: nowrap;
|
|
1060
|
+
flex-shrink: 0;
|
|
1061
|
+
padding: 2px;
|
|
1062
|
+
border: none;
|
|
1063
|
+
border-radius: 50%;
|
|
1064
|
+
background: transparent;
|
|
1065
|
+
}
|
|
1066
|
+
.u-ai-chat__usage-ring {
|
|
1067
|
+
display: block;
|
|
1068
|
+
width: 14px;
|
|
1069
|
+
height: 14px;
|
|
1070
|
+
}
|
|
1071
|
+
.u-ai-chat__usage-ring-track {
|
|
1072
|
+
fill: none;
|
|
1073
|
+
stroke: var(--u-border-color);
|
|
1074
|
+
stroke-width: 5;
|
|
1075
|
+
}
|
|
1076
|
+
.u-ai-chat__usage-ring-value {
|
|
1077
|
+
fill: none;
|
|
1078
|
+
stroke: var(--u-color-primary);
|
|
1079
|
+
stroke-width: 5;
|
|
1080
|
+
transition: stroke-dasharray 0.3s ease;
|
|
1032
1081
|
}
|
|
1033
1082
|
.u-ai-chat__input-stop {
|
|
1034
1083
|
display: block;
|
|
@@ -1067,6 +1116,28 @@
|
|
|
1067
1116
|
font-size: 12px;
|
|
1068
1117
|
}
|
|
1069
1118
|
|
|
1119
|
+
.u-ai-chat__usage-panel {
|
|
1120
|
+
padding: 6px 4px;
|
|
1121
|
+
user-select: none;
|
|
1122
|
+
}
|
|
1123
|
+
.u-ai-chat__usage-panel .u-ai-chat__usage-row {
|
|
1124
|
+
display: flex;
|
|
1125
|
+
justify-content: space-between;
|
|
1126
|
+
align-items: center;
|
|
1127
|
+
flex-wrap: nowrap;
|
|
1128
|
+
gap: 24px;
|
|
1129
|
+
padding: 3px 8px;
|
|
1130
|
+
font-size: var(--u-font-size-main-small);
|
|
1131
|
+
line-height: 1.6;
|
|
1132
|
+
}
|
|
1133
|
+
.u-ai-chat__usage-panel .u-ai-chat__usage-label {
|
|
1134
|
+
color: var(--u-text-color-assist);
|
|
1135
|
+
}
|
|
1136
|
+
.u-ai-chat__usage-panel .u-ai-chat__usage-value {
|
|
1137
|
+
color: var(--u-text-color-main);
|
|
1138
|
+
font-variant-numeric: tabular-nums;
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1070
1141
|
.u-ai-chat__model-panel {
|
|
1071
1142
|
padding: 4px;
|
|
1072
1143
|
user-select: none;
|
|
@@ -40,12 +40,25 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
40
40
|
};
|
|
41
41
|
/** 面板工具:render 展示在右侧侧边面板,卡片 body 仅保留「查看面板」入口 */
|
|
42
42
|
const isPanelTool = computed(() => tool.value?.renderTo === "panel");
|
|
43
|
-
/**
|
|
44
|
-
const
|
|
43
|
+
/** 「UI 即答复」的终结工具(session 元信息无 terminal,恒为 false) */
|
|
44
|
+
const isAnswerTool = computed(() => {
|
|
45
|
+
const t = tool.value;
|
|
46
|
+
return !!(t && "terminal" in t && t.terminal);
|
|
47
|
+
});
|
|
48
|
+
/** 内联自定义 body(render / tool-<name> 插槽):进行中可能是提问表单等交互 UI,需展开可见 */
|
|
49
|
+
const hasInlineBody = computed(() => !isPanelTool.value && !!(tool.value?.render || di?.slots[`tool-${props.toolCall.name}`]));
|
|
50
|
+
/** 完成后是否自动折叠:缺省一律折叠;「UI 即答复」的终结工具缺省保持展开 */
|
|
51
|
+
const autoCollapse = computed(() => tool.value?.autoCollapse ?? !isAnswerTool.value);
|
|
52
|
+
/** 需要用户交互/展示的进行中状态:待确认(看参数再决策)、有内联自定义 body(提问表单等) */
|
|
53
|
+
const needsInteraction = computed(() => {
|
|
54
|
+
if (props.toolCall.status === "awaiting-confirm") return true;
|
|
55
|
+
return (props.toolCall.status === "pending" || props.toolCall.status === "running") && hasInlineBody.value;
|
|
56
|
+
});
|
|
57
|
+
const isSettledStatus = (status) => status === "success" || status === "error" || status === "rejected";
|
|
45
58
|
watch(() => props.toolCall.status, (status) => {
|
|
46
59
|
if (userToggled) return;
|
|
47
|
-
if (status
|
|
48
|
-
else isExpanded.value =
|
|
60
|
+
if (isSettledStatus(status)) isExpanded.value = !autoCollapse.value;
|
|
61
|
+
else isExpanded.value = needsInteraction.value;
|
|
49
62
|
}, { immediate: true });
|
|
50
63
|
const STATUS_CLASS = {
|
|
51
64
|
pending: "is-active",
|
|
@@ -61,13 +74,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
61
74
|
return props.toolCall.status === "pending" || props.toolCall.status === "running";
|
|
62
75
|
});
|
|
63
76
|
/** 终态(成功/失败/拒绝) */
|
|
64
|
-
const isSettled = computed(() =>
|
|
65
|
-
return [
|
|
66
|
-
"success",
|
|
67
|
-
"error",
|
|
68
|
-
"rejected"
|
|
69
|
-
].includes(props.toolCall.status);
|
|
70
|
-
});
|
|
77
|
+
const isSettled = computed(() => isSettledStatus(props.toolCall.status));
|
|
71
78
|
/**
|
|
72
79
|
* 折叠后卸载内容 DOM:仅终态且非面板工具时启用——
|
|
73
80
|
* 进行中/待确认要保留内容状态(确认按钮、表单),面板工具的 body 是「查看面板」入口必须可达
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-call.js","names":[],"sources":["../../../src/components/ai-chat/tool-call.vue"],"sourcesContent":["<template>\n <UCollapseItem\n v-model=\"isExpanded\"\n :class=\"[cls.e('tool-call'), bem.is(toolCall.status)]\"\n :destroy-on-collapse=\"destroyOnCollapse\"\n @change=\"markUserToggled\"\n >\n <template #header>\n <UIcon :class=\"[cls.e('tool-call-status'), statusClass]\">\n <component :is=\"headerIcon\" />\n </UIcon>\n <span :class=\"[cls.e('tool-call-name'), isActive && 'u-shine']\">\n {{ tool?.label ?? toolCall.name }}\n </span>\n <span v-if=\"summary\" :class=\"cls.e('tool-call-summary')\">{{ summary }}</span>\n\n <span\n v-if=\"toolCall.status === 'awaiting-confirm'\"\n :class=\"cls.e('tool-call-confirm')\"\n @click.stop\n @keydown.enter.stop\n @keydown.space.stop\n >\n <UButton size=\"small\" type=\"primary\" @click=\"emit('respond', true)\">允许</UButton>\n <UButton size=\"small\" text @click=\"emit('respond', false)\">拒绝</UButton>\n </span>\n </template>\n\n <!-- 面板工具:render 展示在右侧侧边面板,卡片 body 仅提供查看入口 -->\n <div v-if=\"isPanelTool\" :class=\"cls.e('tool-call-panel-entry')\">\n <span :class=\"cls.e('tool-call-panel-hint')\">内容展示在右侧面板</span>\n <UButton size=\"small\" text type=\"primary\" @click=\"di?.openPanel(toolCall.id)\">\n 查看面板\n </UButton>\n </div>\n <!-- 自定义渲染:meta.render / tool-<name> 插槽优先于通用工具视图 -->\n <component :is=\"tool.render\" v-else-if=\"tool?.render\" :tool-call=\"toolCall\" />\n <component :is=\"slotBody\" v-else-if=\"slotBody && hasResult\" />\n <template v-else>\n <ToolJson v-if=\"prettyArguments\" title=\"参数\" :text=\"prettyArguments\" />\n <ToolJson v-if=\"hasResult\" :title=\"resultTitle\" :text=\"displayResult\" />\n <ToolJson v-if=\"prettyView\" title=\"视图\" :text=\"prettyView\" />\n </template>\n </UCollapseItem>\n</template>\n\n<script lang=\"ts\" setup>\nimport { UButton, UCollapseItem, UIcon } from '@veltra/desktop'\nimport { bem } from '@veltra/utils'\nimport { computed, inject, ref, watch } from 'vue'\n\nimport type { ChatToolCall } from '../../chat/types'\nimport { AiChatDIKey } from './di'\nimport { resolveToolIcon } from './tool-icons'\nimport ToolJson from './tool-json.vue'\n\ndefineOptions({ name: 'UAiChatToolCall' })\n\nconst props = defineProps<{ toolCall: ChatToolCall }>()\n\nconst emit = defineEmits<{ (e: 'respond', approved: boolean): void }>()\n\nconst di = inject(AiChatDIKey)\nconst cls = di?.cls ?? bem('ai-chat')\n\n/** 把未知值格式成可读 JSON,循环引用等异常时退回 String,不得 throw */\nfunction formatUnknown(value: unknown): string {\n if (value == null) return ''\n if (typeof value === 'string') {\n try {\n return JSON.stringify(JSON.parse(value), null, 2)\n } catch {\n return value\n }\n }\n try {\n return JSON.stringify(value, null, 2) ?? String(value)\n } catch {\n return String(value)\n }\n}\n\n/** 当前调用对应的工具定义或渲染元信息(session 下忽略 execute / needsConfirm / terminal) */\nconst tool = computed(() => di?.tools.value[props.toolCall.name])\n\nconst isExpanded = ref(false)\n/** 用户手动切换过后,不再随 status 自动展开/折叠 */\nlet userToggled: boolean = false\n\n/** UCollapseItem 的 change 仅在用户点击头部时触发(程序化 v-model 变更不触发) */\nconst markUserToggled = () => {\n userToggled = true\n}\n\n/** 面板工具:render 展示在右侧侧边面板,卡片 body 仅保留「查看面板」入口 */\nconst isPanelTool = computed(() => tool.value?.renderTo === 'panel')\n\n/** 完成后是否自动折叠:缺省面板工具折叠;有 render 时不折叠,否则折叠 */\nconst autoCollapse = computed(\n () => tool.value?.autoCollapse ?? (isPanelTool.value || !tool.value?.render)\n)\n\nwatch(\n () => props.toolCall.status,\n (status) => {\n if (userToggled) return\n if (status === 'pending' || status === 'running' || status === 'awaiting-confirm') {\n isExpanded.value = true\n } else {\n isExpanded.value = !autoCollapse.value\n }\n },\n { immediate: true }\n)\n\nconst STATUS_CLASS: Record<string, string> = {\n pending: 'is-active',\n running: 'is-active',\n 'awaiting-confirm': 'is-warning',\n success: 'is-success',\n error: 'is-danger',\n rejected: 'is-danger'\n}\n\nconst statusClass = computed(() => STATUS_CLASS[props.toolCall.status] ?? '')\n\n/** 进行中(pending/running):工具名带扫光效果 */\nconst isActive = computed(() => {\n return props.toolCall.status === 'pending' || props.toolCall.status === 'running'\n})\n\n/** 终态(成功/失败/拒绝) */\nconst isSettled = computed(() => {\n return ['success', 'error', 'rejected'].includes(props.toolCall.status)\n})\n\n/**\n * 折叠后卸载内容 DOM:仅终态且非面板工具时启用——\n * 进行中/待确认要保留内容状态(确认按钮、表单),面板工具的 body 是「查看面板」入口必须可达\n */\nconst destroyOnCollapse = computed(() => isSettled.value && !isPanelTool.value)\n\n/** 头部图标:meta.icon > 宿主覆盖 > 内置名称规则 > 兜底;状态点走 status class */\nconst headerIcon = computed(\n () => tool.value?.icon ?? resolveToolIcon(props.toolCall.name, di?.toolIcons?.value)\n)\n\nconst parsedArguments = computed<Record<string, unknown> | null>(() => {\n if (!props.toolCall.arguments) return null\n try {\n return JSON.parse(props.toolCall.arguments)\n } catch {\n return null\n }\n})\n\n/** 折叠态参数摘要:取第一个字符串参数值 */\nconst summary = computed(() => {\n const args = parsedArguments.value\n if (!args) return ''\n for (const value of Object.values(args)) {\n if (typeof value === 'string' && value) {\n return value.length > 40 ? `${value.slice(0, 40)}…` : value\n }\n }\n return ''\n})\n\nconst prettyArguments = computed(() => {\n const args = parsedArguments.value\n if (args) return JSON.stringify(args, null, 2)\n return props.toolCall.arguments || ''\n})\n\nconst hasResult = computed(() => {\n return props.toolCall.result != null || props.toolCall.error != null\n})\n\nconst resultTitle = computed(() => (props.toolCall.status === 'error' ? '错误' : '结果'))\n\nconst displayResult = computed(() => formatUnknown(props.toolCall.error ?? props.toolCall.result))\n\n/** 服务端下发的 view,格式化为 JSON;不得丢弃、不得 throw */\nconst prettyView = computed(() => {\n if (props.toolCall.view == null) return ''\n return formatUnknown(props.toolCall.view)\n})\n\n/** 使用方通过 tool-<name> 插槽自定义该工具的展示(有结果时替换整个 body) */\nconst slotBody = computed(() => {\n const slot = di?.slots[`tool-${props.toolCall.name}`]\n return slot ? () => slot({ toolCall: props.toolCall }) : undefined\n})\n</script>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"tool-call.js","names":[],"sources":["../../../src/components/ai-chat/tool-call.vue"],"sourcesContent":["<template>\n <UCollapseItem\n v-model=\"isExpanded\"\n :class=\"[cls.e('tool-call'), bem.is(toolCall.status)]\"\n :destroy-on-collapse=\"destroyOnCollapse\"\n @change=\"markUserToggled\"\n >\n <template #header>\n <UIcon :class=\"[cls.e('tool-call-status'), statusClass]\">\n <component :is=\"headerIcon\" />\n </UIcon>\n <span :class=\"[cls.e('tool-call-name'), isActive && 'u-shine']\">\n {{ tool?.label ?? toolCall.name }}\n </span>\n <span v-if=\"summary\" :class=\"cls.e('tool-call-summary')\">{{ summary }}</span>\n\n <span\n v-if=\"toolCall.status === 'awaiting-confirm'\"\n :class=\"cls.e('tool-call-confirm')\"\n @click.stop\n @keydown.enter.stop\n @keydown.space.stop\n >\n <UButton size=\"small\" type=\"primary\" @click=\"emit('respond', true)\">允许</UButton>\n <UButton size=\"small\" text @click=\"emit('respond', false)\">拒绝</UButton>\n </span>\n </template>\n\n <!-- 面板工具:render 展示在右侧侧边面板,卡片 body 仅提供查看入口 -->\n <div v-if=\"isPanelTool\" :class=\"cls.e('tool-call-panel-entry')\">\n <span :class=\"cls.e('tool-call-panel-hint')\">内容展示在右侧面板</span>\n <UButton size=\"small\" text type=\"primary\" @click=\"di?.openPanel(toolCall.id)\">\n 查看面板\n </UButton>\n </div>\n <!-- 自定义渲染:meta.render / tool-<name> 插槽优先于通用工具视图 -->\n <component :is=\"tool.render\" v-else-if=\"tool?.render\" :tool-call=\"toolCall\" />\n <component :is=\"slotBody\" v-else-if=\"slotBody && hasResult\" />\n <template v-else>\n <ToolJson v-if=\"prettyArguments\" title=\"参数\" :text=\"prettyArguments\" />\n <ToolJson v-if=\"hasResult\" :title=\"resultTitle\" :text=\"displayResult\" />\n <ToolJson v-if=\"prettyView\" title=\"视图\" :text=\"prettyView\" />\n </template>\n </UCollapseItem>\n</template>\n\n<script lang=\"ts\" setup>\nimport { UButton, UCollapseItem, UIcon } from '@veltra/desktop'\nimport { bem } from '@veltra/utils'\nimport { computed, inject, ref, watch } from 'vue'\n\nimport type { ChatToolCall } from '../../chat/types'\nimport { AiChatDIKey } from './di'\nimport { resolveToolIcon } from './tool-icons'\nimport ToolJson from './tool-json.vue'\n\ndefineOptions({ name: 'UAiChatToolCall' })\n\nconst props = defineProps<{ toolCall: ChatToolCall }>()\n\nconst emit = defineEmits<{ (e: 'respond', approved: boolean): void }>()\n\nconst di = inject(AiChatDIKey)\nconst cls = di?.cls ?? bem('ai-chat')\n\n/** 把未知值格式成可读 JSON,循环引用等异常时退回 String,不得 throw */\nfunction formatUnknown(value: unknown): string {\n if (value == null) return ''\n if (typeof value === 'string') {\n try {\n return JSON.stringify(JSON.parse(value), null, 2)\n } catch {\n return value\n }\n }\n try {\n return JSON.stringify(value, null, 2) ?? String(value)\n } catch {\n return String(value)\n }\n}\n\n/** 当前调用对应的工具定义或渲染元信息(session 下忽略 execute / needsConfirm / terminal) */\nconst tool = computed(() => di?.tools.value[props.toolCall.name])\n\nconst isExpanded = ref(false)\n/** 用户手动切换过后,不再随 status 自动展开/折叠 */\nlet userToggled: boolean = false\n\n/** UCollapseItem 的 change 仅在用户点击头部时触发(程序化 v-model 变更不触发) */\nconst markUserToggled = () => {\n userToggled = true\n}\n\n/** 面板工具:render 展示在右侧侧边面板,卡片 body 仅保留「查看面板」入口 */\nconst isPanelTool = computed(() => tool.value?.renderTo === 'panel')\n\n/** 「UI 即答复」的终结工具(session 元信息无 terminal,恒为 false) */\nconst isAnswerTool = computed(() => {\n const t = tool.value\n return !!(t && 'terminal' in t && t.terminal)\n})\n\n/** 内联自定义 body(render / tool-<name> 插槽):进行中可能是提问表单等交互 UI,需展开可见 */\nconst hasInlineBody = computed(\n () => !isPanelTool.value && !!(tool.value?.render || di?.slots[`tool-${props.toolCall.name}`])\n)\n\n/** 完成后是否自动折叠:缺省一律折叠;「UI 即答复」的终结工具缺省保持展开 */\nconst autoCollapse = computed(() => tool.value?.autoCollapse ?? !isAnswerTool.value)\n\n/** 需要用户交互/展示的进行中状态:待确认(看参数再决策)、有内联自定义 body(提问表单等) */\nconst needsInteraction = computed(() => {\n if (props.toolCall.status === 'awaiting-confirm') return true\n const active = props.toolCall.status === 'pending' || props.toolCall.status === 'running'\n return active && hasInlineBody.value\n})\n\nconst isSettledStatus = (status: ChatToolCall['status']) =>\n status === 'success' || status === 'error' || status === 'rejected'\n\n// 默认折叠(含进行中);仅交互/答复类自动展开,用户手动切换过后不再自动变化\nwatch(\n () => props.toolCall.status,\n (status) => {\n if (userToggled) return\n if (isSettledStatus(status)) {\n isExpanded.value = !autoCollapse.value\n } else {\n isExpanded.value = needsInteraction.value\n }\n },\n { immediate: true }\n)\n\nconst STATUS_CLASS: Record<string, string> = {\n pending: 'is-active',\n running: 'is-active',\n 'awaiting-confirm': 'is-warning',\n success: 'is-success',\n error: 'is-danger',\n rejected: 'is-danger'\n}\n\nconst statusClass = computed(() => STATUS_CLASS[props.toolCall.status] ?? '')\n\n/** 进行中(pending/running):工具名带扫光效果 */\nconst isActive = computed(() => {\n return props.toolCall.status === 'pending' || props.toolCall.status === 'running'\n})\n\n/** 终态(成功/失败/拒绝) */\nconst isSettled = computed(() => isSettledStatus(props.toolCall.status))\n\n/**\n * 折叠后卸载内容 DOM:仅终态且非面板工具时启用——\n * 进行中/待确认要保留内容状态(确认按钮、表单),面板工具的 body 是「查看面板」入口必须可达\n */\nconst destroyOnCollapse = computed(() => isSettled.value && !isPanelTool.value)\n\n/** 头部图标:meta.icon > 宿主覆盖 > 内置名称规则 > 兜底;状态点走 status class */\nconst headerIcon = computed(\n () => tool.value?.icon ?? resolveToolIcon(props.toolCall.name, di?.toolIcons?.value)\n)\n\nconst parsedArguments = computed<Record<string, unknown> | null>(() => {\n if (!props.toolCall.arguments) return null\n try {\n return JSON.parse(props.toolCall.arguments)\n } catch {\n return null\n }\n})\n\n/** 折叠态参数摘要:取第一个字符串参数值 */\nconst summary = computed(() => {\n const args = parsedArguments.value\n if (!args) return ''\n for (const value of Object.values(args)) {\n if (typeof value === 'string' && value) {\n return value.length > 40 ? `${value.slice(0, 40)}…` : value\n }\n }\n return ''\n})\n\nconst prettyArguments = computed(() => {\n const args = parsedArguments.value\n if (args) return JSON.stringify(args, null, 2)\n return props.toolCall.arguments || ''\n})\n\nconst hasResult = computed(() => {\n return props.toolCall.result != null || props.toolCall.error != null\n})\n\nconst resultTitle = computed(() => (props.toolCall.status === 'error' ? '错误' : '结果'))\n\nconst displayResult = computed(() => formatUnknown(props.toolCall.error ?? props.toolCall.result))\n\n/** 服务端下发的 view,格式化为 JSON;不得丢弃、不得 throw */\nconst prettyView = computed(() => {\n if (props.toolCall.view == null) return ''\n return formatUnknown(props.toolCall.view)\n})\n\n/** 使用方通过 tool-<name> 插槽自定义该工具的展示(有结果时替换整个 body) */\nconst slotBody = computed(() => {\n const slot = di?.slots[`tool-${props.toolCall.name}`]\n return slot ? () => slot({ toolCall: props.toolCall }) : undefined\n})\n</script>\n"],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import _sfc_main$1 from "./components/ai-orb/ai-orb.js";
|
|
2
1
|
import _sfc_main from "./components/ai-chat/ai-chat.js";
|
|
3
2
|
import { ChatAttachment, ChatJob, ChatJobStatus, ChatMessage, ChatQueuedMessage, ChatTokenUsage, ChatTool, ChatToolCall, ChatToolContext, ChatToolMeta, ChatToolRenderProps, ChatTransport, ChatTransportHandlers, ChatTransportRequest, ToolCallStatus } from "./chat/types.js";
|
|
4
3
|
import { AskQuestionAnswer, AskQuestionArgs, AskQuestionItem, AskQuestionResult } from "./tools/ask-question/ask-question.js";
|
|
@@ -6,10 +5,9 @@ import "./tools/index.js";
|
|
|
6
5
|
import { ChatSessionAdapter, ChatSessionEvent, ChatSessionTransport, createServerTransport, isServerTransport } from "./chat/session.js";
|
|
7
6
|
import { ChatModel, ChatModelOption, ChatProvider, ChatReasoningLevel } from "./providers/types.js";
|
|
8
7
|
import { AiChatEmits, AiChatExposed, AiChatProps, _AiChatExposed } from "./types/ai-chat.js";
|
|
9
|
-
import { AiOrbEmits, AiOrbExposed, AiOrbProps, AiOrbReaction, AiOrbStatus } from "./types/ai-orb.js";
|
|
10
8
|
import "./types/index.js";
|
|
11
9
|
import { ChatFoldState, ChatPendingApproval, createFoldState, foldSessionEvent } from "./chat/fold.js";
|
|
12
10
|
import { UseChatOptions, useChat } from "./chat/use-chat.js";
|
|
13
11
|
import { OpenAITransport, OpenAITransportOptions, createOpenAITransport } from "./chat/transports/openai.js";
|
|
14
12
|
import "./chat/index.js";
|
|
15
|
-
export { type AiChatEmits, type AiChatExposed, type AiChatProps, type
|
|
13
|
+
export { type AiChatEmits, type AiChatExposed, type AiChatProps, type AskQuestionAnswer, type AskQuestionArgs, type AskQuestionItem, type AskQuestionResult, ChatAttachment, ChatFoldState, ChatJob, ChatJobStatus, ChatMessage, type ChatModel, type ChatModelOption, ChatPendingApproval, type ChatProvider, ChatQueuedMessage, type ChatReasoningLevel, ChatSessionAdapter, ChatSessionEvent, ChatSessionTransport, ChatTokenUsage, ChatTool, ChatToolCall, ChatToolContext, ChatToolMeta, ChatToolRenderProps, ChatTransport, ChatTransportHandlers, ChatTransportRequest, OpenAITransport, OpenAITransportOptions, ToolCallStatus, _sfc_main as UAiChat, UseChatOptions, type _AiChatExposed, createFoldState, createOpenAITransport, createServerTransport, foldSessionEvent, isServerTransport, useChat };
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,5 @@ import { createServerTransport, isServerTransport } from "./chat/session.js";
|
|
|
2
2
|
import { createFoldState, foldSessionEvent } from "./chat/fold.js";
|
|
3
3
|
import { useChat } from "./chat/use-chat.js";
|
|
4
4
|
import { createOpenAITransport } from "./chat/transports/openai.js";
|
|
5
|
-
import _sfc_main$1 from "./components/ai-orb/ai-orb.js";
|
|
6
5
|
import _sfc_main from "./components/ai-chat/ai-chat.js";
|
|
7
|
-
export { _sfc_main as UAiChat,
|
|
6
|
+
export { _sfc_main as UAiChat, createFoldState, createOpenAITransport, createServerTransport, foldSessionEvent, isServerTransport, useChat };
|
|
@@ -18,6 +18,8 @@ export interface ChatModel {
|
|
|
18
18
|
reasoningLevels?: ChatReasoningLevel[];
|
|
19
19
|
/** 默认推理等级(须落在 reasoningLevels 内) */
|
|
20
20
|
defaultReasoningLevel?: string;
|
|
21
|
+
/** 上下文窗口上限(token)。配置后输入栏用量环按「最近一次请求总 token / 上限」显示占比 */
|
|
22
|
+
contextWindow?: number;
|
|
21
23
|
}
|
|
22
24
|
/** 模型服务商配置(内嵌 models) */
|
|
23
25
|
export interface ChatProvider {
|
package/dist/style.js
CHANGED
|
@@ -81,7 +81,8 @@ const AskQuestionRender = defineComponent({
|
|
|
81
81
|
});
|
|
82
82
|
/**
|
|
83
83
|
* 创建内置提问工具:execute 挂起等待用户在 UI 中作答,提交后结果回灌模型。
|
|
84
|
-
*
|
|
84
|
+
* 工具卡片由内联提问表单渲染(多题分页导航,提交后展示问答摘要);
|
|
85
|
+
* 进行中因有内联 render 自动展开,终态默认折叠,展开后可查看问答摘要。
|
|
85
86
|
*/
|
|
86
87
|
function createAskQuestionTool(options = {}) {
|
|
87
88
|
const { name = "askQuestion", description = DEFAULT_DESCRIPTION, label = "提问", icon = QuestionFilled } = options;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ask-question.js","names":["AskQuestion"],"sources":["../../../src/tools/ask-question/ask-question.ts"],"sourcesContent":["import { QuestionFilled } from '@veltra/icons/normal'\nimport { computed, defineComponent, h, type Component, type PropType } from 'vue'\n\nimport type { ChatTool, ChatToolCall } from '../../chat/types'\nimport AskQuestion from '../../components/ai-chat/ask-question.vue'\nimport { deferAskQuestion, resolveAskQuestion, takeAskQuestionDeferred } from './deferred'\n\n/** 单个提问项 */\nexport interface AskQuestionItem {\n /** 问题文案 */\n question: string\n /** 预设选项(单选);缺省为纯文本题 */\n options?: string[]\n /** 自定义输入占位文案 */\n placeholder?: string\n}\n\n/** 一条问答结果 */\nexport interface AskQuestionAnswer {\n /** 问题文案 */\n question: string\n /** 用户回答(选中的选项或自定义输入) */\n answer: string\n}\n\n/** 提问工具参数(模型输出) */\nexport interface AskQuestionArgs {\n questions: AskQuestionItem[]\n}\n\n/** 提问工具结果(序列化后回灌模型,渲染层据此展示问答摘要) */\nexport interface AskQuestionResult {\n answers: AskQuestionAnswer[]\n}\n\nexport interface CreateAskQuestionToolOptions {\n /** 工具名(传给模型),默认 askQuestion */\n name?: string\n /** 工具描述(传给模型) */\n description?: string\n /** 工具显示名,默认「提问」 */\n label?: string\n /** 工具图标,默认 QuestionFilled */\n icon?: Component\n}\n\nconst DEFAULT_DESCRIPTION =\n '当需求不明确或存在歧义时,向用户提问以澄清。一次可提 1-4 个关键问题;' +\n '每个问题可提供若干预设选项,用户可选择选项或自定义输入。用户作答后再继续生成。'\n\n/** 提问参数 JSON Schema(原样传给模型) */\nconst QUESTIONS_SCHEMA: Record<string, unknown> = {\n type: 'object',\n properties: {\n questions: {\n type: 'array',\n description: '问题列表',\n minItems: 1,\n maxItems: 4,\n items: {\n type: 'object',\n properties: {\n question: { type: 'string', description: '问题文案' },\n options: {\n type: 'array',\n description: '预设选项(单选);缺省为纯文本题',\n items: { type: 'string' }\n },\n placeholder: { type: 'string', description: '自定义输入占位文案' }\n },\n required: ['question']\n }\n }\n },\n required: ['questions']\n}\n\nfunction parseQuestions(toolCall: ChatToolCall): AskQuestionItem[] {\n try {\n const args = JSON.parse(toolCall.arguments || '{}') as Partial<AskQuestionArgs>\n return Array.isArray(args.questions) ? args.questions : []\n } catch {\n return []\n }\n}\n\nfunction parseAnswers(toolCall: ChatToolCall): AskQuestionAnswer[] | undefined {\n if (toolCall.status !== 'success' || !toolCall.result) return undefined\n try {\n const result = JSON.parse(toolCall.result) as Partial<AskQuestionResult>\n return Array.isArray(result.answers) ? result.answers : undefined\n } catch {\n return undefined\n }\n}\n\nfunction statusError(toolCall: ChatToolCall): string | undefined {\n if (toolCall.status !== 'error') return undefined\n const err = toolCall.error ?? ''\n if (err.includes('Abort') || err.includes('取消')) return '已取消'\n return err || '提问失败'\n}\n\n/** 把 toolCall 转成提问表单的 questions / onSubmit;提交仍走 resolveAskQuestion */\nconst AskQuestionRender = defineComponent({\n name: 'AskQuestionRender',\n props: { toolCall: { type: Object as PropType<ChatToolCall>, required: true } },\n setup(props) {\n const questions = computed(() => parseQuestions(props.toolCall))\n const answers = computed(() => parseAnswers(props.toolCall))\n const error = computed(() => statusError(props.toolCall))\n const onSubmit = (value: AskQuestionAnswer[] | AskQuestionResult) => {\n resolveAskQuestion(props.toolCall.id, {\n answers: Array.isArray(value) ? value : value.answers\n })\n }\n return () =>\n h(AskQuestion, {\n questions: questions.value,\n answers: answers.value,\n error: error.value,\n onSubmit\n })\n }\n})\n\n/**\n * 创建内置提问工具:execute 挂起等待用户在 UI 中作答,提交后结果回灌模型。\n *
|
|
1
|
+
{"version":3,"file":"ask-question.js","names":["AskQuestion"],"sources":["../../../src/tools/ask-question/ask-question.ts"],"sourcesContent":["import { QuestionFilled } from '@veltra/icons/normal'\nimport { computed, defineComponent, h, type Component, type PropType } from 'vue'\n\nimport type { ChatTool, ChatToolCall } from '../../chat/types'\nimport AskQuestion from '../../components/ai-chat/ask-question.vue'\nimport { deferAskQuestion, resolveAskQuestion, takeAskQuestionDeferred } from './deferred'\n\n/** 单个提问项 */\nexport interface AskQuestionItem {\n /** 问题文案 */\n question: string\n /** 预设选项(单选);缺省为纯文本题 */\n options?: string[]\n /** 自定义输入占位文案 */\n placeholder?: string\n}\n\n/** 一条问答结果 */\nexport interface AskQuestionAnswer {\n /** 问题文案 */\n question: string\n /** 用户回答(选中的选项或自定义输入) */\n answer: string\n}\n\n/** 提问工具参数(模型输出) */\nexport interface AskQuestionArgs {\n questions: AskQuestionItem[]\n}\n\n/** 提问工具结果(序列化后回灌模型,渲染层据此展示问答摘要) */\nexport interface AskQuestionResult {\n answers: AskQuestionAnswer[]\n}\n\nexport interface CreateAskQuestionToolOptions {\n /** 工具名(传给模型),默认 askQuestion */\n name?: string\n /** 工具描述(传给模型) */\n description?: string\n /** 工具显示名,默认「提问」 */\n label?: string\n /** 工具图标,默认 QuestionFilled */\n icon?: Component\n}\n\nconst DEFAULT_DESCRIPTION =\n '当需求不明确或存在歧义时,向用户提问以澄清。一次可提 1-4 个关键问题;' +\n '每个问题可提供若干预设选项,用户可选择选项或自定义输入。用户作答后再继续生成。'\n\n/** 提问参数 JSON Schema(原样传给模型) */\nconst QUESTIONS_SCHEMA: Record<string, unknown> = {\n type: 'object',\n properties: {\n questions: {\n type: 'array',\n description: '问题列表',\n minItems: 1,\n maxItems: 4,\n items: {\n type: 'object',\n properties: {\n question: { type: 'string', description: '问题文案' },\n options: {\n type: 'array',\n description: '预设选项(单选);缺省为纯文本题',\n items: { type: 'string' }\n },\n placeholder: { type: 'string', description: '自定义输入占位文案' }\n },\n required: ['question']\n }\n }\n },\n required: ['questions']\n}\n\nfunction parseQuestions(toolCall: ChatToolCall): AskQuestionItem[] {\n try {\n const args = JSON.parse(toolCall.arguments || '{}') as Partial<AskQuestionArgs>\n return Array.isArray(args.questions) ? args.questions : []\n } catch {\n return []\n }\n}\n\nfunction parseAnswers(toolCall: ChatToolCall): AskQuestionAnswer[] | undefined {\n if (toolCall.status !== 'success' || !toolCall.result) return undefined\n try {\n const result = JSON.parse(toolCall.result) as Partial<AskQuestionResult>\n return Array.isArray(result.answers) ? result.answers : undefined\n } catch {\n return undefined\n }\n}\n\nfunction statusError(toolCall: ChatToolCall): string | undefined {\n if (toolCall.status !== 'error') return undefined\n const err = toolCall.error ?? ''\n if (err.includes('Abort') || err.includes('取消')) return '已取消'\n return err || '提问失败'\n}\n\n/** 把 toolCall 转成提问表单的 questions / onSubmit;提交仍走 resolveAskQuestion */\nconst AskQuestionRender = defineComponent({\n name: 'AskQuestionRender',\n props: { toolCall: { type: Object as PropType<ChatToolCall>, required: true } },\n setup(props) {\n const questions = computed(() => parseQuestions(props.toolCall))\n const answers = computed(() => parseAnswers(props.toolCall))\n const error = computed(() => statusError(props.toolCall))\n const onSubmit = (value: AskQuestionAnswer[] | AskQuestionResult) => {\n resolveAskQuestion(props.toolCall.id, {\n answers: Array.isArray(value) ? value : value.answers\n })\n }\n return () =>\n h(AskQuestion, {\n questions: questions.value,\n answers: answers.value,\n error: error.value,\n onSubmit\n })\n }\n})\n\n/**\n * 创建内置提问工具:execute 挂起等待用户在 UI 中作答,提交后结果回灌模型。\n * 工具卡片由内联提问表单渲染(多题分页导航,提交后展示问答摘要);\n * 进行中因有内联 render 自动展开,终态默认折叠,展开后可查看问答摘要。\n */\nexport function createAskQuestionTool(\n options: CreateAskQuestionToolOptions = {}\n): ChatTool<AskQuestionArgs> {\n const {\n name = 'askQuestion',\n description = DEFAULT_DESCRIPTION,\n label = '提问',\n icon = QuestionFilled\n } = options\n\n return {\n name,\n description,\n parameters: QUESTIONS_SCHEMA,\n icon,\n label,\n render: AskQuestionRender,\n execute: (_args, { toolCall, signal }) =>\n new Promise<AskQuestionResult>((resolve, reject) => {\n deferAskQuestion(toolCall.id, { resolve, reject })\n signal.addEventListener(\n 'abort',\n () => {\n // 不依赖 signal.reason(各环境 DOMException 行为不一),统一拒绝为 AbortError\n const error = new Error('Aborted')\n error.name = 'AbortError'\n takeAskQuestionDeferred(toolCall.id)?.reject(error)\n },\n { once: true }\n )\n })\n }\n}\n"],"mappings":";;;;;AA8CA,MAAM,sBACJ;;AAIF,MAAM,mBAA4C;CAChD,MAAM;CACN,YAAY,EACV,WAAW;EACT,MAAM;EACN,aAAa;EACb,UAAU;EACV,UAAU;EACV,OAAO;GACL,MAAM;GACN,YAAY;IACV,UAAU;KAAE,MAAM;KAAU,aAAa;IAAO;IAChD,SAAS;KACP,MAAM;KACN,aAAa;KACb,OAAO,EAAE,MAAM,SAAS;IAC1B;IACA,aAAa;KAAE,MAAM;KAAU,aAAa;IAAY;GAC1D;GACA,UAAU,CAAC,UAAU;EACvB;CACF,EACF;CACA,UAAU,CAAC,WAAW;AACxB;AAEA,SAAS,eAAe,UAA2C;CACjE,IAAI;EACF,MAAM,OAAO,KAAK,MAAM,SAAS,aAAa,IAAI;EAClD,OAAO,MAAM,QAAQ,KAAK,SAAS,IAAI,KAAK,YAAY,CAAC;CAC3D,QAAQ;EACN,OAAO,CAAC;CACV;AACF;AAEA,SAAS,aAAa,UAAyD;CAC7E,IAAI,SAAS,WAAW,aAAa,CAAC,SAAS,QAAQ,OAAO,KAAA;CAC9D,IAAI;EACF,MAAM,SAAS,KAAK,MAAM,SAAS,MAAM;EACzC,OAAO,MAAM,QAAQ,OAAO,OAAO,IAAI,OAAO,UAAU,KAAA;CAC1D,QAAQ;EACN;CACF;AACF;AAEA,SAAS,YAAY,UAA4C;CAC/D,IAAI,SAAS,WAAW,SAAS,OAAO,KAAA;CACxC,MAAM,MAAM,SAAS,SAAS;CAC9B,IAAI,IAAI,SAAS,OAAO,KAAK,IAAI,SAAS,IAAI,GAAG,OAAO;CACxD,OAAO,OAAO;AAChB;;AAGA,MAAM,oBAAoB,gBAAgB;CACxC,MAAM;CACN,OAAO,EAAE,UAAU;EAAE,MAAM;EAAkC,UAAU;CAAK,EAAE;CAC9E,MAAM,OAAO;EACX,MAAM,YAAY,eAAe,eAAe,MAAM,QAAQ,CAAC;EAC/D,MAAM,UAAU,eAAe,aAAa,MAAM,QAAQ,CAAC;EAC3D,MAAM,QAAQ,eAAe,YAAY,MAAM,QAAQ,CAAC;EACxD,MAAM,YAAY,UAAmD;GACnE,mBAAmB,MAAM,SAAS,IAAI,EACpC,SAAS,MAAM,QAAQ,KAAK,IAAI,QAAQ,MAAM,QAChD,CAAC;EACH;EACA,aACE,EAAEA,WAAa;GACb,WAAW,UAAU;GACrB,SAAS,QAAQ;GACjB,OAAO,MAAM;GACb;EACF,CAAC;CACL;AACF,CAAC;;;;;;AAOD,SAAgB,sBACd,UAAwC,CAAC,GACd;CAC3B,MAAM,EACJ,OAAO,eACP,cAAc,qBACd,QAAQ,MACR,OAAO,mBACL;CAEJ,OAAO;EACL;EACA;EACA,YAAY;EACZ;EACA;EACA,QAAQ;EACR,UAAU,OAAO,EAAE,UAAU,aAC3B,IAAI,SAA4B,SAAS,WAAW;GAClD,iBAAiB,SAAS,IAAI;IAAE;IAAS;GAAO,CAAC;GACjD,OAAO,iBACL,eACM;IAEJ,MAAM,wBAAQ,IAAI,MAAM,SAAS;IACjC,MAAM,OAAO;IACb,wBAAwB,SAAS,EAAE,CAAC,EAAE,OAAO,KAAK;GACpD,GACA,EAAE,MAAM,KAAK,CACf;EACF,CAAC;CACL;AACF"}
|
package/dist/types/ai-chat.d.ts
CHANGED
|
@@ -42,7 +42,8 @@ export interface AiChatProps {
|
|
|
42
42
|
rendererProps?: Record<string, unknown>;
|
|
43
43
|
/**
|
|
44
44
|
* 是否展示 token 用量明细(缓存命中 / 未命中;缺字段不显示)。
|
|
45
|
-
* 默认 false
|
|
45
|
+
* 默认 false:用量环明细面板只列 总 / 输入 / 输出;true 时追加有数据的缓存项。
|
|
46
|
+
* 接口未返回 usage 时不展示用量环。
|
|
46
47
|
*/
|
|
47
48
|
tokenUsageDetail?: boolean;
|
|
48
49
|
/**
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,3 +1,2 @@
|
|
|
1
1
|
import { AiChatEmits, AiChatExposed, AiChatProps, _AiChatExposed } from "./ai-chat.js";
|
|
2
|
-
|
|
3
|
-
export { AiChatEmits, AiChatExposed, AiChatProps, AiOrbEmits, AiOrbExposed, AiOrbProps, AiOrbReaction, AiOrbStatus, _AiChatExposed };
|
|
2
|
+
export { AiChatEmits, AiChatExposed, AiChatProps, _AiChatExposed };
|
package/package.json
CHANGED
package/src/chat/use-chat.ts
CHANGED
|
@@ -60,6 +60,8 @@ export function useChat(options: UseChatOptions) {
|
|
|
60
60
|
const tokenUsage = ref<ChatTokenUsage | null>(null)
|
|
61
61
|
/** 最近一轮用户对话(含工具多轮请求)的 token;该轮无 usage 时为 null */
|
|
62
62
|
const lastTurnUsage = ref<ChatTokenUsage | null>(null)
|
|
63
|
+
/** 最近一次单次请求的 token(未跨轮累计),其 totalTokens 近似当前上下文占用 */
|
|
64
|
+
const lastRequestUsage = ref<ChatTokenUsage | null>(null)
|
|
63
65
|
|
|
64
66
|
/** session 下由 jobs/snapshot 整体替换;函数 transport 保持空数组 */
|
|
65
67
|
const jobs = ref<ChatJob[]>([])
|
|
@@ -344,6 +346,7 @@ export function useChat(options: UseChatOptions) {
|
|
|
344
346
|
emit('tool-call', toolCall)
|
|
345
347
|
},
|
|
346
348
|
onUsage: (usage) => {
|
|
349
|
+
lastRequestUsage.value = usage
|
|
347
350
|
lastTurnUsage.value = lastTurnUsage.value
|
|
348
351
|
? addTokenUsage(lastTurnUsage.value, usage)
|
|
349
352
|
: usage
|
|
@@ -462,6 +465,7 @@ export function useChat(options: UseChatOptions) {
|
|
|
462
465
|
if (session()) {
|
|
463
466
|
sessionRuntime.resetLocal()
|
|
464
467
|
lastTurnUsage.value = null
|
|
468
|
+
lastRequestUsage.value = null
|
|
465
469
|
return
|
|
466
470
|
}
|
|
467
471
|
abort()
|
|
@@ -469,6 +473,7 @@ export function useChat(options: UseChatOptions) {
|
|
|
469
473
|
messages.value = []
|
|
470
474
|
tokenUsage.value = null
|
|
471
475
|
lastTurnUsage.value = null
|
|
476
|
+
lastRequestUsage.value = null
|
|
472
477
|
snapshot()
|
|
473
478
|
}
|
|
474
479
|
|
|
@@ -481,6 +486,7 @@ export function useChat(options: UseChatOptions) {
|
|
|
481
486
|
jobs,
|
|
482
487
|
tokenUsage,
|
|
483
488
|
lastTurnUsage,
|
|
489
|
+
lastRequestUsage,
|
|
484
490
|
projections,
|
|
485
491
|
title,
|
|
486
492
|
pendingApprovals,
|