@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.
Files changed (49) hide show
  1. package/dist/chat/use-chat.d.ts +15 -0
  2. package/dist/chat/use-chat.js +6 -0
  3. package/dist/chat/use-chat.js.map +1 -1
  4. package/dist/components/ai-chat/ai-chat.js +3 -1
  5. package/dist/components/ai-chat/ai-chat.js.map +1 -1
  6. package/dist/components/ai-chat/chat-input.js +155 -77
  7. package/dist/components/ai-chat/chat-input.js.map +1 -1
  8. package/dist/components/ai-chat/format-duration.js +13 -0
  9. package/dist/components/ai-chat/format-duration.js.map +1 -0
  10. package/dist/components/ai-chat/message-item.js +50 -31
  11. package/dist/components/ai-chat/message-item.js.map +1 -1
  12. package/dist/components/ai-chat/message-list.js +43 -8
  13. package/dist/components/ai-chat/message-list.js.map +1 -1
  14. package/dist/components/ai-chat/style.js +1 -0
  15. package/dist/components/ai-chat/style2.css +89 -18
  16. package/dist/components/ai-chat/tool-call.js +18 -11
  17. package/dist/components/ai-chat/tool-call.js.map +1 -1
  18. package/dist/index.d.ts +1 -3
  19. package/dist/index.js +1 -2
  20. package/dist/providers/types.d.ts +2 -0
  21. package/dist/style.js +0 -1
  22. package/dist/tools/ask-question/ask-question.js +2 -1
  23. package/dist/tools/ask-question/ask-question.js.map +1 -1
  24. package/dist/types/ai-chat.d.ts +2 -1
  25. package/dist/types/index.d.ts +1 -2
  26. package/package.json +1 -1
  27. package/src/chat/use-chat.ts +6 -0
  28. package/src/components/ai-chat/__test__/ai-chat.test.ts +131 -24
  29. package/src/components/ai-chat/ai-chat.vue +2 -0
  30. package/src/components/ai-chat/chat-input.vue +81 -13
  31. package/src/components/ai-chat/format-duration.ts +8 -0
  32. package/src/components/ai-chat/message-item.vue +24 -16
  33. package/src/components/ai-chat/message-list.vue +56 -5
  34. package/src/components/ai-chat/style.scss +101 -18
  35. package/src/components/ai-chat/style.ts +2 -0
  36. package/src/components/ai-chat/tool-call.vue +27 -9
  37. package/src/components/index.ts +0 -1
  38. package/src/providers/types.ts +2 -0
  39. package/src/style.ts +0 -1
  40. package/src/tools/ask-question/__test__/ask-question.test.ts +20 -2
  41. package/src/tools/ask-question/ask-question.ts +2 -1
  42. package/src/types/ai-chat.ts +2 -1
  43. package/src/types/index.ts +0 -1
  44. package/dist/components/ai-orb/style.d.ts +0 -1
  45. package/dist/components/ai-orb/style.js +0 -1
  46. package/dist/types/ai-orb.d.ts +0 -26
  47. package/src/components/ai-orb/index.ts +0 -1
  48. package/src/components/ai-orb/style.ts +0 -1
  49. /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
- max-width: 800px;
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 var(--u-gap-default) var(--u-gap-small);
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
- top: var(--u-gap-small);
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: 999px;
331
+ border-radius: 50%;
317
332
  background-color: var(--u-bg-color-top);
318
333
  color: var(--u-text-color-second);
319
- font-size: var(--u-font-size-main-small);
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(-4px);
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(-4px);
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-chat__input-usage {
1025
- min-width: 0;
1026
- overflow: hidden;
1027
- text-overflow: ellipsis;
1028
- white-space: nowrap;
1029
- font-size: var(--u-font-size-main-small);
1030
- color: var(--u-text-color-assist);
1031
- line-height: 1.4;
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
- /** 完成后是否自动折叠:缺省面板工具折叠;有 render 时不折叠,否则折叠 */
44
- const autoCollapse = computed(() => tool.value?.autoCollapse ?? (isPanelTool.value || !tool.value?.render));
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 === "pending" || status === "running" || status === "awaiting-confirm") isExpanded.value = true;
48
- else isExpanded.value = !autoCollapse.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 AiOrbEmits, type AiOrbExposed, type AiOrbProps, type AiOrbReaction, type AiOrbStatus, 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, _sfc_main$1 as UAiOrb, UseChatOptions, type _AiChatExposed, createFoldState, createOpenAITransport, createServerTransport, foldSessionEvent, isServerTransport, useChat };
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, _sfc_main$1 as UAiOrb, createFoldState, createOpenAITransport, createServerTransport, foldSessionEvent, isServerTransport, useChat };
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
@@ -1,2 +1 @@
1
1
  import "./components/ai-chat/style.js";
2
- import "./components/ai-orb/style.js";
@@ -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 * 工具卡片由内联提问表单渲染(多题分页导航,提交后展示问答摘要,不自动折叠)。\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;;;;;AAMD,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"}
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"}
@@ -42,7 +42,8 @@ export interface AiChatProps {
42
42
  rendererProps?: Record<string, unknown>;
43
43
  /**
44
44
  * 是否展示 token 用量明细(缓存命中 / 未命中;缺字段不显示)。
45
- * 默认 false:仅在拿到 usage 时显示会话累计「总 token」。接口未返回 usage 时不展示。
45
+ * 默认 false:用量环明细面板只列 总 / 输入 / 输出;true 时追加有数据的缓存项。
46
+ * 接口未返回 usage 时不展示用量环。
46
47
  */
47
48
  tokenUsageDetail?: boolean;
48
49
  /**
@@ -1,3 +1,2 @@
1
1
  import { AiChatEmits, AiChatExposed, AiChatProps, _AiChatExposed } from "./ai-chat.js";
2
- import { AiOrbEmits, AiOrbExposed, AiOrbProps, AiOrbReaction, AiOrbStatus } from "./ai-orb.js";
3
- export { AiChatEmits, AiChatExposed, AiChatProps, AiOrbEmits, AiOrbExposed, AiOrbProps, AiOrbReaction, AiOrbStatus, _AiChatExposed };
2
+ export { AiChatEmits, AiChatExposed, AiChatProps, _AiChatExposed };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@veltra/ai",
3
- "version": "2.1.9",
3
+ "version": "2.1.11",
4
4
  "homepage": "https://github.com/cabinet-fe/ultra-ui#readme",
5
5
  "bugs": {
6
6
  "url": "https://github.com/cabinet-fe/ultra-ui/issues"
@@ -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,