@veltra/ai 2.1.6 → 2.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/README.md +42 -0
  2. package/dist/chat/fold.d.ts +41 -0
  3. package/dist/chat/fold.js +208 -0
  4. package/dist/chat/fold.js.map +1 -0
  5. package/dist/chat/index.d.ts +4 -2
  6. package/dist/chat/session.d.ts +123 -0
  7. package/dist/chat/session.js +79 -0
  8. package/dist/chat/session.js.map +1 -0
  9. package/dist/chat/types.d.ts +38 -1
  10. package/dist/chat/use-chat-helpers.js +39 -0
  11. package/dist/chat/use-chat-helpers.js.map +1 -0
  12. package/dist/chat/use-chat-session.js +68 -0
  13. package/dist/chat/use-chat-session.js.map +1 -0
  14. package/dist/chat/use-chat.d.ts +50 -2
  15. package/dist/chat/use-chat.js +73 -39
  16. package/dist/chat/use-chat.js.map +1 -1
  17. package/dist/components/ai-chat/ai-chat.js +49 -14
  18. package/dist/components/ai-chat/ai-chat.js.map +1 -1
  19. package/dist/components/ai-chat/approval-banner.js +54 -0
  20. package/dist/components/ai-chat/approval-banner.js.map +1 -0
  21. package/dist/components/ai-chat/ask-question.js +32 -51
  22. package/dist/components/ai-chat/ask-question.js.map +1 -1
  23. package/dist/components/ai-chat/di.js.map +1 -1
  24. package/dist/components/ai-chat/job-bar.js +48 -0
  25. package/dist/components/ai-chat/job-bar.js.map +1 -0
  26. package/dist/components/ai-chat/message-list.js +5 -2
  27. package/dist/components/ai-chat/message-list.js.map +1 -1
  28. package/dist/components/ai-chat/queue-list.js +9 -3
  29. package/dist/components/ai-chat/queue-list.js.map +1 -1
  30. package/dist/components/ai-chat/side-panel.js +12 -30
  31. package/dist/components/ai-chat/side-panel.js.map +1 -1
  32. package/dist/components/ai-chat/style2.css +146 -8
  33. package/dist/components/ai-chat/tool-call.js +51 -40
  34. package/dist/components/ai-chat/tool-call.js.map +1 -1
  35. package/dist/components/ai-chat/tool-icons.js +31 -0
  36. package/dist/components/ai-chat/tool-icons.js.map +1 -0
  37. package/dist/components/ai-chat/tool-json.js +40 -0
  38. package/dist/components/ai-chat/tool-json.js.map +1 -0
  39. package/dist/components/ai-orb/style2.css +1 -1
  40. package/dist/index.d.ts +6 -4
  41. package/dist/index.js +3 -1
  42. package/dist/tools/ask-question/ask-question.js +48 -2
  43. package/dist/tools/ask-question/ask-question.js.map +1 -1
  44. package/dist/types/ai-chat.d.ts +17 -6
  45. package/package.json +7 -7
  46. package/src/chat/__test__/fold.test.ts +136 -0
  47. package/src/chat/__test__/session.test.ts +191 -0
  48. package/src/chat/__test__/use-chat.test.ts +268 -1
  49. package/src/chat/fold.ts +260 -0
  50. package/src/chat/index.ts +2 -0
  51. package/src/chat/session.ts +168 -0
  52. package/src/chat/types.ts +40 -0
  53. package/src/chat/use-chat-helpers.ts +39 -0
  54. package/src/chat/use-chat-session.ts +87 -0
  55. package/src/chat/use-chat.ts +90 -44
  56. package/src/components/ai-chat/__test__/ai-chat.test.ts +405 -7
  57. package/src/components/ai-chat/__test__/tool-icons.test.ts +33 -0
  58. package/src/components/ai-chat/ai-chat.vue +39 -4
  59. package/src/components/ai-chat/approval-banner.vue +37 -0
  60. package/src/components/ai-chat/ask-question.vue +38 -54
  61. package/src/components/ai-chat/di.ts +6 -4
  62. package/src/components/ai-chat/job-bar.vue +41 -0
  63. package/src/components/ai-chat/message-list.vue +4 -1
  64. package/src/components/ai-chat/queue-list.vue +3 -1
  65. package/src/components/ai-chat/side-panel.vue +15 -13
  66. package/src/components/ai-chat/style.scss +158 -6
  67. package/src/components/ai-chat/tool-call.vue +46 -34
  68. package/src/components/ai-chat/tool-icons.ts +33 -0
  69. package/src/components/ai-chat/tool-json.vue +52 -0
  70. package/src/tools/ask-question/ask-question.ts +53 -4
  71. package/src/types/ai-chat.ts +17 -5
@@ -11,7 +11,10 @@ const _hoisted_4 = ["onClick"];
11
11
  const _sfc_main = /*@__PURE__*/ defineComponent({
12
12
  name: "UAiChatQueueList",
13
13
  __name: "queue-list",
14
- props: { queue: {} },
14
+ props: {
15
+ queue: {},
16
+ readonly: { type: Boolean }
17
+ },
15
18
  emits: [
16
19
  "start-now",
17
20
  "edit",
@@ -31,7 +34,10 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
31
34
  }, [createElementVNode("span", {
32
35
  class: normalizeClass(unref(cls).e("queue-text")),
33
36
  title: item.content
34
- }, toDisplayString(item.content), 11, _hoisted_1), createElementVNode("span", { class: normalizeClass(unref(cls).e("queue-actions")) }, [
37
+ }, toDisplayString(item.content), 11, _hoisted_1), !__props.readonly ? (openBlock(), createElementBlock("span", {
38
+ key: 0,
39
+ class: normalizeClass(unref(cls).e("queue-actions"))
40
+ }, [
35
41
  createElementVNode("button", {
36
42
  type: "button",
37
43
  class: normalizeClass([unref(cls).e("queue-action"), unref(cls).em("queue-action", "primary")]),
@@ -59,7 +65,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
59
65
  default: withCtx(() => [createVNode(unref(Close))]),
60
66
  _: 1
61
67
  })], 10, _hoisted_4)
62
- ], 2)], 2);
68
+ ], 2)) : createCommentVNode("v-if", true)], 2);
63
69
  }), 128))], 2)) : createCommentVNode("v-if", true);
