@veltra/ai 2.2.1 → 2.2.3

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 (41) hide show
  1. package/dist/chat/fold.js +14 -2
  2. package/dist/chat/fold.js.map +1 -1
  3. package/dist/chat/session.d.ts +13 -6
  4. package/dist/chat/session.js.map +1 -1
  5. package/dist/chat/transports/openai.d.ts +2 -1
  6. package/dist/chat/transports/openai.js +20 -213
  7. package/dist/chat/transports/openai.js.map +1 -1
  8. package/dist/chat/use-chat-session.js +3 -13
  9. package/dist/chat/use-chat-session.js.map +1 -1
  10. package/dist/components/ai-chat/ai-chat.js.map +1 -1
  11. package/dist/components/ai-chat/approval-banner.js.map +1 -1
  12. package/dist/components/ai-chat/ask-question.js.map +1 -1
  13. package/dist/components/ai-chat/chat-input.js.map +1 -1
  14. package/dist/components/ai-chat/job-bar.js.map +1 -1
  15. package/dist/components/ai-chat/message-item.js.map +1 -1
  16. package/dist/components/ai-chat/message-list.js.map +1 -1
  17. package/dist/components/ai-chat/model-picker.js.map +1 -1
  18. package/dist/components/ai-chat/queue-list.js.map +1 -1
  19. package/dist/components/ai-chat/reasoning-picker.js.map +1 -1
  20. package/dist/components/ai-chat/side-panel.js.map +1 -1
  21. package/dist/components/ai-chat/tool-call.js.map +1 -1
  22. package/dist/components/ai-chat/tool-json.js.map +1 -1
  23. package/dist/components/ai-chat/turn-process.js.map +1 -1
  24. package/dist/components/ai-orb/ai-orb.js.map +1 -1
  25. package/package.json +3 -3
  26. package/src/chat/__test__/bedrock-harness-sim.test.ts +467 -0
  27. package/src/chat/__test__/fixtures/bedrock/harness.ts +735 -0
  28. package/src/chat/__test__/fixtures/bedrock/http.ts +117 -0
  29. package/src/chat/__test__/fixtures/bedrock/types.ts +15 -0
  30. package/src/chat/__test__/fold.test.ts +26 -1
  31. package/src/chat/__test__/session.test.ts +1 -1
  32. package/src/chat/__test__/use-chat.test.ts +6 -3
  33. package/src/chat/fold.ts +16 -1
  34. package/src/chat/session.ts +13 -6
  35. package/src/chat/transports/__test__/openai.test.ts +1 -1
  36. package/src/chat/transports/openai.ts +25 -288
  37. package/src/chat/use-chat-session.ts +4 -5
  38. package/src/components/ai-chat/__test__/ai-chat.test.ts +1 -1
  39. package/src/components/ai-chat/__test__/tool-icons.test.ts +1 -1
  40. package/src/components/ai-orb/__test__/ai-orb.test.ts +1 -1
  41. package/src/tools/ask-question/__test__/ask-question.test.ts +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"reasoning-picker.js","names":[],"sources":["../../../src/components/ai-chat/reasoning-picker.vue"],"sourcesContent":["<template>\n <UDropdown\n v-if=\"levels.length > 1\"\n ref=\"dropdownRef\"\n trigger=\"click\"\n direction=\"top\"\n alignment=\"end\"\n width=\"auto\"\n min-width=\"140px\"\n :content-class=\"cls.e('reasoning-panel')\"\n >\n <template #trigger>\n <button type=\"button\" :class=\"cls.e('reasoning-trigger')\" title=\"推理等级\">\n <span :class=\"cls.e('reasoning-trigger-label')\">{{ currentLabel }}</span>\n <UIcon :class=\"cls.e('reasoning-trigger-arrow')\">\n <ArrowDown />\n </UIcon>\n </button>\n </template>\n\n <template #content>\n <div :class=\"cls.e('reasoning-list')\">\n <div\n v-for=\"level in levels\"\n :key=\"level.value\"\n :class=\"[cls.e('reasoning-option'), bem.is('active', level.value === reasoningLevel)]\"\n @click=\"handleSelect(level.value)\"\n >\n <span>{{ level.label }}</span>\n <UIcon v-if=\"level.value === reasoningLevel\" :class=\"cls.e('reasoning-check')\">\n <Check />\n </UIcon>\n </div>\n </div>\n </template>\n </UDropdown>\n</template>\n\n<script lang=\"ts\" setup>\nimport { UDropdown, UIcon } from '@veltra/desktop'\nimport { ArrowDown, Check } from '@veltra/icons/normal'\nimport { bem } from '@veltra/utils'\nimport { computed, inject, useTemplateRef } from 'vue'\n\nimport type { ChatReasoningLevel } from '../../providers'\nimport { AiChatDIKey } from './di'\n\ndefineOptions({ name: 'UAiChatReasoningPicker' })\n\nconst { levels } = defineProps<{\n /** 当前模型的推理等级列表;不足两档时没有切换意义,不渲染 */\n levels: ChatReasoningLevel[]\n}>()\n\nconst reasoningLevel = defineModel<string | undefined>('reasoningLevel')\n\nconst di = inject(AiChatDIKey)\nconst cls = di?.cls ?? bem('ai-chat')\n\nconst dropdownRef = useTemplateRef('dropdownRef')\n\nconst currentLabel = computed(() => {\n return levels.find((level) => level.value === reasoningLevel.value)?.label ?? '推理'\n})\n\nconst handleSelect = (value: string) => {\n reasoningLevel.value = value\n dropdownRef.value?.close()\n}\n</script>\n"],"mappings":";;;;;;;;;;;;;;;;EAsDA,MAAM,iBAAiB,SAA+B,SAAC,gBAAgB;EAGvE,MAAM,MADK,OAAO,WACN,CAAA,EAAI,OAAO,IAAI,SAAS;EAEpC,MAAM,cAAc,eAAe,aAAa;EAEhD,MAAM,eAAe,eAAe;GAClC,OAAO,QAAA,OAAO,MAAM,UAAU,MAAM,UAAU,eAAe,KAAK,CAAC,EAAE,SAAS;EAChF,CAAC;EAED,MAAM,gBAAgB,UAAkB;GACtC,eAAe,QAAQ;GACvB,YAAY,OAAO,MAAM;EAC3B;;GAlEU,OAAA,QAAA,OAAO,SAAM,KADrB,UAAA,GAAA,YAkCY,MAAA,SAAA,GAAA;;IAhCN,SAAA;IAAJ,KAAI;IACJ,SAAQ;IACR,WAAU;IACV,WAAU;IACV,OAAM;IACN,aAAU;IACT,iBAAe,MAAA,GAAA,CAAG,CAAC,EAAC,iBAAA;;IAEV,SAAO,cAMP,CALT,mBAKS,UAAA;KALD,MAAK;KAAU,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,mBAAA,CAAA;KAAuB,OAAM;IAC9D,GAAA,CAAA,mBAAyE,QAAA,EAAlE,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,yBAAA,CAAA,EAAgC,GAAA,gBAAA,aAAA,KAAY,GAAA,CAAA,GAC/D,YAEQ,MAAA,KAAA,GAAA,EAFA,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,yBAAA,CAAA,EAAA,GAAA;KAClB,SAAA,cAAa,CAAb,YAAa,MAAA,SAAA,CAAA,CAAA,CAAA;;;IAKR,SAAO,cAaV,CAZN,mBAYM,OAAA,EAZA,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,gBAAA,CAAA,EAAA,GAChB,EAAA,UAAA,IAAA,GAAA,mBAUM,UAAA,MAAA,WATY,QAAA,SAAT,UAAK;KADd,OAAA,UAAA,GAAA,mBAUM,OAAA;MARH,KAAK,MAAM;MACX,OAAK,eAAA,CAAG,MAAA,GAAA,CAAG,CAAC,EAAC,kBAAA,GAAsB,MAAA,GAAA,CAAG,CAAC,GAAE,UAAW,MAAM,UAAU,eAAA,KAAc,CAAA,CAAA;MAClF,UAAK,WAAE,aAAa,MAAM,KAAK;KAEhC,GAAA,CAAA,mBAA8B,QAAA,MAAA,gBAArB,MAAM,KAAK,GAAA,CAAA,GACP,MAAM,UAAU,eAAA,SAA7B,UAAA,GAAA,YAEQ,MAAA,KAAA,GAAA;;MAFsC,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,iBAAA,CAAA;;MACxD,SAAA,cAAS,CAAT,YAAS,MAAA,KAAA,CAAA,CAAA,CAAA"}
