@veltra/ai 2.1.6 → 2.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -0
- package/dist/chat/fold.d.ts +41 -0
- package/dist/chat/fold.js +208 -0
- package/dist/chat/fold.js.map +1 -0
- package/dist/chat/index.d.ts +4 -2
- package/dist/chat/session.d.ts +123 -0
- package/dist/chat/session.js +79 -0
- package/dist/chat/session.js.map +1 -0
- package/dist/chat/types.d.ts +38 -1
- package/dist/chat/use-chat-helpers.js +39 -0
- package/dist/chat/use-chat-helpers.js.map +1 -0
- package/dist/chat/use-chat-session.js +68 -0
- package/dist/chat/use-chat-session.js.map +1 -0
- package/dist/chat/use-chat.d.ts +50 -2
- package/dist/chat/use-chat.js +73 -39
- package/dist/chat/use-chat.js.map +1 -1
- package/dist/components/ai-chat/ai-chat.js +49 -14
- package/dist/components/ai-chat/ai-chat.js.map +1 -1
- package/dist/components/ai-chat/approval-banner.js +54 -0
- package/dist/components/ai-chat/approval-banner.js.map +1 -0
- package/dist/components/ai-chat/ask-question.js +32 -51
- package/dist/components/ai-chat/ask-question.js.map +1 -1
- package/dist/components/ai-chat/di.js.map +1 -1
- package/dist/components/ai-chat/job-bar.js +48 -0
- package/dist/components/ai-chat/job-bar.js.map +1 -0
- package/dist/components/ai-chat/message-list.js +5 -2
- package/dist/components/ai-chat/message-list.js.map +1 -1
- package/dist/components/ai-chat/queue-list.js +9 -3
- package/dist/components/ai-chat/queue-list.js.map +1 -1
- package/dist/components/ai-chat/side-panel.js +12 -30
- package/dist/components/ai-chat/side-panel.js.map +1 -1
- package/dist/components/ai-chat/style2.css +146 -8
- package/dist/components/ai-chat/tool-call.js +51 -40
- package/dist/components/ai-chat/tool-call.js.map +1 -1
- package/dist/components/ai-chat/tool-icons.js +31 -0
- package/dist/components/ai-chat/tool-icons.js.map +1 -0
- package/dist/components/ai-chat/tool-json.js +40 -0
- package/dist/components/ai-chat/tool-json.js.map +1 -0
- package/dist/components/ai-orb/style2.css +1 -1
- package/dist/index.d.ts +6 -4
- package/dist/index.js +3 -1
- package/dist/tools/ask-question/ask-question.js +48 -2
- package/dist/tools/ask-question/ask-question.js.map +1 -1
- package/dist/types/ai-chat.d.ts +17 -6
- package/package.json +7 -7
- package/src/chat/__test__/fold.test.ts +136 -0
- package/src/chat/__test__/session.test.ts +191 -0
- package/src/chat/__test__/use-chat.test.ts +268 -1
- package/src/chat/fold.ts +260 -0
- package/src/chat/index.ts +2 -0
- package/src/chat/session.ts +168 -0
- package/src/chat/types.ts +40 -0
- package/src/chat/use-chat-helpers.ts +39 -0
- package/src/chat/use-chat-session.ts +87 -0
- package/src/chat/use-chat.ts +90 -44
- package/src/components/ai-chat/__test__/ai-chat.test.ts +405 -7
- package/src/components/ai-chat/__test__/tool-icons.test.ts +33 -0
- package/src/components/ai-chat/ai-chat.vue +39 -4
- package/src/components/ai-chat/approval-banner.vue +37 -0
- package/src/components/ai-chat/ask-question.vue +38 -54
- package/src/components/ai-chat/di.ts +6 -4
- package/src/components/ai-chat/job-bar.vue +41 -0
- package/src/components/ai-chat/message-list.vue +4 -1
- package/src/components/ai-chat/queue-list.vue +3 -1
- package/src/components/ai-chat/side-panel.vue +15 -13
- package/src/components/ai-chat/style.scss +158 -6
- package/src/components/ai-chat/tool-call.vue +46 -34
- package/src/components/ai-chat/tool-icons.ts +33 -0
- package/src/components/ai-chat/tool-json.vue +52 -0
- package/src/tools/ask-question/ask-question.ts +53 -4
- 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: {
|
|
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),
|
|
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":"
|
|
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 {
|
|
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
|
|
20
|
-
pending:
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
|
46
|
-
|
|
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 {
|
|
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
|
-
/**
|
|
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:
|
|
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-
|
|
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:
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
-
/**
|
|
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
|
|
36
|
-
pending:
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
|
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 ??
|
|
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
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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("
|
|
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"}
|