64
70
  };
65
71
  }
@@ -1 +1 @@
1
- {"version":3,"file":"queue-list.js","names":[],"sources":["../../../src/components/ai-chat/queue-list.vue"],"sourcesContent":["<template>\n <div v-if=\"queue.length\" :class=\"cls.e('queue')\">\n <div :class=\"cls.e('queue-head')\">待发送队列 · {{ queue.length }}</div>\n <div v-for=\"item in queue\" :key=\"item.id\" :class=\"cls.e('queue-item')\">\n <span :class=\"cls.e('queue-text')\" :title=\"item.content\">{{ item.content }}</span>\n <span :class=\"cls.e('queue-actions')\">\n <button\n type=\"button\"\n :class=\"[cls.e('queue-action'), cls.em('queue-action', 'primary')]\"\n title=\"中断当前会话,立即执行该条\"\n @click=\"emit('start-now', item.id)\"\n >\n <UIcon><CaretRight /></UIcon>立即开始\n </button>\n <button\n type=\"button\"\n :class=\"cls.e('queue-action')\"\n title=\"取回输入框编辑\"\n @click=\"emit('edit', item.id)\"\n >\n <UIcon><EditPen /></UIcon>\n </button>\n <button\n type=\"button\"\n :class=\"cls.e('queue-action')\"\n title=\"移出队列\"\n @click=\"emit('remove', item.id)\"\n >\n <UIcon><Close /></UIcon>\n </button>\n </span>\n </div>\n </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { UIcon } from '@veltra/desktop'\nimport { CaretRight, Close, EditPen } from '@veltra/icons/normal'\nimport { bem } from '@veltra/utils'\nimport { inject } from 'vue'\n\nimport type { ChatQueuedMessage } from '../../chat/types'\nimport { AiChatDIKey } from './di'\n\ndefineOptions({ name: 'UAiChatQueueList' })\n\ndefineProps<{\n /** 待发送队列(按执行顺序排列) */\n queue: ChatQueuedMessage[]\n}>()\n\nconst emit = defineEmits<{\n /** 立即开始:中断当前会话并插队执行 */\n (e: 'start-now', id: string): void\n /** 编辑:取回输入框重新编辑 */\n (e: 'edit', id: string): void\n /** 移出队列 */\n (e: 'remove', id: string): void\n}>()\n\nconst di = inject(AiChatDIKey)\nconst cls = di?.cls ?? bem('ai-chat')\n</script>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;EAmDA,MAAM,OAAO;EAUb,MAAM,MADK,OAAO,WACN,CAAA,EAAI,OAAO,IAAI,SAAS;;GA5DvB,OAAA,QAAA,MAAM,UAAjB,UAAA,GAAA,mBA+BM,OAAA;;IA/BoB,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,OAAA,CAAA;GACpC,GAAA,CAAA,mBAAkE,OAAA,EAA5D,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,YAAA,CAAA,EAAgB,GAAA,aAAQ,gBAAG,QAAA,MAAM,MAAM,GAAA,CAAA,IACzD,UAAA,IAAA,GAAA,mBA4BM,UAAA,MAAA,WA5Bc,QAAA,QAAR,SAAI;IAAhB,OAAA,UAAA,GAAA,mBA4BM,OAAA;KA5BsB,KAAK,KAAK;KAAK,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,YAAA,CAAA;IACrD,GAAA,CAAA,mBAAkF,QAAA;KAA3E,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,YAAA,CAAA;KAAiB,OAAO,KAAK;IAAY,GAAA,gBAAA,KAAK,OAAO,GAAA,IAAA,UAAA,GACxE,mBAyBO,QAAA,EAzBA,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,eAAA,CAAA,EAAA,GAAA;KACjB,mBAOS,UAAA;MANP,MAAK;MACJ,OAAK,eAAA,CAAG,MAAA,GAAA,CAAG,CAAC,EAAC,cAAA,GAAkB,MAAA,GAAA,CAAG,CAAC,GAAE,gBAAA,SAAA,CAAA,CAAA;MACtC,OAAM;MACL,UAAK,WAAE,KAAI,aAAc,KAAK,EAAE;KAEjC,GAAA,CAAA,YAA6B,MAAA,KAAA,GAAA,MAAA;MAAtB,SAAA,cAAc,CAAd,YAAc,MAAA,UAAA,CAAA,CAAA,CAAA;;KAAQ,CAAA,GAAA,OAAA,OAAA,OAAA,KAAA,gBAAA,SAC/B,EAAA,EAAA,GAAA,IAAA,UAAA;KACA,mBAOS,UAAA;MANP,MAAK;MACJ,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,cAAA,CAAA;MACb,OAAM;MACL,UAAK,WAAE,KAAI,QAAS,KAAK,EAAE;KAE5B,GAAA,CAAA,YAA0B,MAAA,KAAA,GAAA,MAAA;MAAnB,SAAA,cAAW,CAAX,YAAW,MAAA,OAAA,CAAA,CAAA,CAAA;;;KAEpB,mBAOS,UAAA;MANP,MAAK;MACJ,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,cAAA,CAAA;MACb,OAAM;MACL,UAAK,WAAE,KAAI,UAAW,KAAK,EAAE;KAE9B,GAAA,CAAA,YAAwB,MAAA,KAAA,GAAA,MAAA;MAAjB,SAAA,cAAS,CAAT,YAAS,MAAA,KAAA,CAAA,CAAA,CAAA"}
1
+ {"version":3,"file":"queue-list.js","names":[],"sources":["../../../src/components/ai-chat/queue-list.vue"],"sourcesContent":["<template>\n <div v-if=\"queue.length\" :class=\"cls.e('queue')\">\n <div :class=\"cls.e('queue-head')\">待发送队列 · {{ queue.length }}</div>\n <div v-for=\"item in queue\" :key=\"item.id\" :class=\"cls.e('queue-item')\">\n <span :class=\"cls.e('queue-text')\" :title=\"item.content\">{{ item.content }}</span>\n <span v-if=\"!readonly\" :class=\"cls.e('queue-actions')\">\n <button\n type=\"button\"\n :class=\"[cls.e('queue-action'), cls.em('queue-action', 'primary')]\"\n title=\"中断当前会话,立即执行该条\"\n @click=\"emit('start-now', item.id)\"\n >\n <UIcon><CaretRight /></UIcon>立即开始\n </button>\n <button\n type=\"button\"\n :class=\"cls.e('queue-action')\"\n title=\"取回输入框编辑\"\n @click=\"emit('edit', item.id)\"\n >\n <UIcon><EditPen /></UIcon>\n </button>\n <button\n type=\"button\"\n :class=\"cls.e('queue-action')\"\n title=\"移出队列\"\n @click=\"emit('remove', item.id)\"\n >\n <UIcon><Close /></UIcon>\n </button>\n </span>\n </div>\n </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { UIcon } from '@veltra/desktop'\nimport { CaretRight, Close, EditPen } from '@veltra/icons/normal'\nimport { bem } from '@veltra/utils'\nimport { inject } from 'vue'\n\nimport type { ChatQueuedMessage } from '../../chat/types'\nimport { AiChatDIKey } from './di'\n\ndefineOptions({ name: 'UAiChatQueueList' })\n\ndefineProps<{\n /** 待发送队列(按执行顺序排列) */\n queue: ChatQueuedMessage[]\n /** 只读时不展示插队 / 编辑 / 移除 */\n readonly?: boolean\n}>()\n\nconst emit = defineEmits<{\n /** 立即开始:中断当前会话并插队执行 */\n (e: 'start-now', id: string): void\n /** 编辑:取回输入框重新编辑 */\n (e: 'edit', id: string): void\n /** 移出队列 */\n (e: 'remove', id: string): void\n}>()\n\nconst di = inject(AiChatDIKey)\nconst cls = di?.cls ?? bem('ai-chat')\n</script>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;EAqDA,MAAM,OAAO;EAUb,MAAM,MADK,OAAO,WACN,CAAA,EAAI,OAAO,IAAI,SAAS;;GA9DvB,OAAA,QAAA,MAAM,UAAjB,UAAA,GAAA,mBA+BM,OAAA;;IA/BoB,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,OAAA,CAAA;GACpC,GAAA,CAAA,mBAAkE,OAAA,EAA5D,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,YAAA,CAAA,EAAgB,GAAA,aAAQ,gBAAG,QAAA,MAAM,MAAM,GAAA,CAAA,IACzD,UAAA,IAAA,GAAA,mBA4BM,UAAA,MAAA,WA5Bc,QAAA,QAAR,SAAI;IAAhB,OAAA,UAAA,GAAA,mBA4BM,OAAA;KA5BsB,KAAK,KAAK;KAAK,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,YAAA,CAAA;IACrD,GAAA,CAAA,mBAAkF,QAAA;KAA3E,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,YAAA,CAAA;KAAiB,OAAO,KAAK;IAAY,GAAA,gBAAA,KAAK,OAAO,GAAA,IAAA,UAAA,GAC3D,CAAA,QAAA,YAAb,UAAA,GAAA,mBAyBO,QAAA;;KAzBiB,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,eAAA,CAAA;;KAClC,mBAOS,UAAA;MANP,MAAK;MACJ,OAAK,eAAA,CAAG,MAAA,GAAA,CAAG,CAAC,EAAC,cAAA,GAAkB,MAAA,GAAA,CAAG,CAAC,GAAE,gBAAA,SAAA,CAAA,CAAA;MACtC,OAAM;MACL,UAAK,WAAE,KAAI,aAAc,KAAK,EAAE;KAEjC,GAAA,CAAA,YAA6B,MAAA,KAAA,GAAA,MAAA;MAAtB,SAAA,cAAc,CAAd,YAAc,MAAA,UAAA,CAAA,CAAA,CAAA;;KAAQ,CAAA,GAAA,OAAA,OAAA,OAAA,KAAA,gBAAA,SAC/B,EAAA,EAAA,GAAA,IAAA,UAAA;KACA,mBAOS,UAAA;MANP,MAAK;MACJ,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,cAAA,CAAA;MACb,OAAM;MACL,UAAK,WAAE,KAAI,QAAS,KAAK,EAAE;KAE5B,GAAA,CAAA,YAA0B,MAAA,KAAA,GAAA,MAAA;MAAnB,SAAA,cAAW,CAAX,YAAW,MAAA,OAAA,CAAA,CAAA,CAAA;;;KAEpB,mBAOS,UAAA;MANP,MAAK;MACJ,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,cAAA,CAAA;MACb,OAAM;MACL,UAAK,WAAE,KAAI,UAAW,KAAK,EAAE;KAE9B,GAAA,CAAA,YAAwB,MAAA,KAAA,GAAA,MAAA;MAAjB,SAAA,cAAS,CAAT,YAAS,MAAA,KAAA,CAAA,CAAA,CAAA"}
@@ -1,6 +1,7 @@
1
1
  import { AiChatDIKey } from "./di.js";
2
+ import { resolveToolIcon } from "./tool-icons.js";
2
3
  import { computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, inject, normalizeClass, openBlock, resolveDynamicComponent, toDisplayString, unref, withCtx } from "vue";
3
- import { CircleCheckFilled, CircleClose, Close, Loading, WarningFilled } from "@veltra/icons/normal";
4
+ import { Close } from "@veltra/icons/normal";
4
5
  import { UIcon, UScroll } from "@veltra/desktop";
5
6
  import { bem } from "@veltra/utils";
6
7
  //#region src/components/ai-chat/side-panel.vue
@@ -16,36 +17,17 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
16
17
  const cls = di?.cls ?? bem("ai-chat");
17
18
  /** 当前调用对应的工具定义(解析 icon/label/render) */
18
19
  const tool = computed(() => di?.tools.value[props.toolCall.name]);
19
- const STATUS_META = {
20
- pending: {
21
- icon: Loading,
22
- class: "is-active"
23
- },
24
- running: {
25
- icon: Loading,
26
- class: "is-active"
27
- },
28
- "awaiting-confirm": {
29
- icon: WarningFilled,
30
- class: "is-warning"
31
- },
32
- success: {
33
- icon: CircleCheckFilled,
34
- class: "is-success"
35
- },
36
- error: {
37
- icon: CircleClose,
38
- class: "is-danger"
39
- },
40
- rejected: {
41
- icon: Close,
42
- class: "is-danger"
43
- }
20
+ const STATUS_CLASS = {
21
+ pending: "is-active",
22
+ running: "is-active",
23
+ "awaiting-confirm": "is-warning",
24
+ success: "is-success",
25
+ error: "is-danger",
26
+ rejected: "is-danger"
44
27
  };
45
- const statusIcon = computed(() => STATUS_META[props.toolCall.status]?.icon ?? Loading);
46
- const statusClass = computed(() => STATUS_META[props.toolCall.status]?.class ?? "");
47
- /** 头部图标:工具自定义图标优先,缺省用状态图标(与工具卡片一致) */
48
- const headerIcon = computed(() => tool.value?.icon ?? statusIcon.value);
28
+ const statusClass = computed(() => STATUS_CLASS[props.toolCall.status] ?? "");
29
+ /** 头部图标:meta.icon > 宿主覆盖 > 内置名称规则 > 兜底;状态点走 status class */
30
+ const headerIcon = computed(() => tool.value?.icon ?? resolveToolIcon(props.toolCall.name, di?.toolIcons?.value));
49
31
  /** 面板标题:优先工具定义的 panelTitle(业务对象 + 动作),缺省回落到 label ?? name */
50
32
  const panelTitle = computed(() => {
51
33
  const t = tool.value;
@@ -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 { CircleCheckFilled, CircleClose, Close, Loading, WarningFilled } from '@veltra/icons/normal'\nimport { bem } from '@veltra/utils'\nimport { computed, inject, type Component } from 'vue'\n\nimport type { ChatToolCall } from '../../chat/types'\nimport { AiChatDIKey } from './di'\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_META: Record<string, { icon: Component; class: string }> = {\n pending: { icon: Loading, class: 'is-active' },\n running: { icon: Loading, class: 'is-active' },\n 'awaiting-confirm': { icon: WarningFilled, class: 'is-warning' },\n success: { icon: CircleCheckFilled, class: 'is-success' },\n error: { icon: CircleClose, class: 'is-danger' },\n rejected: { icon: Close, class: 'is-danger' }\n}\n\nconst statusIcon = computed(() => STATUS_META[props.toolCall.status]?.icon ?? Loading)\nconst statusClass = computed(() => STATUS_META[props.toolCall.status]?.class ?? '')\n\n/** 头部图标:工具自定义图标优先,缺省用状态图标(与工具卡片一致) */\nconst headerIcon = computed(() => tool.value?.icon ?? statusIcon.value)\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":";;;;;;;;;;;;EAiCA,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,cAAkE;GACtE,SAAS;IAAE,MAAM;IAAS,OAAO;GAAY;GAC7C,SAAS;IAAE,MAAM;IAAS,OAAO;GAAY;GAC7C,oBAAoB;IAAE,MAAM;IAAe,OAAO;GAAa;GAC/D,SAAS;IAAE,MAAM;IAAmB,OAAO;GAAa;GACxD,OAAO;IAAE,MAAM;IAAa,OAAO;GAAY;GAC/C,UAAU;IAAE,MAAM;IAAO,OAAO;GAAY;EAC9C;EAEA,MAAM,aAAa,eAAe,YAAY,MAAM,SAAS,OAAO,EAAE,QAAQ,OAAO;EACrF,MAAM,cAAc,eAAe,YAAY,MAAM,SAAS,OAAO,EAAE,SAAS,EAAE;;EAGlF,MAAM,aAAa,eAAe,KAAK,OAAO,QAAQ,WAAW,KAAK;;EAGtE,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;;GAzEC,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":[],"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"}
@@ -14,7 +14,7 @@
14
14
  * --u-height-default: 32px;
15
15
  * --u-height-small: 24px;
16
16
  */
17
- /** 暗色:data-theme 与系统偏好,与 UITheme.injectBuiltInThemes 选择器策略一致 */
17
+ /** 暗色:`UITheme.render()` 按主题系列写入 html[data-theme] */
18
18
  .u-ai-chat {
19
19
  height: 100%;
20
20
  min-height: 0;
@@ -149,7 +149,7 @@
149
149
  .u-ai-chat__panel-fallback {
150
150
  margin: 0;
151
151
  padding: var(--u-gap-default);
152
- font-family: var(--u-font-family-code);
152
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
153
153
  font-size: var(--u-font-size-main-small);
154
154
  line-height: 1.5;
155
155
  white-space: pre-wrap;
@@ -241,6 +241,14 @@
241
241
  color: var(--u-color-primary);
242
242
  background-color: var(--u-color-primary-light-9);
243
243
  }
244
+ .u-ai-chat__welcome-item:disabled {
245
+ cursor: default;
246
+ color: var(--u-text-color-second);
247
+ }
248
+ .u-ai-chat__welcome-item:disabled:hover {
249
+ color: var(--u-text-color-second);
250
+ background-color: var(--u-bg-color-top);
251
+ }
244
252
  .u-ai-chat__welcome--swap-enter-active {
245
253
  transition: opacity 0.18s ease, transform 0.18s ease;
246
254
  }
@@ -528,17 +536,28 @@
528
536
  }
529
537
  .u-ai-chat__tool-call-section {
530
538
  color: var(--u-text-color-assist);
531
- margin-bottom: 4px;
532
539
  }
533
- .u-ai-chat__tool-call-section:not(:first-child) {
540
+ .u-ai-chat__tool-call-json:not(:first-child) {
534
541
  margin-top: var(--u-gap-small);
535
542
  }
543
+ .u-ai-chat__tool-call-json-head {
544
+ display: flex;
545
+ justify-content: flex-start;
546
+ align-items: center;
547
+ flex-wrap: nowrap;
548
+ gap: var(--u-gap-small);
549
+ margin-bottom: 4px;
550
+ }
551
+ .u-ai-chat__tool-call-json-toggle {
552
+ flex-shrink: 0;
553
+ margin-left: auto;
554
+ }
536
555
  .u-ai-chat__tool-call-code {
537
556
  margin: 0;
538
557
  padding: 4px var(--u-gap-small);
539
558
  border-radius: var(--u-radius-small);
540
559
  background-color: var(--u-bg-color-bottom);
541
- font-family: var(--u-font-family-code);
560
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
542
561
  font-size: var(--u-font-size-main-small);
543
562
  line-height: 1.5;
544
563
  white-space: pre-wrap;
@@ -766,6 +785,125 @@
766
785
  .u-ai-chat__queue-action--primary {
767
786
  color: var(--u-color-primary);
768
787
  }
788
+ .u-ai-chat__job-bar {
789
+ flex-shrink: 0;
790
+ width: 100%;
791
+ max-width: 800px;
792
+ box-sizing: border-box;
793
+ margin-bottom: 4px;
794
+ border-radius: var(--u-radius-default);
795
+ background-color: var(--u-bg-color-middle);
796
+ padding: 4px;
797
+ font-size: var(--u-font-size-main-small);
798
+ }
799
+ .u-ai-chat__job-bar-head {
800
+ display: flex;
801
+ justify-content: space-between;
802
+ align-items: center;
803
+ flex-wrap: nowrap;
804
+ width: 100%;
805
+ border: none;
806
+ background: none;
807
+ padding: 2px 6px;
808
+ color: var(--u-text-color-assist);
809
+ font-size: inherit;
810
+ font-family: inherit;
811
+ cursor: pointer;
812
+ }
813
+ .u-ai-chat__job-bar-chevron {
814
+ font-size: 12px;
815
+ transition: transform 0.2s;
816
+ }
817
+ .u-ai-chat__job-bar-chevron.is-expanded {
818
+ transform: rotate(90deg);
819
+ }
820
+ .u-ai-chat__job-bar-list {
821
+ display: flex;
822
+ flex-direction: column;
823
+ }
824
+ .u-ai-chat__job-bar-item {
825
+ display: flex;
826
+ justify-content: flex-start;
827
+ align-items: center;
828
+ flex-wrap: nowrap;
829
+ gap: 6px;
830
+ padding: 3px 6px;
831
+ border-radius: var(--u-radius-small);
832
+ }
833
+ .u-ai-chat__job-bar-icon {
834
+ flex-shrink: 0;
835
+ font-size: 13px;
836
+ color: var(--u-text-color-second);
837
+ }
838
+ .u-ai-chat__job-bar-label {
839
+ flex: 1;
840
+ min-width: 0;
841
+ overflow: hidden;
842
+ text-overflow: ellipsis;
843
+ white-space: nowrap;
844
+ color: var(--u-text-color-main);
845
+ }
846
+ .u-ai-chat__job-bar-dot {
847
+ flex-shrink: 0;
848
+ width: 6px;
849
+ height: 6px;
850
+ border-radius: 50%;
851
+ background-color: var(--u-text-color-placeholder);
852
+ }
853
+ .u-ai-chat__job-bar-dot.is-running {
854
+ background-color: var(--u-color-primary);
855
+ }
856
+ .u-ai-chat__job-bar-dot.is-done {
857
+ background-color: var(--u-color-success);
858
+ }
859
+ .u-ai-chat__job-bar-dot.is-error {
860
+ background-color: var(--u-color-danger);
861
+ }
862
+ .u-ai-chat__session-question {
863
+ flex-shrink: 0;
864
+ width: 100%;
865
+ max-width: 800px;
866
+ box-sizing: border-box;
867
+ margin-bottom: 4px;
868
+ border-radius: var(--u-radius-default);
869
+ background-color: var(--u-bg-color-top);
870
+ box-shadow: var(--u-shadow-sm);
871
+ padding: var(--u-gap-small);
872
+ }
873
+ .u-ai-chat__approval-banner {
874
+ flex-shrink: 0;
875
+ width: 100%;
876
+ max-width: 800px;
877
+ box-sizing: border-box;
878
+ margin-bottom: 4px;
879
+ }
880
+ .u-ai-chat__approval-banner-card {
881
+ padding: var(--u-gap-small);
882
+ border-radius: var(--u-radius-default);
883
+ background-color: var(--u-bg-color-top);
884
+ box-shadow: var(--u-shadow-sm);
885
+ }
886
+ .u-ai-chat__approval-banner-card + .u-ai-chat__approval-banner-card {
887
+ margin-top: 4px;
888
+ }
889
+ .u-ai-chat__approval-banner-title {
890
+ color: var(--u-text-color-title);
891
+ font-weight: 500;
892
+ font-size: var(--u-font-size-main-small);
893
+ }
894
+ .u-ai-chat__approval-banner-reason {
895
+ margin-top: 4px;
896
+ color: var(--u-text-color-assist);
897
+ font-size: var(--u-font-size-main-small);
898
+ }
899
+ .u-ai-chat__approval-banner-actions {
900
+ display: flex;
901
+ justify-content: flex-end;
902
+ align-items: center;
903
+ flex-wrap: nowrap;
904
+ gap: var(--u-gap-small);
905
+ margin-top: var(--u-gap-small);
906
+ }
769
907
  .u-ai-chat__input-area {
770
908
  flex-shrink: 0;
771
909
  width: 100%;
@@ -839,7 +977,7 @@
839
977
  transition: background-color 0.15s ease;
840
978
  }
841
979
  .u-ai-chat__model-trigger:hover {
842
- background-color: var(--u-nav-hover-bg);
980
+ background-color: var(--u-bg-color-hover);
843
981
  }
844
982
  .u-ai-chat__model-trigger-label {
845
983
  overflow: hidden;
@@ -941,7 +1079,7 @@
941
1079
  transition: background-color 0.15s ease;
942
1080
  }
943
1081
  .u-ai-chat__model-panel .u-ai-chat__model-option-main:hover {
944
- background-color: var(--u-nav-hover-bg);
1082
+ background-color: var(--u-bg-color-hover);
945
1083
  }
946
1084
  .u-ai-chat__model-panel .u-ai-chat__model-option-head {
947
1085
  display: flex;
@@ -1030,7 +1168,7 @@
1030
1168
  transition: background-color 0.15s ease, color 0.15s ease;
1031
1169
  }
1032
1170
  .u-ai-chat__model-panel .u-ai-chat__reasoning-option:hover {
1033
- background-color: var(--u-nav-hover-bg);
1171
+ background-color: var(--u-bg-color-hover);
1034
1172
  }
1035
1173
  .u-ai-chat__model-panel .u-ai-chat__reasoning-option.is-active {
1036
1174
  color: var(--u-color-primary);
@@ -1,6 +1,7 @@
1
1
  import { AiChatDIKey } from "./di.js";
2
+ import { resolveToolIcon } from "./tool-icons.js";
3
+ import _sfc_main$1 from "./tool-json.js";
2
4
  import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, inject, normalizeClass, openBlock, ref, resolveDynamicComponent, toDisplayString, unref, watch, withCtx, withKeys, withModifiers } from "vue";
3
- import { CircleCheckFilled, CircleClose, Close, Loading, WarningFilled } from "@veltra/icons/normal";
4
5
  import { UButton, UCollapseItem, UIcon } from "@veltra/desktop";
5
6
  import { bem } from "@veltra/utils";
6
7
  //#region src/components/ai-chat/tool-call.vue
@@ -14,7 +15,21 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
14
15
  const emit = __emit;
15
16
  const di = inject(AiChatDIKey);
16
17
  const cls = di?.cls ?? bem("ai-chat");
17
- /** 当前调用对应的工具定义(解析 icon/label/render/autoCollapse/terminal) */
18
+ /** 把未知值格式成可读 JSON,循环引用等异常时退回 String,不得 throw */
19
+ function formatUnknown(value) {
20
+ if (value == null) return "";
21
+ if (typeof value === "string") try {
22
+ return JSON.stringify(JSON.parse(value), null, 2);
23
+ } catch {
24
+ return value;
25
+ }
26
+ try {
27
+ return JSON.stringify(value, null, 2) ?? String(value);
28
+ } catch {
29
+ return String(value);
30
+ }
31
+ }
32
+ /** 当前调用对应的工具定义或渲染元信息(session 下忽略 execute / needsConfirm / terminal) */
18
33
  const tool = computed(() => di?.tools.value[props.toolCall.name]);
19
34
  const isExpanded = ref(false);
20
35
  /** 用户手动切换过后,不再随 status 自动展开/折叠 */
@@ -29,37 +44,18 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
29
44
  const autoCollapse = computed(() => tool.value?.autoCollapse ?? (isPanelTool.value || !tool.value?.render));
30
45
  watch(() => props.toolCall.status, (status) => {
31
46
  if (userToggled) return;
32
- if (status === "running" || status === "awaiting-confirm") isExpanded.value = true;
47
+ if (status === "pending" || status === "running" || status === "awaiting-confirm") isExpanded.value = true;
33
48
  else isExpanded.value = !autoCollapse.value;
34
49
  }, { immediate: true });
35
- const STATUS_META = {
36
- pending: {
37
- icon: Loading,
38
- class: "is-active"
39
- },
40
- running: {
41
- icon: Loading,
42
- class: "is-active"
43
- },
44
- "awaiting-confirm": {
45
- icon: WarningFilled,
46
- class: "is-warning"
47
- },
48
- success: {
49
- icon: CircleCheckFilled,
50
- class: "is-success"
51
- },
52
- error: {
53
- icon: CircleClose,
54
- class: "is-danger"
55
- },
56
- rejected: {
57
- icon: Close,
58
- class: "is-danger"
59
- }
50
+ const STATUS_CLASS = {
51
+ pending: "is-active",
52
+ running: "is-active",
53
+ "awaiting-confirm": "is-warning",
54
+ success: "is-success",
55
+ error: "is-danger",
56
+ rejected: "is-danger"
60
57
  };
61
- const statusIcon = computed(() => STATUS_META[props.toolCall.status]?.icon ?? Loading);
62
- const statusClass = computed(() => STATUS_META[props.toolCall.status]?.class ?? "");
58
+ const statusClass = computed(() => STATUS_CLASS[props.toolCall.status] ?? "");
63
59
  /** 进行中(pending/running):工具名带扫光效果 */
64
60
  const isActive = computed(() => {
65
61
  return props.toolCall.status === "pending" || props.toolCall.status === "running";
@@ -77,8 +73,8 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
77
73
  * 进行中/待确认要保留内容状态(确认按钮、表单),面板工具的 body 是「查看面板」入口必须可达
78
74
  */
79
75
  const destroyOnCollapse = computed(() => isSettled.value && !isPanelTool.value);
80
- /** 头部图标:工具自定义图标优先,缺省用状态图标 */
81
- const headerIcon = computed(() => tool.value?.icon ?? statusIcon.value);
76
+ /** 头部图标:meta.icon > 宿主覆盖 > 内置名称规则 > 兜底;状态点走 status class */
77
+ const headerIcon = computed(() => tool.value?.icon ?? resolveToolIcon(props.toolCall.name, di?.toolIcons?.value));
82
78
  const parsedArguments = computed(() => {
83
79
  if (!props.toolCall.arguments) return null;
84
80
  try {
@@ -102,13 +98,12 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
102
98
  const hasResult = computed(() => {
103
99
  return props.toolCall.result != null || props.toolCall.error != null;
104
100
  });
105
- const displayResult = computed(() => {
106
- const raw = props.toolCall.error ?? props.toolCall.result ?? "";
107
- try {
108
- return JSON.stringify(JSON.parse(raw), null, 2);
109
- } catch {
110
- return raw;
111
- }
101
+ const resultTitle = computed(() => props.toolCall.status === "error" ? "错误" : "结果");
102
+ const displayResult = computed(() => formatUnknown(props.toolCall.error ?? props.toolCall.result));
103
+ /** 服务端下发的 view,格式化为 JSON;不得丢弃、不得 throw */
104
+ const prettyView = computed(() => {
105
+ if (props.toolCall.view == null) return "";
106
+ return formatUnknown(props.toolCall.view);
112
107
  });
113
108
  /** 使用方通过 tool-<name> 插槽自定义该工具的展示(有结果时替换整个 body) */
114
109
  const slotBody = computed(() => {
@@ -165,7 +160,23 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
165
160
  }, {
166
161
  default: withCtx(() => [..._cache[9] || (_cache[9] = [createTextVNode(" 查看面板 ", -1)])]),
167
162
  _: 1
168
- })], 2)) : tool.value?.render ? (openBlock(), createElementBlock(Fragment, { key: 1 }, [createCommentVNode(" 自定义渲染:工具定义的 render 优先于 tool-<name> 插槽,均替换整个 body "), (openBlock(), createBlock(resolveDynamicComponent(tool.value.render), { "tool-call": __props.toolCall }, null, 8, ["tool-call"]))], 2112)) : slotBody.value && hasResult.value ? (openBlock(), createBlock(resolveDynamicComponent(slotBody.value), { key: 2 })) : (openBlock(), createElementBlock(Fragment, { key: 3 }, [prettyArguments.value ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [createElementVNode("div", { class: normalizeClass(unref(cls).e("tool-call-section")) }, "参数", 2), createElementVNode("pre", { class: normalizeClass(unref(cls).e("tool-call-code")) }, toDisplayString(prettyArguments.value), 3)], 64)) : createCommentVNode("v-if", true), hasResult.value ? (openBlock(), createElementBlock(Fragment, { key: 1 }, [createElementVNode("div", { class: normalizeClass(unref(cls).e("tool-call-section")) }, toDisplayString(__props.toolCall.status === "error" ? "错误" : "结果"), 3), createElementVNode("pre", { class: normalizeClass(unref(cls).e("tool-call-code")) }, toDisplayString(displayResult.value), 3)], 64)) : createCommentVNode("v-if", true)], 64))]),
163
+ })], 2)) : tool.value?.render ? (openBlock(), createElementBlock(Fragment, { key: 1 }, [createCommentVNode(" 自定义渲染:meta.render / tool-<name> 插槽优先于通用工具视图 "), (openBlock(), createBlock(resolveDynamicComponent(tool.value.render), { "tool-call": __props.toolCall }, null, 8, ["tool-call"]))], 2112)) : slotBody.value && hasResult.value ? (openBlock(), createBlock(resolveDynamicComponent(slotBody.value), { key: 2 })) : (openBlock(), createElementBlock(Fragment, { key: 3 }, [
164
+ prettyArguments.value ? (openBlock(), createBlock(_sfc_main$1, {
165
+ key: 0,
166
+ title: "参数",
167
+ text: prettyArguments.value
168
+ }, null, 8, ["text"])) : createCommentVNode("v-if", true),
169
+ hasResult.value ? (openBlock(), createBlock(_sfc_main$1, {
170
+ key: 1,
171
+ title: resultTitle.value,
172
+ text: displayResult.value
173
+ }, null, 8, ["title", "text"])) : createCommentVNode("v-if", true),
174
+ prettyView.value ? (openBlock(), createBlock(_sfc_main$1, {
175
+ key: 2,
176
+ title: "视图",
177
+ text: prettyView.value
178
+ }, null, 8, ["text"])) : createCommentVNode("v-if", true)
179
+ ], 64))]),
169
180
  _: 1
170
181
  }, 8, [
171
182
  "modelValue",
@@ -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 <!-- 自定义渲染:工具定义的 render 优先于 tool-<name> 插槽,均替换整个 body -->\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 <template v-if=\"prettyArguments\">\n <div :class=\"cls.e('tool-call-section')\">参数</div>\n <pre :class=\"cls.e('tool-call-code')\">{{ prettyArguments }}</pre>\n </template>\n\n <template v-if=\"hasResult\">\n <div :class=\"cls.e('tool-call-section')\">\n {{ toolCall.status === 'error' ? '错误' : '结果' }}\n </div>\n <pre :class=\"cls.e('tool-call-code')\">{{ displayResult }}</pre>\n </template>\n </template>\n </UCollapseItem>\n</template>\n\n<script lang=\"ts\" setup>\nimport { UButton, UCollapseItem, UIcon } from '@veltra/desktop'\nimport { CircleCheckFilled, CircleClose, Close, Loading, WarningFilled } from '@veltra/icons/normal'\nimport { bem } from '@veltra/utils'\nimport { computed, inject, ref, watch, type Component } from 'vue'\n\nimport type { ChatToolCall } from '../../chat/types'\nimport { AiChatDIKey } from './di'\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/** 当前调用对应的工具定义(解析 icon/label/render/autoCollapse/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 === 'running' || status === 'awaiting-confirm') {\n isExpanded.value = true\n } else {\n isExpanded.value = !autoCollapse.value\n }\n },\n { immediate: true }\n)\n\nconst STATUS_META: Record<string, { icon: Component; class: string }> = {\n pending: { icon: Loading, class: 'is-active' },\n running: { icon: Loading, class: 'is-active' },\n 'awaiting-confirm': { icon: WarningFilled, class: 'is-warning' },\n success: { icon: CircleCheckFilled, class: 'is-success' },\n error: { icon: CircleClose, class: 'is-danger' },\n rejected: { icon: Close, class: 'is-danger' }\n}\n\nconst statusIcon = computed(() => STATUS_META[props.toolCall.status]?.icon ?? Loading)\nconst statusClass = computed(() => STATUS_META[props.toolCall.status]?.class ?? '')\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/** 头部图标:工具自定义图标优先,缺省用状态图标 */\nconst headerIcon = computed(() => tool.value?.icon ?? statusIcon.value)\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 displayResult = 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\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/** 完成后是否自动折叠:缺省面板工具折叠;有 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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"}
@@ -0,0 +1,31 @@
1
+ import { EditPen, Folder, Internet, QuestionFilled, Terminal, Tools } from "@veltra/icons/normal";
2
+ //#region src/components/ai-chat/tool-icons.ts
3
+ /**
4
+ * 内置 toolName → icon:按名称子串匹配,顺序即优先级。
5
+ * 未命中任何规则时走兜底图标,不得 throw。
6
+ */
7
+ const TOOL_ICON_RULES = [
8
+ [["bash", "terminal"], Terminal],
9
+ [["fs", "file"], Folder],
10
+ [["web", "globe"], Internet],
11
+ [["edit", "pencil"], EditPen],
12
+ [["ask", "question"], QuestionFilled]
13
+ ];
14
+ /** 未知名工具的兜底图标 */
15
+ const FALLBACK_TOOL_ICON = Tools;
16
+ /**
17
+ * 按工具名解析图标。
18
+ * 宿主 `toolIcons` 精确名覆盖优先于内置规则;都未命中则兜底。
19
+ */
20
+ function resolveToolIcon(name, overrides) {
21
+ const key = String(name ?? "");
22
+ const fromOverride = overrides?.[key];
23
+ if (fromOverride) return fromOverride;
24
+ const lower = key.toLowerCase();
25
+ for (const [needles, icon] of TOOL_ICON_RULES) if (needles.some((needle) => lower.includes(needle))) return icon;
26
+ return FALLBACK_TOOL_ICON;
27
+ }
28
+ //#endregion
29
+ export { FALLBACK_TOOL_ICON, resolveToolIcon };
30
+
31
+ //# sourceMappingURL=tool-icons.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-icons.js","names":[],"sources":["../../../src/components/ai-chat/tool-icons.ts"],"sourcesContent":["import { EditPen, Folder, Internet, QuestionFilled, Terminal, Tools } from '@veltra/icons/normal'\nimport type { Component } from 'vue'\n\n/**\n * 内置 toolName → icon:按名称子串匹配,顺序即优先级。\n * 未命中任何规则时走兜底图标,不得 throw。\n */\nconst TOOL_ICON_RULES: [needles: string[], icon: Component][] = [\n [['bash', 'terminal'], Terminal],\n [['fs', 'file'], Folder],\n [['web', 'globe'], Internet],\n [['edit', 'pencil'], EditPen],\n [['ask', 'question'], QuestionFilled]\n]\n\n/** 未知名工具的兜底图标 */\nexport const FALLBACK_TOOL_ICON: Component = Tools\n\n/**\n * 按工具名解析图标。\n * 宿主 `toolIcons` 精确名覆盖优先于内置规则;都未命中则兜底。\n */\nexport function resolveToolIcon(name: string, overrides?: Record<string, Component>): Component {\n const key = String(name ?? '')\n const fromOverride = overrides?.[key]\n if (fromOverride) return fromOverride\n\n const lower = key.toLowerCase()\n for (const [needles, icon] of TOOL_ICON_RULES) {\n if (needles.some((needle) => lower.includes(needle))) return icon\n }\n return FALLBACK_TOOL_ICON\n}\n"],"mappings":";;;;;;AAOA,MAAM,kBAA0D;CAC9D,CAAC,CAAC,QAAQ,UAAU,GAAG,QAAQ;CAC/B,CAAC,CAAC,MAAM,MAAM,GAAG,MAAM;CACvB,CAAC,CAAC,OAAO,OAAO,GAAG,QAAQ;CAC3B,CAAC,CAAC,QAAQ,QAAQ,GAAG,OAAO;CAC5B,CAAC,CAAC,OAAO,UAAU,GAAG,cAAc;AACtC;;AAGA,MAAa,qBAAgC;;;;;AAM7C,SAAgB,gBAAgB,MAAc,WAAkD;CAC9F,MAAM,MAAM,OAAO,QAAQ,EAAE;CAC7B,MAAM,eAAe,YAAY;CACjC,IAAI,cAAc,OAAO;CAEzB,MAAM,QAAQ,IAAI,YAAY;CAC9B,KAAK,MAAM,CAAC,SAAS,SAAS,iBAC5B,IAAI,QAAQ,MAAM,WAAW,MAAM,SAAS,MAAM,CAAC,GAAG,OAAO;CAE/D,OAAO;AACT"}
@@ -0,0 +1,40 @@
1
+ import { AiChatDIKey } from "./di.js";
2
+ import { computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, defineComponent, inject, normalizeClass, openBlock, ref, toDisplayString, unref, withCtx } from "vue";
3
+ import { UButton } from "@veltra/desktop";
4
+ import { bem } from "@veltra/utils";
5
+ //#region src/components/ai-chat/tool-json.vue
6
+ const PREVIEW_LIMIT = 480;
7
+ const _sfc_main = /*@__PURE__*/ defineComponent({
8
+ name: "UAiChatToolJson",
9
+ __name: "tool-json",
10
+ props: {
11
+ title: {},
12
+ text: {}
13
+ },
14
+ setup(__props) {
15
+ const cls = inject(AiChatDIKey)?.cls ?? bem("ai-chat");
16
+ const expanded = ref(false);
17
+ const overLimit = computed(() => __props.text.length > PREVIEW_LIMIT);
18
+ const shown = computed(() => {
19
+ if (expanded.value || !overLimit.value) return __props.text;
20
+ return `${__props.text.slice(0, PREVIEW_LIMIT)}…`;
21
+ });
22
+ return (_ctx, _cache) => {
23
+ return openBlock(), createElementBlock("div", { class: normalizeClass(unref(cls).e("tool-call-json")) }, [createElementVNode("div", { class: normalizeClass(unref(cls).e("tool-call-json-head")) }, [createElementVNode("span", { class: normalizeClass(unref(cls).e("tool-call-section")) }, toDisplayString(__props.title), 3), overLimit.value ? (openBlock(), createBlock(unref(UButton), {
24
+ key: 0,
25
+ class: normalizeClass(unref(cls).e("tool-call-json-toggle")),
26
+ size: "small",
27
+ text: "",
28
+ type: "primary",
29
+ onClick: _cache[0] || (_cache[0] = ($event) => expanded.value = !expanded.value)
30
+ }, {
31
+ default: withCtx(() => [createTextVNode(toDisplayString(expanded.value ? "收起" : "展开"), 1)]),
32
+ _: 1
33
+ }, 8, ["class"])) : createCommentVNode("v-if", true)], 2), createElementVNode("pre", { class: normalizeClass([unref(cls).e("tool-call-code"), unref(bem).is("truncated", overLimit.value && !expanded.value)]) }, toDisplayString(shown.value), 3)], 2);
34
+ };
35
+ }
36
+ });
37
+ //#endregion
38
+ export { _sfc_main as default };
39
+
40
+ //# sourceMappingURL=tool-json.js.map
@@ -0,0 +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"}