1
+ {"version":3,"file":"reasoning-picker.js","names":["_useModel","_openBlock","_createBlock","_unref","_withCtx","_createElementVNode","_normalizeClass","_toDisplayString","_createVNode","_createElementBlock","_Fragment","_renderList","_createCommentVNode"],"sources":["../../../src/components/ai-chat/reasoning-picker.vue"],"sourcesContent":["<template>\n <UDropdown\n v-if=\"levels.length > 1\"\n ref=\"dropdownRef\"\n trigger=\"click\"\n direction=\"top\"\n alignment=\"end\"\n width=\"auto\"\n min-width=\"140px\"\n :content-class=\"cls.e('reasoning-panel')\"\n >\n <template #trigger>\n <button type=\"button\" :class=\"cls.e('reasoning-trigger')\" title=\"推理等级\">\n <span :class=\"cls.e('reasoning-trigger-label')\">{{ currentLabel }}</span>\n <UIcon :class=\"cls.e('reasoning-trigger-arrow')\">\n <ArrowDown />\n </UIcon>\n </button>\n </template>\n\n <template #content>\n <div :class=\"cls.e('reasoning-list')\">\n <div\n v-for=\"level in levels\"\n :key=\"level.value\"\n :class=\"[cls.e('reasoning-option'), bem.is('active', level.value === reasoningLevel)]\"\n @click=\"handleSelect(level.value)\"\n >\n <span>{{ level.label }}</span>\n <UIcon v-if=\"level.value === reasoningLevel\" :class=\"cls.e('reasoning-check')\">\n <Check />\n </UIcon>\n </div>\n </div>\n </template>\n </UDropdown>\n</template>\n\n<script lang=\"ts\" setup>\nimport { UDropdown, UIcon } from '@veltra/desktop'\nimport { ArrowDown, Check } from '@veltra/icons/normal'\nimport { bem } from '@veltra/utils'\nimport { computed, inject, useTemplateRef } from 'vue'\n\nimport type { ChatReasoningLevel } from '../../providers'\nimport { AiChatDIKey } from './di'\n\ndefineOptions({ name: 'UAiChatReasoningPicker' })\n\nconst { levels } = defineProps<{\n /** 当前模型的推理等级列表;不足两档时没有切换意义,不渲染 */\n levels: ChatReasoningLevel[]\n}>()\n\nconst reasoningLevel = defineModel<string | undefined>('reasoningLevel')\n\nconst di = inject(AiChatDIKey)\nconst cls = di?.cls ?? bem('ai-chat')\n\nconst dropdownRef = useTemplateRef('dropdownRef')\n\nconst currentLabel = computed(() => {\n return levels.find((level) => level.value === reasoningLevel.value)?.label ?? '推理'\n})\n\nconst handleSelect = (value: string) => {\n reasoningLevel.value = value\n dropdownRef.value?.close()\n}\n</script>\n"],"mappings":";;;;;;;;;;;;;;;;EAsDA,MAAM,iBAAiBA,SAA+B,SAAC,gBAAgB;EAGvE,MAAM,MADK,OAAO,WACN,CAAA,EAAI,OAAO,IAAI,SAAS;EAEpC,MAAM,cAAc,eAAe,aAAa;EAEhD,MAAM,eAAe,eAAe;GAClC,OAAO,QAAA,OAAO,MAAM,UAAU,MAAM,UAAU,eAAe,KAAK,CAAC,EAAE,SAAS;EAChF,CAAC;EAED,MAAM,gBAAgB,UAAkB;GACtC,eAAe,QAAQ;GACvB,YAAY,OAAO,MAAM;EAC3B;;GAlEU,OAAA,QAAA,OAAO,SAAM,KADrBC,UAAA,GAAAC,YAkCYC,MAAA,SAAA,GAAA;;IAhCN,SAAA;IAAJ,KAAI;IACJ,SAAQ;IACR,WAAU;IACV,WAAU;IACV,OAAM;IACN,aAAU;IACT,iBAAeA,MAAA,GAAA,CAAG,CAAC,EAAC,iBAAA;;IAEV,SAAOC,cAMP,CALTC,mBAKS,UAAA;KALD,MAAK;KAAU,OAAKC,eAAEH,MAAA,GAAA,CAAG,CAAC,EAAC,mBAAA,CAAA;KAAuB,OAAM;IAC9D,GAAA,CAAAE,mBAAyE,QAAA,EAAlE,OAAKC,eAAEH,MAAA,GAAA,CAAG,CAAC,EAAC,yBAAA,CAAA,EAAgC,GAAAI,gBAAA,aAAA,KAAY,GAAA,CAAA,GAC/DC,YAEQL,MAAA,KAAA,GAAA,EAFA,OAAKG,eAAEH,MAAA,GAAA,CAAG,CAAC,EAAC,yBAAA,CAAA,EAAA,GAAA;KAClB,SAAAC,cAAa,CAAbI,YAAaL,MAAA,SAAA,CAAA,CAAA,CAAA;;;IAKR,SAAOC,cAaV,CAZNC,mBAYM,OAAA,EAZA,OAAKC,eAAEH,MAAA,GAAA,CAAG,CAAC,EAAC,gBAAA,CAAA,EAAA,GAChB,EAAAF,UAAA,IAAA,GAAAQ,mBAUMC,UAAA,MAAAC,WATY,QAAA,SAAT,UAAK;KADd,OAAAV,UAAA,GAAAQ,mBAUM,OAAA;MARH,KAAK,MAAM;MACX,OAAKH,eAAA,CAAGH,MAAA,GAAA,CAAG,CAAC,EAAC,kBAAA,GAAsBA,MAAA,GAAA,CAAG,CAAC,GAAE,UAAW,MAAM,UAAU,eAAA,KAAc,CAAA,CAAA;MAClF,UAAK,WAAE,aAAa,MAAM,KAAK;KAEhC,GAAA,CAAAE,mBAA8B,QAAA,MAAAE,gBAArB,MAAM,KAAK,GAAA,CAAA,GACP,MAAM,UAAU,eAAA,SAA7BN,UAAA,GAAAC,YAEQC,MAAA,KAAA,GAAA;;MAFsC,OAAKG,eAAEH,MAAA,GAAA,CAAG,CAAC,EAAC,iBAAA,CAAA;;MACxD,SAAAC,cAAS,CAATI,YAASL,MAAA,KAAA,CAAA,CAAA,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"side-panel.js","names":[],"sources":["../../../src/components/ai-chat/side-panel.vue"],"sourcesContent":["<template>\n <aside :class=\"cls.e('panel')\">\n <div :class=\"cls.e('panel-card')\">\n <div :class=\"cls.e('panel-header')\">\n <UIcon :class=\"[cls.e('panel-icon'), statusClass]\">\n <component :is=\"headerIcon\" />\n </UIcon>\n <span :class=\"cls.e('panel-title')\">{{ panelTitle }}</span>\n <UIcon :class=\"cls.e('panel-close')\" title=\"关闭面板\" @click=\"emit('close')\">\n <Close />\n </UIcon>\n </div>\n\n <UScroll :class=\"cls.e('panel-body')\">\n <!-- render 组件与工具卡片同一契约(ChatToolRenderProps),可随 toolCall 状态实时更新 -->\n <component :is=\"tool.render\" v-if=\"tool?.render\" :tool-call=\"toolCall\" />\n <pre v-else :class=\"cls.e('panel-fallback')\">{{ fallbackResult }}</pre>\n </UScroll>\n </div>\n </aside>\n</template>\n\n<script lang=\"ts\" setup>\nimport { UIcon, UScroll } from '@veltra/desktop'\nimport { Close } from '@veltra/icons/normal'\nimport { bem } from '@veltra/utils'\nimport { computed, inject } from 'vue'\n\nimport type { ChatToolCall } from '../../chat/types'\nimport { AiChatDIKey } from './di'\nimport { resolveToolIcon } from './tool-icons'\n\ndefineOptions({ name: 'UAiChatSidePanel' })\n\nconst props = defineProps<{ toolCall: ChatToolCall }>()\n\nconst emit = defineEmits<{ (e: 'close'): void }>()\n\nconst di = inject(AiChatDIKey)\nconst cls = di?.cls ?? bem('ai-chat')\n\n/** 当前调用对应的工具定义(解析 icon/label/render) */\nconst tool = computed(() => di?.tools.value[props.toolCall.name])\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/** 头部图标:meta.icon > 宿主覆盖 > 内置名称规则 > 兜底;状态点走 status class */\nconst headerIcon = computed(\n () => tool.value?.icon ?? resolveToolIcon(props.toolCall.name, di?.toolIcons?.value)\n)\n\n/** 面板标题:优先工具定义的 panelTitle(业务对象 + 动作),缺省回落到 label ?? name */\nconst panelTitle = computed(() => {\n const t = tool.value\n const pt = t?.panelTitle\n if (typeof pt === 'function') return pt(props.toolCall)\n return pt ?? t?.label ?? props.toolCall.name\n})\n\n/** 未提供 render 时的兜底展示(面板工具通常都应提供 render) */\nconst fallbackResult = computed(() => {\n const raw = props.toolCall.error ?? props.toolCall.result ?? ''\n try {\n return JSON.stringify(JSON.parse(raw), null, 2)\n } catch {\n return raw\n }\n})\n</script>\n"],"mappings":";;;;;;;;;;;;;EAkCA,MAAM,QAAQ;EAEd,MAAM,OAAO;EAEb,MAAM,KAAK,OAAO,WAAW;EAC7B,MAAM,MAAM,IAAI,OAAO,IAAI,SAAS;;EAGpC,MAAM,OAAO,eAAe,IAAI,MAAM,MAAM,MAAM,SAAS,KAAK;EAEhE,MAAM,eAAuC;GAC3C,SAAS;GACT,SAAS;GACT,oBAAoB;GACpB,SAAS;GACT,OAAO;GACP,UAAU;EACZ;EAEA,MAAM,cAAc,eAAe,aAAa,MAAM,SAAS,WAAW,EAAE;;EAG5E,MAAM,aAAa,eACX,KAAK,OAAO,QAAQ,gBAAgB,MAAM,SAAS,MAAM,IAAI,WAAW,KAAK,CACrF;;EAGA,MAAM,aAAa,eAAe;GAChC,MAAM,IAAI,KAAK;GACf,MAAM,KAAK,GAAG;GACd,IAAI,OAAO,OAAO,YAAY,OAAO,GAAG,MAAM,QAAQ;GACtD,OAAO,MAAM,GAAG,SAAS,MAAM,SAAS;EAC1C,CAAC;;EAGD,MAAM,iBAAiB,eAAe;GACpC,MAAM,MAAM,MAAM,SAAS,SAAS,MAAM,SAAS,UAAU;GAC7D,IAAI;IACF,OAAO,KAAK,UAAU,KAAK,MAAM,GAAG,GAAG,MAAM,CAAC;GAChD,QAAQ;IACN,OAAO;GACT;EACF,CAAC;;GA3EC,OAAA,UAAA,GAAA,mBAkBQ,SAAA,EAlBA,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,OAAA,CAAA,EAAA,GAClB,CAAA,mBAgBM,OAAA,EAhBA,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,YAAA,CAAA,EAAA,GAChB,CAAA,mBAQM,OAAA,EARA,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,cAAA,CAAA,EAAA,GAAA;IAChB,YAEQ,MAAA,KAAA,GAAA,EAFA,OAAK,eAAA,CAAG,MAAA,GAAA,CAAG,CAAC,EAAC,YAAA,GAAgB,YAAA,KAAW,CAAA,EAAA,GAAA;KAC9C,SAAA,cAA8B,EAA9B,UAAA,GAAA,YAA8B,wBAAd,WAAA,KAAU,CAAA,EAAA,CAAA;;;IAE5B,mBAA2D,QAAA,EAApD,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,aAAA,CAAA,EAAoB,GAAA,gBAAA,WAAA,KAAU,GAAA,CAAA;IACjD,YAEQ,MAAA,KAAA,GAAA;KAFA,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,aAAA,CAAA;KAAiB,OAAM;KAAQ,SAAK,OAAA,OAAA,OAAA,MAAA,WAAE,KAAI,OAAA;;KAC5D,SAAA,cAAS,CAAT,YAAS,MAAA,KAAA,CAAA,CAAA,CAAA;;;GAIb,GAAA,CAAA,GAAA,YAIU,MAAA,OAAA,GAAA,EAJA,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,YAAA,CAAA,EAAA,GAAA;IACpB,SAAA,cAAmE,CAAnE,mBAAA,8DAAA,GACmC,KAAA,OAAM,UAAzC,UAAA,GAAA,YAAyE,wBAAzD,KAAA,MAAK,MAAM,GAAA;;KAAuB,aAAW,QAAA;IAC7D,GAAA,MAAA,GAAA,CAAA,WAAA,CAAA,MAAA,UAAA,GAAA,mBAAuE,OAAA;;KAA1D,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,gBAAA,CAAA;IAAuB,GAAA,gBAAA,eAAA,KAAc,GAAA,CAAA,EAAA,CAAA"}
1
+ {"version":3,"file":"side-panel.js","names":["_openBlock","_createElementBlock","_normalizeClass","_unref","_createElementVNode","_createVNode","_withCtx","_createBlock","_resolveDynamicComponent","_toDisplayString","_createCommentVNode"],"sources":["../../../src/components/ai-chat/side-panel.vue"],"sourcesContent":["<template>\n <aside :class=\"cls.e('panel')\">\n <div :class=\"cls.e('panel-card')\">\n <div :class=\"cls.e('panel-header')\">\n <UIcon :class=\"[cls.e('panel-icon'), statusClass]\">\n <component :is=\"headerIcon\" />\n </UIcon>\n <span :class=\"cls.e('panel-title')\">{{ panelTitle }}</span>\n <UIcon :class=\"cls.e('panel-close')\" title=\"关闭面板\" @click=\"emit('close')\">\n <Close />\n </UIcon>\n </div>\n\n <UScroll :class=\"cls.e('panel-body')\">\n <!-- render 组件与工具卡片同一契约(ChatToolRenderProps),可随 toolCall 状态实时更新 -->\n <component :is=\"tool.render\" v-if=\"tool?.render\" :tool-call=\"toolCall\" />\n <pre v-else :class=\"cls.e('panel-fallback')\">{{ fallbackResult }}</pre>\n </UScroll>\n </div>\n </aside>\n</template>\n\n<script lang=\"ts\" setup>\nimport { UIcon, UScroll } from '@veltra/desktop'\nimport { Close } from '@veltra/icons/normal'\nimport { bem } from '@veltra/utils'\nimport { computed, inject } from 'vue'\n\nimport type { ChatToolCall } from '../../chat/types'\nimport { AiChatDIKey } from './di'\nimport { resolveToolIcon } from './tool-icons'\n\ndefineOptions({ name: 'UAiChatSidePanel' })\n\nconst props = defineProps<{ toolCall: ChatToolCall }>()\n\nconst emit = defineEmits<{ (e: 'close'): void }>()\n\nconst di = inject(AiChatDIKey)\nconst cls = di?.cls ?? bem('ai-chat')\n\n/** 当前调用对应的工具定义(解析 icon/label/render) */\nconst tool = computed(() => di?.tools.value[props.toolCall.name])\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/** 头部图标:meta.icon > 宿主覆盖 > 内置名称规则 > 兜底;状态点走 status class */\nconst headerIcon = computed(\n () => tool.value?.icon ?? resolveToolIcon(props.toolCall.name, di?.toolIcons?.value)\n)\n\n/** 面板标题:优先工具定义的 panelTitle(业务对象 + 动作),缺省回落到 label ?? name */\nconst panelTitle = computed(() => {\n const t = tool.value\n const pt = t?.panelTitle\n if (typeof pt === 'function') return pt(props.toolCall)\n return pt ?? t?.label ?? props.toolCall.name\n})\n\n/** 未提供 render 时的兜底展示(面板工具通常都应提供 render) */\nconst fallbackResult = computed(() => {\n const raw = props.toolCall.error ?? props.toolCall.result ?? ''\n try {\n return JSON.stringify(JSON.parse(raw), null, 2)\n } catch {\n return raw\n }\n})\n</script>\n"],"mappings":";;;;;;;;;;;;;EAkCA,MAAM,QAAQ;EAEd,MAAM,OAAO;EAEb,MAAM,KAAK,OAAO,WAAW;EAC7B,MAAM,MAAM,IAAI,OAAO,IAAI,SAAS;;EAGpC,MAAM,OAAO,eAAe,IAAI,MAAM,MAAM,MAAM,SAAS,KAAK;EAEhE,MAAM,eAAuC;GAC3C,SAAS;GACT,SAAS;GACT,oBAAoB;GACpB,SAAS;GACT,OAAO;GACP,UAAU;EACZ;EAEA,MAAM,cAAc,eAAe,aAAa,MAAM,SAAS,WAAW,EAAE;;EAG5E,MAAM,aAAa,eACX,KAAK,OAAO,QAAQ,gBAAgB,MAAM,SAAS,MAAM,IAAI,WAAW,KAAK,CACrF;;EAGA,MAAM,aAAa,eAAe;GAChC,MAAM,IAAI,KAAK;GACf,MAAM,KAAK,GAAG;GACd,IAAI,OAAO,OAAO,YAAY,OAAO,GAAG,MAAM,QAAQ;GACtD,OAAO,MAAM,GAAG,SAAS,MAAM,SAAS;EAC1C,CAAC;;EAGD,MAAM,iBAAiB,eAAe;GACpC,MAAM,MAAM,MAAM,SAAS,SAAS,MAAM,SAAS,UAAU;GAC7D,IAAI;IACF,OAAO,KAAK,UAAU,KAAK,MAAM,GAAG,GAAG,MAAM,CAAC;GAChD,QAAQ;IACN,OAAO;GACT;EACF,CAAC;;GA3EC,OAAAA,UAAA,GAAAC,mBAkBQ,SAAA,EAlBA,OAAKC,eAAEC,MAAA,GAAA,CAAG,CAAC,EAAC,OAAA,CAAA,EAAA,GAClB,CAAAC,mBAgBM,OAAA,EAhBA,OAAKF,eAAEC,MAAA,GAAA,CAAG,CAAC,EAAC,YAAA,CAAA,EAAA,GAChB,CAAAC,mBAQM,OAAA,EARA,OAAKF,eAAEC,MAAA,GAAA,CAAG,CAAC,EAAC,cAAA,CAAA,EAAA,GAAA;IAChBE,YAEQF,MAAA,KAAA,GAAA,EAFA,OAAKD,eAAA,CAAGC,MAAA,GAAA,CAAG,CAAC,EAAC,YAAA,GAAgB,YAAA,KAAW,CAAA,EAAA,GAAA;KAC9C,SAAAG,cAA8B,EAA9BN,UAAA,GAAAO,YAA8BC,wBAAd,WAAA,KAAU,CAAA,EAAA,CAAA;;;IAE5BJ,mBAA2D,QAAA,EAApD,OAAKF,eAAEC,MAAA,GAAA,CAAG,CAAC,EAAC,aAAA,CAAA,EAAoB,GAAAM,gBAAA,WAAA,KAAU,GAAA,CAAA;IACjDJ,YAEQF,MAAA,KAAA,GAAA;KAFA,OAAKD,eAAEC,MAAA,GAAA,CAAG,CAAC,EAAC,aAAA,CAAA;KAAiB,OAAM;KAAQ,SAAK,OAAA,OAAA,OAAA,MAAA,WAAE,KAAI,OAAA;;KAC5D,SAAAG,cAAS,CAATD,YAASF,MAAA,KAAA,CAAA,CAAA,CAAA;;;GAIb,GAAA,CAAA,GAAAE,YAIUF,MAAA,OAAA,GAAA,EAJA,OAAKD,eAAEC,MAAA,GAAA,CAAG,CAAC,EAAC,YAAA,CAAA,EAAA,GAAA;IACpB,SAAAG,cAAmE,CAAnEI,mBAAA,8DAAA,GACmC,KAAA,OAAM,UAAzCV,UAAA,GAAAO,YAAyEC,wBAAzD,KAAA,MAAK,MAAM,GAAA;;KAAuB,aAAW,QAAA;IAC7D,GAAA,MAAA,GAAA,CAAA,WAAA,CAAA,MAAAR,UAAA,GAAAC,mBAAuE,OAAA;;KAA1D,OAAKC,eAAEC,MAAA,GAAA,CAAG,CAAC,EAAC,gBAAA,CAAA;IAAuB,GAAAM,gBAAA,eAAA,KAAc,GAAA,CAAA,EAAA,CAAA"}
@@ -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/** 「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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"}
1
+ {"version":3,"file":"tool-call.js","names":["_openBlock","_createBlock","_unref","_normalizeClass","_withCtx","_createVNode","_resolveDynamicComponent","_createElementVNode","_toDisplayString","_createElementBlock","_createCommentVNode","_withModifiers","_withKeys","_createTextVNode","_Fragment","ToolJson"],"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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"}
@@ -1 +1 @@
1
- {"version":3,"file":"tool-json.js","names":[],"sources":["../../../src/components/ai-chat/tool-json.vue"],"sourcesContent":["<template>\n <div :class=\"cls.e('tool-call-json')\">\n <div :class=\"cls.e('tool-call-json-head')\">\n <span :class=\"cls.e('tool-call-section')\">{{ title }}</span>\n <UButton\n v-if=\"overLimit\"\n :class=\"cls.e('tool-call-json-toggle')\"\n size=\"small\"\n text\n type=\"primary\"\n @click=\"expanded = !expanded\"\n >\n {{ expanded ? '收起' : '展开' }}\n </UButton>\n </div>\n <pre :class=\"[cls.e('tool-call-code'), bem.is('truncated', overLimit && !expanded)]\">{{\n shown\n }}</pre>\n </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { UButton } from '@veltra/desktop'\nimport { bem } from '@veltra/utils'\nimport { computed, inject, ref } from 'vue'\n\nimport { AiChatDIKey } from './di'\n\ndefineOptions({ name: 'UAiChatToolJson' })\n\n/** 超过该字符数截断,展开后可见全文 */\nconst PREVIEW_LIMIT = 480\n\nconst { title, text } = defineProps<{\n /** 区块标题(参数 / 结果 / 视图) */\n title: string\n /** 已格式化的 JSON 或原文 */\n text: string\n}>()\n\nconst di = inject(AiChatDIKey)\nconst cls = di?.cls ?? bem('ai-chat')\n\nconst expanded = ref(false)\n\nconst overLimit = computed(() => text.length > PREVIEW_LIMIT)\n\nconst shown = computed(() => {\n if (expanded.value || !overLimit.value) return text\n return `${text.slice(0, PREVIEW_LIMIT)}…`\n})\n</script>\n"],"mappings":";;;;;AA+BA,MAAM,gBAAgB;;;;;;;;;EAUtB,MAAM,MADK,OAAO,WACN,CAAA,EAAI,OAAO,IAAI,SAAS;EAEpC,MAAM,WAAW,IAAI,KAAK;EAE1B,MAAM,YAAY,eAAe,QAAA,KAAK,SAAS,aAAa;EAE5D,MAAM,QAAQ,eAAe;GAC3B,IAAI,SAAS,SAAS,CAAC,UAAU,OAAO,OAAO,QAAA;GAC/C,OAAO,GAAG,QAAA,KAAK,MAAM,GAAG,aAAa,EAAE;EACzC,CAAC;;GAjDC,OAAA,UAAA,GAAA,mBAiBM,OAAA,EAjBA,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,gBAAA,CAAA,EAAA,GAChB,CAAA,mBAYM,OAAA,EAZA,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,qBAAA,CAAA,EAAA,GAChB,CAAA,mBAA4D,QAAA,EAArD,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,mBAAA,CAAA,EAA0B,GAAA,gBAAA,QAAA,KAAK,GAAA,CAAA,GAE1C,UAAA,SADR,UAAA,GAAA,YASU,MAAA,OAAA,GAAA;;IAPP,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,uBAAA,CAAA;IACb,MAAK;IACL,MAAA;IACA,MAAK;IACJ,SAAK,OAAA,OAAA,OAAA,MAAA,WAAE,SAAA,QAAQ,CAAI,SAAA;;IAEpB,SAAA,cAA4B,CAAzB,gBAAA,gBAAA,SAAA,QAAQ,OAAA,IAAA,GAAA,CAAA,CAAA,CAAA;;GAGf,GAAA,GAAA,CAAA,OAAA,CAAA,KAAA,mBAAA,QAAA,IAAA,CAAA,GAAA,CAAA,GAAA,mBAEQ,OAAA,EAFF,OAAK,eAAA,CAAG,MAAA,GAAA,CAAG,CAAC,EAAC,gBAAA,GAAoB,MAAA,GAAA,CAAG,CAAC,GAAE,aAAc,UAAA,SAAS,CAAK,SAAA,KAAQ,CAAA,CAAA,EAC/E,GAAA,gBAAA,MAAA,KAAK,GAAA,CAAA,CAAA,GAAA,CAAA"}
1
+ {"version":3,"file":"tool-json.js","names":["_openBlock","_createElementBlock","_normalizeClass","_unref","_createElementVNode","_toDisplayString","_createBlock","_withCtx","_createTextVNode","_createCommentVNode"],"sources":["../../../src/components/ai-chat/tool-json.vue"],"sourcesContent":["<template>\n <div :class=\"cls.e('tool-call-json')\">\n <div :class=\"cls.e('tool-call-json-head')\">\n <span :class=\"cls.e('tool-call-section')\">{{ title }}</span>\n <UButton\n v-if=\"overLimit\"\n :class=\"cls.e('tool-call-json-toggle')\"\n size=\"small\"\n text\n type=\"primary\"\n @click=\"expanded = !expanded\"\n >\n {{ expanded ? '收起' : '展开' }}\n </UButton>\n </div>\n <pre :class=\"[cls.e('tool-call-code'), bem.is('truncated', overLimit && !expanded)]\">{{\n shown\n }}</pre>\n </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { UButton } from '@veltra/desktop'\nimport { bem } from '@veltra/utils'\nimport { computed, inject, ref } from 'vue'\n\nimport { AiChatDIKey } from './di'\n\ndefineOptions({ name: 'UAiChatToolJson' })\n\n/** 超过该字符数截断,展开后可见全文 */\nconst PREVIEW_LIMIT = 480\n\nconst { title, text } = defineProps<{\n /** 区块标题(参数 / 结果 / 视图) */\n title: string\n /** 已格式化的 JSON 或原文 */\n text: string\n}>()\n\nconst di = inject(AiChatDIKey)\nconst cls = di?.cls ?? bem('ai-chat')\n\nconst expanded = ref(false)\n\nconst overLimit = computed(() => text.length > PREVIEW_LIMIT)\n\nconst shown = computed(() => {\n if (expanded.value || !overLimit.value) return text\n return `${text.slice(0, PREVIEW_LIMIT)}…`\n})\n</script>\n"],"mappings":";;;;;AA+BA,MAAM,gBAAgB;;;;;;;;;EAUtB,MAAM,MADK,OAAO,WACN,CAAA,EAAI,OAAO,IAAI,SAAS;EAEpC,MAAM,WAAW,IAAI,KAAK;EAE1B,MAAM,YAAY,eAAe,QAAA,KAAK,SAAS,aAAa;EAE5D,MAAM,QAAQ,eAAe;GAC3B,IAAI,SAAS,SAAS,CAAC,UAAU,OAAO,OAAO,QAAA;GAC/C,OAAO,GAAG,QAAA,KAAK,MAAM,GAAG,aAAa,EAAE;EACzC,CAAC;;GAjDC,OAAAA,UAAA,GAAAC,mBAiBM,OAAA,EAjBA,OAAKC,eAAEC,MAAA,GAAA,CAAG,CAAC,EAAC,gBAAA,CAAA,EAAA,GAChB,CAAAC,mBAYM,OAAA,EAZA,OAAKF,eAAEC,MAAA,GAAA,CAAG,CAAC,EAAC,qBAAA,CAAA,EAAA,GAChB,CAAAC,mBAA4D,QAAA,EAArD,OAAKF,eAAEC,MAAA,GAAA,CAAG,CAAC,EAAC,mBAAA,CAAA,EAA0B,GAAAE,gBAAA,QAAA,KAAK,GAAA,CAAA,GAE1C,UAAA,SADRL,UAAA,GAAAM,YASUH,MAAA,OAAA,GAAA;;IAPP,OAAKD,eAAEC,MAAA,GAAA,CAAG,CAAC,EAAC,uBAAA,CAAA;IACb,MAAK;IACL,MAAA;IACA,MAAK;IACJ,SAAK,OAAA,OAAA,OAAA,MAAA,WAAE,SAAA,QAAQ,CAAI,SAAA;;IAEpB,SAAAI,cAA4B,CAAzBC,gBAAAH,gBAAA,SAAA,QAAQ,OAAA,IAAA,GAAA,CAAA,CAAA,CAAA;;GAGf,GAAA,GAAA,CAAA,OAAA,CAAA,KAAA,mBAAA,QAAA,IAAA,CAAA,GAAA,CAAA,GAAAD,mBAEQ,OAAA,EAFF,OAAKF,eAAA,CAAGC,MAAA,GAAA,CAAG,CAAC,EAAC,gBAAA,GAAoBA,MAAA,GAAA,CAAG,CAAC,GAAE,aAAc,UAAA,SAAS,CAAK,SAAA,KAAQ,CAAA,CAAA,EAC/E,GAAAE,gBAAA,MAAA,KAAK,GAAA,CAAA,CAAA,GAAA,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"turn-process.js","names":[],"sources":["../../../src/components/ai-chat/turn-process.vue"],"sourcesContent":["<template>\n <div :class=\"[cls.e('process'), bem.is('expanded', expanded)]\">\n <div :class=\"cls.e('process-header')\" @click=\"expanded = !expanded\">\n <UIcon :class=\"cls.e('process-icon')\"><CircleCheck /></UIcon>\n <span :class=\"cls.e('process-title')\">已完成</span>\n <UIcon :class=\"cls.e('process-chevron')\"><ArrowRight /></UIcon>\n </div>\n <!-- 折叠时完全卸载过程 DOM:历史轮次的思考/工具卡片可能很多,常驻挂载浪费渲染成本 -->\n <div v-if=\"expanded\" :class=\"cls.e('process-content')\">\n <MessageItem\n v-for=\"msg in messages\"\n :key=\"msg.id\"\n :message=\"msg\"\n :is-last=\"false\"\n :renderer-props=\"rendererProps\"\n @respond=\"(id, approved) => emit('respond', id, approved)\"\n />\n </div>\n </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { UIcon } from '@veltra/desktop'\nimport { ArrowRight, CircleCheck } from '@veltra/icons/normal'\nimport { bem } from '@veltra/utils'\nimport { inject, ref } from 'vue'\n\nimport type { ChatMessage } from '../../chat/types'\nimport { AiChatDIKey } from './di'\nimport MessageItem from './message-item.vue'\n\ndefineOptions({ name: 'UAiChatTurnProcess' })\n\ndefineProps<{\n /** 本轮最终答案之前的过程消息(思考 + 中间文本 + 工具卡片) */\n messages: ChatMessage[]\n /** 透传给 MarkdownRender 的属性 */\n rendererProps?: Record<string, unknown>\n}>()\n\nconst emit = defineEmits<{ (e: 'respond', toolCallId: string, approved: boolean): void }>()\n\nconst di = inject(AiChatDIKey)\nconst cls = di?.cls ?? bem('ai-chat')\n\nconst expanded = ref(false)\n</script>\n"],"mappings":";;;;;;;;;;;;;;;;EAwCA,MAAM,OAAO;EAGb,MAAM,MADK,OAAO,WACN,CAAA,EAAI,OAAO,IAAI,SAAS;EAEpC,MAAM,WAAW,IAAI,KAAK;;GA5CxB,OAAA,UAAA,GAAA,mBAiBM,OAAA,EAjBA,OAAK,eAAA,CAAG,MAAA,GAAA,CAAG,CAAC,EAAC,SAAA,GAAa,MAAA,GAAA,CAAG,CAAC,GAAE,YAAa,SAAA,KAAQ,CAAA,CAAA,EAAA,GAAA;IACzD,mBAIM,OAAA;KAJA,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,gBAAA,CAAA;KAAqB,SAAK,OAAA,OAAA,OAAA,MAAA,WAAE,SAAA,QAAQ,CAAI,SAAA;;KACxD,YAA6D,MAAA,KAAA,GAAA,EAArD,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,cAAA,CAAA,EAAA,GAAA;MAAkB,SAAA,cAAe,CAAf,YAAe,MAAA,WAAA,CAAA,CAAA,CAAA;;;KACrD,mBAAgD,QAAA,EAAzC,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,eAAA,CAAA,EAAmB,GAAA,OAAG,CAAA;KACzC,YAA+D,MAAA,KAAA,GAAA,EAAvD,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,iBAAA,CAAA,EAAA,GAAA;MAAqB,SAAA,cAAc,CAAd,YAAc,MAAA,UAAA,CAAA,CAAA,CAAA;;;;IAEzD,mBAAA,6CAAA;IACW,SAAA,SAAX,UAAA,GAAA,mBASM,OAAA;;KATgB,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,iBAAA,CAAA;IAChC,GAAA,EAAA,UAAA,IAAA,GAAA,mBAOE,UAAA,MAAA,WANc,QAAA,WAAP,QAAG;KADZ,OAAA,UAAA,GAAA,YAOE,aAAA;MALC,KAAK,IAAI;MACT,SAAS;MACT,WAAS;MACT,kBAAgB,QAAA;MAChB,WAAO,OAAA,OAAA,OAAA,MAAG,IAAI,aAAa,KAAI,WAAY,IAAI,QAAQ"}
1
+ {"version":3,"file":"turn-process.js","names":["_openBlock","_createElementBlock","_normalizeClass","_unref","_createElementVNode","_createVNode","_withCtx","_createCommentVNode","_Fragment","_renderList","_createBlock","MessageItem"],"sources":["../../../src/components/ai-chat/turn-process.vue"],"sourcesContent":["<template>\n <div :class=\"[cls.e('process'), bem.is('expanded', expanded)]\">\n <div :class=\"cls.e('process-header')\" @click=\"expanded = !expanded\">\n <UIcon :class=\"cls.e('process-icon')\"><CircleCheck /></UIcon>\n <span :class=\"cls.e('process-title')\">已完成</span>\n <UIcon :class=\"cls.e('process-chevron')\"><ArrowRight /></UIcon>\n </div>\n <!-- 折叠时完全卸载过程 DOM:历史轮次的思考/工具卡片可能很多,常驻挂载浪费渲染成本 -->\n <div v-if=\"expanded\" :class=\"cls.e('process-content')\">\n <MessageItem\n v-for=\"msg in messages\"\n :key=\"msg.id\"\n :message=\"msg\"\n :is-last=\"false\"\n :renderer-props=\"rendererProps\"\n @respond=\"(id, approved) => emit('respond', id, approved)\"\n />\n </div>\n </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { UIcon } from '@veltra/desktop'\nimport { ArrowRight, CircleCheck } from '@veltra/icons/normal'\nimport { bem } from '@veltra/utils'\nimport { inject, ref } from 'vue'\n\nimport type { ChatMessage } from '../../chat/types'\nimport { AiChatDIKey } from './di'\nimport MessageItem from './message-item.vue'\n\ndefineOptions({ name: 'UAiChatTurnProcess' })\n\ndefineProps<{\n /** 本轮最终答案之前的过程消息(思考 + 中间文本 + 工具卡片) */\n messages: ChatMessage[]\n /** 透传给 MarkdownRender 的属性 */\n rendererProps?: Record<string, unknown>\n}>()\n\nconst emit = defineEmits<{ (e: 'respond', toolCallId: string, approved: boolean): void }>()\n\nconst di = inject(AiChatDIKey)\nconst cls = di?.cls ?? bem('ai-chat')\n\nconst expanded = ref(false)\n</script>\n"],"mappings":";;;;;;;;;;;;;;;;EAwCA,MAAM,OAAO;EAGb,MAAM,MADK,OAAO,WACN,CAAA,EAAI,OAAO,IAAI,SAAS;EAEpC,MAAM,WAAW,IAAI,KAAK;;GA5CxB,OAAAA,UAAA,GAAAC,mBAiBM,OAAA,EAjBA,OAAKC,eAAA,CAAGC,MAAA,GAAA,CAAG,CAAC,EAAC,SAAA,GAAaA,MAAA,GAAA,CAAG,CAAC,GAAE,YAAa,SAAA,KAAQ,CAAA,CAAA,EAAA,GAAA;IACzDC,mBAIM,OAAA;KAJA,OAAKF,eAAEC,MAAA,GAAA,CAAG,CAAC,EAAC,gBAAA,CAAA;KAAqB,SAAK,OAAA,OAAA,OAAA,MAAA,WAAE,SAAA,QAAQ,CAAI,SAAA;;KACxDE,YAA6DF,MAAA,KAAA,GAAA,EAArD,OAAKD,eAAEC,MAAA,GAAA,CAAG,CAAC,EAAC,cAAA,CAAA,EAAA,GAAA;MAAkB,SAAAG,cAAe,CAAfD,YAAeF,MAAA,WAAA,CAAA,CAAA,CAAA;;;KACrDC,mBAAgD,QAAA,EAAzC,OAAKF,eAAEC,MAAA,GAAA,CAAG,CAAC,EAAC,eAAA,CAAA,EAAmB,GAAA,OAAG,CAAA;KACzCE,YAA+DF,MAAA,KAAA,GAAA,EAAvD,OAAKD,eAAEC,MAAA,GAAA,CAAG,CAAC,EAAC,iBAAA,CAAA,EAAA,GAAA;MAAqB,SAAAG,cAAc,CAAdD,YAAcF,MAAA,UAAA,CAAA,CAAA,CAAA;;;;IAEzDI,mBAAA,6CAAA;IACW,SAAA,SAAXP,UAAA,GAAAC,mBASM,OAAA;;KATgB,OAAKC,eAAEC,MAAA,GAAA,CAAG,CAAC,EAAC,iBAAA,CAAA;IAChC,GAAA,EAAAH,UAAA,IAAA,GAAAC,mBAOEO,UAAA,MAAAC,WANc,QAAA,WAAP,QAAG;KADZ,OAAAT,UAAA,GAAAU,YAOEC,aAAA;MALC,KAAK,IAAI;MACT,SAAS;MACT,WAAS;MACT,kBAAgB,QAAA;MAChB,WAAO,OAAA,OAAA,OAAA,MAAG,IAAI,aAAa,KAAI,WAAY,IAAI,QAAQ"}
@@ -1 +1 @@
1
- {"version":3,"file":"ai-orb.js","names":[],"sources":["../../../src/components/ai-orb/ai-orb.vue"],"sourcesContent":["<template>\n <canvas\n ref=\"canvasRef\"\n :class=\"cls.b\"\n :style=\"{ width: `${size}px`, height: `${size}px` }\"\n aria-hidden=\"true\"\n @pointermove=\"handlePointerMove\"\n @pointerleave=\"handlePointerLeave\"\n @pointerdown=\"handlePointerDown\"\n @click=\"emit('click')\"\n />\n</template>\n\n<script lang=\"ts\" setup>\nimport { bem } from '@veltra/utils'\nimport { onBeforeUnmount, onMounted, useTemplateRef, watch } from 'vue'\n\nimport type { AiOrbEmits, AiOrbExposed, AiOrbProps, AiOrbReaction } from '../../types/ai-orb'\nimport { createOrbRenderer, type AiOrbRenderer } from './orb-renderer'\n\ndefineOptions({ name: 'UAiOrb' })\n\nconst props = withDefaults(defineProps<AiOrbProps>(), { size: 48, status: 'idle' })\nconst emit = defineEmits<AiOrbEmits>()\n\nconst cls = bem('ai-orb')\n\nconst canvasRef = useTemplateRef<HTMLCanvasElement>('canvasRef')\n\nlet renderer: AiOrbRenderer | null = null\nlet observer: IntersectionObserver | null = null\nlet motionQuery: MediaQueryList | null = null\n\n/** 指针坐标 → 单位球空间(球心为原点,1 = 球半径),交给渲染器做视线跟随 */\nconst handlePointerMove = (e: PointerEvent) => {\n const el = canvasRef.value\n if (!el || !renderer) return\n const rect = el.getBoundingClientRect()\n const ballRadius = rect.width * 0.36\n renderer.setPointer({\n x: (e.clientX - rect.left - rect.width / 2) / ballRadius,\n y: (e.clientY - rect.top - rect.height / 2) / ballRadius\n })\n}\n\nconst handlePointerLeave = () => renderer?.setPointer(null)\n\nconst handlePointerDown = () => renderer?.poke()\n\nconst applyVisibility = (visible: boolean) => {\n if (!renderer) return\n if (motionQuery?.matches) {\n // 减少动态偏好:只画一帧静态画面,不启动画循环\n renderer.stop()\n renderer.renderOnce()\n return\n }\n if (visible) renderer.start()\n else renderer.stop()\n}\n\nconst handleMotionChange = () => {\n // 偏好像素级开关变化时重评估(不可见时 start 也会被 IntersectionObserver 纠正)\n applyVisibility(true)\n}\n\nonMounted(() => {\n const canvas = canvasRef.value\n if (!canvas) return\n\n renderer = createOrbRenderer(canvas, { size: props.size, status: props.status })\n\n motionQuery = window.matchMedia?.('(prefers-reduced-motion: reduce)') ?? null\n motionQuery?.addEventListener?.('change', handleMotionChange)\n\n if (typeof IntersectionObserver !== 'undefined') {\n // 滚出视口的球暂停动画循环,长会话下同屏多个球也不累积开销\n observer = new IntersectionObserver((entries) => {\n applyVisibility(entries[0]?.isIntersecting ?? true)\n })\n observer.observe(canvas)\n } else {\n applyVisibility(true)\n }\n})\n\nwatch(\n () => props.status,\n (status) => renderer?.setStatus(status)\n)\n\nwatch(\n () => props.size,\n (size) => renderer?.resize(size)\n)\n\nconst react = (reaction: AiOrbReaction) => renderer?.trigger(reaction)\n\ndefineExpose<AiOrbExposed>({ react })\n\nonBeforeUnmount(() => {\n observer?.disconnect()\n motionQuery?.removeEventListener?.('change', handleMotionChange)\n renderer?.stop()\n renderer = null\n})\n</script>\n"],"mappings":";;;;;;;;;;;;;EAsBA,MAAM,QAAQ;EACd,MAAM,OAAO;EAEb,MAAM,MAAM,IAAI,QAAQ;EAExB,MAAM,YAAY,eAAkC,WAAW;EAE/D,IAAI,WAAiC;EACrC,IAAI,WAAwC;EAC5C,IAAI,cAAqC;;EAGzC,MAAM,qBAAqB,MAAoB;GAC7C,MAAM,KAAK,UAAU;GACrB,IAAI,CAAC,MAAM,CAAC,UAAU;GACtB,MAAM,OAAO,GAAG,sBAAsB;GACtC,MAAM,aAAa,KAAK,QAAQ;GAChC,SAAS,WAAW;IAClB,IAAI,EAAE,UAAU,KAAK,OAAO,KAAK,QAAQ,KAAK;IAC9C,IAAI,EAAE,UAAU,KAAK,MAAM,KAAK,SAAS,KAAK;GAChD,CAAC;EACH;EAEA,MAAM,2BAA2B,UAAU,WAAW,IAAI;EAE1D,MAAM,0BAA0B,UAAU,KAAK;EAE/C,MAAM,mBAAmB,YAAqB;GAC5C,IAAI,CAAC,UAAU;GACf,IAAI,aAAa,SAAS;IAExB,SAAS,KAAK;IACd,SAAS,WAAW;IACpB;GACF;GACA,IAAI,SAAS,SAAS,MAAM;QACvB,SAAS,KAAK;EACrB;EAEA,MAAM,2BAA2B;GAE/B,gBAAgB,IAAI;EACtB;EAEA,gBAAgB;GACd,MAAM,SAAS,UAAU;GACzB,IAAI,CAAC,QAAQ;GAEb,WAAW,kBAAkB,QAAQ;IAAE,MAAM,MAAM;IAAM,QAAQ,MAAM;GAAO,CAAC;GAE/E,cAAc,OAAO,aAAa,kCAAkC,KAAK;GACzE,aAAa,mBAAmB,UAAU,kBAAkB;GAE5D,IAAI,OAAO,yBAAyB,aAAa;IAE/C,WAAW,IAAI,sBAAsB,YAAY;KAC/C,gBAAgB,QAAQ,EAAE,EAAE,kBAAkB,IAAI;IACpD,CAAC;IACD,SAAS,QAAQ,MAAM;GACzB,OACE,gBAAgB,IAAI;EAExB,CAAC;EAED,YACQ,MAAM,SACX,WAAW,UAAU,UAAU,MAAM,CACxC;EAEA,YACQ,MAAM,OACX,SAAS,UAAU,OAAO,IAAI,CACjC;EAEA,MAAM,SAAS,aAA4B,UAAU,QAAQ,QAAQ;EAErE,SAA2B,EAAE,MAAM,CAAC;EAEpC,sBAAsB;GACpB,UAAU,WAAW;GACrB,aAAa,sBAAsB,UAAU,kBAAkB;GAC/D,UAAU,KAAK;GACf,WAAW;EACb,CAAC;;GAxGC,OAAA,UAAA,GAAA,mBASE,UAAA;IARI,SAAA;IAAJ,KAAI;IACH,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,CAAC;IACZ,OAAK,eAAA;KAAA,OAAA,GAAc,QAAA,KAAI;KAAA,QAAA,GAAiB,QAAA,KAAI;IAAA,CAAA;IAC7C,eAAY;IACX,eAAa;IACb,gBAAc;IACd,eAAa;IACb,SAAK,OAAA,OAAA,OAAA,MAAA,WAAE,KAAI,OAAA"}
1
+ {"version":3,"file":"ai-orb.js","names":["_openBlock","_createElementBlock","_normalizeClass","_unref","_normalizeStyle"],"sources":["../../../src/components/ai-orb/ai-orb.vue"],"sourcesContent":["<template>\n <canvas\n ref=\"canvasRef\"\n :class=\"cls.b\"\n :style=\"{ width: `${size}px`, height: `${size}px` }\"\n aria-hidden=\"true\"\n @pointermove=\"handlePointerMove\"\n @pointerleave=\"handlePointerLeave\"\n @pointerdown=\"handlePointerDown\"\n @click=\"emit('click')\"\n />\n</template>\n\n<script lang=\"ts\" setup>\nimport { bem } from '@veltra/utils'\nimport { onBeforeUnmount, onMounted, useTemplateRef, watch } from 'vue'\n\nimport type { AiOrbEmits, AiOrbExposed, AiOrbProps, AiOrbReaction } from '../../types/ai-orb'\nimport { createOrbRenderer, type AiOrbRenderer } from './orb-renderer'\n\ndefineOptions({ name: 'UAiOrb' })\n\nconst props = withDefaults(defineProps<AiOrbProps>(), { size: 48, status: 'idle' })\nconst emit = defineEmits<AiOrbEmits>()\n\nconst cls = bem('ai-orb')\n\nconst canvasRef = useTemplateRef<HTMLCanvasElement>('canvasRef')\n\nlet renderer: AiOrbRenderer | null = null\nlet observer: IntersectionObserver | null = null\nlet motionQuery: MediaQueryList | null = null\n\n/** 指针坐标 → 单位球空间(球心为原点,1 = 球半径),交给渲染器做视线跟随 */\nconst handlePointerMove = (e: PointerEvent) => {\n const el = canvasRef.value\n if (!el || !renderer) return\n const rect = el.getBoundingClientRect()\n const ballRadius = rect.width * 0.36\n renderer.setPointer({\n x: (e.clientX - rect.left - rect.width / 2) / ballRadius,\n y: (e.clientY - rect.top - rect.height / 2) / ballRadius\n })\n}\n\nconst handlePointerLeave = () => renderer?.setPointer(null)\n\nconst handlePointerDown = () => renderer?.poke()\n\nconst applyVisibility = (visible: boolean) => {\n if (!renderer) return\n if (motionQuery?.matches) {\n // 减少动态偏好:只画一帧静态画面,不启动画循环\n renderer.stop()\n renderer.renderOnce()\n return\n }\n if (visible) renderer.start()\n else renderer.stop()\n}\n\nconst handleMotionChange = () => {\n // 偏好像素级开关变化时重评估(不可见时 start 也会被 IntersectionObserver 纠正)\n applyVisibility(true)\n}\n\nonMounted(() => {\n const canvas = canvasRef.value\n if (!canvas) return\n\n renderer = createOrbRenderer(canvas, { size: props.size, status: props.status })\n\n motionQuery = window.matchMedia?.('(prefers-reduced-motion: reduce)') ?? null\n motionQuery?.addEventListener?.('change', handleMotionChange)\n\n if (typeof IntersectionObserver !== 'undefined') {\n // 滚出视口的球暂停动画循环,长会话下同屏多个球也不累积开销\n observer = new IntersectionObserver((entries) => {\n applyVisibility(entries[0]?.isIntersecting ?? true)\n })\n observer.observe(canvas)\n } else {\n applyVisibility(true)\n }\n})\n\nwatch(\n () => props.status,\n (status) => renderer?.setStatus(status)\n)\n\nwatch(\n () => props.size,\n (size) => renderer?.resize(size)\n)\n\nconst react = (reaction: AiOrbReaction) => renderer?.trigger(reaction)\n\ndefineExpose<AiOrbExposed>({ react })\n\nonBeforeUnmount(() => {\n observer?.disconnect()\n motionQuery?.removeEventListener?.('change', handleMotionChange)\n renderer?.stop()\n renderer = null\n})\n</script>\n"],"mappings":";;;;;;;;;;;;;EAsBA,MAAM,QAAQ;EACd,MAAM,OAAO;EAEb,MAAM,MAAM,IAAI,QAAQ;EAExB,MAAM,YAAY,eAAkC,WAAW;EAE/D,IAAI,WAAiC;EACrC,IAAI,WAAwC;EAC5C,IAAI,cAAqC;;EAGzC,MAAM,qBAAqB,MAAoB;GAC7C,MAAM,KAAK,UAAU;GACrB,IAAI,CAAC,MAAM,CAAC,UAAU;GACtB,MAAM,OAAO,GAAG,sBAAsB;GACtC,MAAM,aAAa,KAAK,QAAQ;GAChC,SAAS,WAAW;IAClB,IAAI,EAAE,UAAU,KAAK,OAAO,KAAK,QAAQ,KAAK;IAC9C,IAAI,EAAE,UAAU,KAAK,MAAM,KAAK,SAAS,KAAK;GAChD,CAAC;EACH;EAEA,MAAM,2BAA2B,UAAU,WAAW,IAAI;EAE1D,MAAM,0BAA0B,UAAU,KAAK;EAE/C,MAAM,mBAAmB,YAAqB;GAC5C,IAAI,CAAC,UAAU;GACf,IAAI,aAAa,SAAS;IAExB,SAAS,KAAK;IACd,SAAS,WAAW;IACpB;GACF;GACA,IAAI,SAAS,SAAS,MAAM;QACvB,SAAS,KAAK;EACrB;EAEA,MAAM,2BAA2B;GAE/B,gBAAgB,IAAI;EACtB;EAEA,gBAAgB;GACd,MAAM,SAAS,UAAU;GACzB,IAAI,CAAC,QAAQ;GAEb,WAAW,kBAAkB,QAAQ;IAAE,MAAM,MAAM;IAAM,QAAQ,MAAM;GAAO,CAAC;GAE/E,cAAc,OAAO,aAAa,kCAAkC,KAAK;GACzE,aAAa,mBAAmB,UAAU,kBAAkB;GAE5D,IAAI,OAAO,yBAAyB,aAAa;IAE/C,WAAW,IAAI,sBAAsB,YAAY;KAC/C,gBAAgB,QAAQ,EAAE,EAAE,kBAAkB,IAAI;IACpD,CAAC;IACD,SAAS,QAAQ,MAAM;GACzB,OACE,gBAAgB,IAAI;EAExB,CAAC;EAED,YACQ,MAAM,SACX,WAAW,UAAU,UAAU,MAAM,CACxC;EAEA,YACQ,MAAM,OACX,SAAS,UAAU,OAAO,IAAI,CACjC;EAEA,MAAM,SAAS,aAA4B,UAAU,QAAQ,QAAQ;EAErE,SAA2B,EAAE,MAAM,CAAC;EAEpC,sBAAsB;GACpB,UAAU,WAAW;GACrB,aAAa,sBAAsB,UAAU,kBAAkB;GAC/D,UAAU,KAAK;GACf,WAAW;EACb,CAAC;;GAxGC,OAAAA,UAAA,GAAAC,mBASE,UAAA;IARI,SAAA;IAAJ,KAAI;IACH,OAAKC,eAAEC,MAAA,GAAA,CAAG,CAAC,CAAC;IACZ,OAAKC,eAAA;KAAA,OAAA,GAAc,QAAA,KAAI;KAAA,QAAA,GAAiB,QAAA,KAAI;IAAA,CAAA;IAC7C,eAAY;IACX,eAAa;IACb,gBAAc;IACd,eAAa;IACb,SAAK,OAAA,OAAA,OAAA,MAAA,WAAE,KAAI,OAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@veltra/ai",
3
- "version": "2.2.1",
3
+ "version": "2.2.3",
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"
@@ -36,7 +36,7 @@
36
36
  }
37
37
  },
38
38
  "dependencies": {
39
- "markstream-vue": "^2.0.11"
39
+ "markstream-vue": "^2.0.17"
40
40
  },
41
41
  "peerDependencies": {
42
42
  "@veltra/compositions": "^1.7.14",
@@ -44,6 +44,6 @@
44
44
  "@veltra/icons": "^1.5.0",
45
45
  "@veltra/styles": "^1.7.14",
46
46
  "@veltra/utils": "^1.7.14",
47
- "vue": ">=3.5.42"
47
+ "vue": ">=3.5.43"
48
48
  }
49
49
  }