@veltra/ai 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chat/index.d.ts +5 -0
- package/dist/chat/transports/index.d.ts +2 -0
- package/dist/chat/transports/openai.d.ts +23 -0
- package/dist/chat/transports/openai.js +176 -0
- package/dist/chat/transports/openai.js.map +1 -0
- package/dist/chat/types.d.ts +125 -0
- package/dist/chat/use-chat.d.ts +25 -0
- package/dist/chat/use-chat.js +214 -0
- package/dist/chat/use-chat.js.map +1 -0
- package/dist/components/ai-chat/ai-chat.js +88 -0
- package/dist/components/ai-chat/ai-chat.js.map +1 -0
- package/dist/components/ai-chat/ai-chat.vue.d.ts +36 -0
- package/dist/components/ai-chat/ask-question.js +187 -0
- package/dist/components/ai-chat/ask-question.js.map +1 -0
- package/dist/components/ai-chat/chat-input.js +143 -0
- package/dist/components/ai-chat/chat-input.js.map +1 -0
- package/dist/components/ai-chat/di.js +6 -0
- package/dist/components/ai-chat/di.js.map +1 -0
- package/dist/components/ai-chat/index.d.ts +2 -0
- package/dist/components/ai-chat/message-item.js +120 -0
- package/dist/components/ai-chat/message-item.js.map +1 -0
- package/dist/components/ai-chat/message-list.js +75 -0
- package/dist/components/ai-chat/message-list.js.map +1 -0
- package/dist/components/ai-chat/style.d.ts +5 -0
- package/dist/components/ai-chat/style.js +6 -0
- package/dist/components/ai-chat/style2.css +416 -0
- package/dist/components/ai-chat/tool-call.js +151 -0
- package/dist/components/ai-chat/tool-call.js.map +1 -0
- package/dist/components/index.d.ts +3 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +5 -0
- package/dist/style.d.ts +1 -0
- package/dist/style.js +1 -0
- package/dist/tools/ask-question/ask-question.d.ts +45 -0
- package/dist/tools/ask-question/ask-question.js +65 -0
- package/dist/tools/ask-question/ask-question.js.map +1 -0
- package/dist/tools/ask-question/deferred.js +22 -0
- package/dist/tools/ask-question/deferred.js.map +1 -0
- package/dist/tools/ask-question/index.d.ts +2 -0
- package/dist/tools/index.d.ts +3 -0
- package/dist/types/ai-chat.d.ts +49 -0
- package/dist/types/index.d.ts +2 -0
- package/package.json +40 -0
- package/src/chat/__test__/use-chat.test.ts +246 -0
- package/src/chat/index.ts +3 -0
- package/src/chat/transports/index.ts +1 -0
- package/src/chat/transports/openai.ts +237 -0
- package/src/chat/types.ts +136 -0
- package/src/chat/use-chat.ts +245 -0
- package/src/components/ai-chat/__test__/ai-chat.test.ts +316 -0
- package/src/components/ai-chat/ai-chat.vue +67 -0
- package/src/components/ai-chat/ask-question.vue +232 -0
- package/src/components/ai-chat/chat-input.vue +127 -0
- package/src/components/ai-chat/di.ts +15 -0
- package/src/components/ai-chat/index.ts +1 -0
- package/src/components/ai-chat/message-item.vue +120 -0
- package/src/components/ai-chat/message-list.vue +81 -0
- package/src/components/ai-chat/style.scss +439 -0
- package/src/components/ai-chat/style.ts +6 -0
- package/src/components/ai-chat/tool-call.vue +147 -0
- package/src/components/index.ts +1 -0
- package/src/env.d.ts +12 -0
- package/src/index.ts +4 -0
- package/src/style.ts +1 -0
- package/src/tools/ask-question/__test__/ask-question.test.ts +198 -0
- package/src/tools/ask-question/ask-question.ts +114 -0
- package/src/tools/ask-question/deferred.ts +27 -0
- package/src/tools/ask-question/index.ts +1 -0
- package/src/tools/index.ts +1 -0
- package/src/types/ai-chat.ts +56 -0
- package/src/types/index.ts +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message-item.js","names":[],"sources":["../../../src/components/ai-chat/message-item.vue"],"sourcesContent":["<template>\n <div\n :class=\"[cls.e('message'), cls.em('message', message.role), bem.is('streaming', isStreaming)]\"\n >\n <!-- 用户消息:右对齐气泡 -->\n <template v-if=\"message.role === 'user'\">\n <div :class=\"cls.e('message-bubble')\">\n <div v-if=\"message.attachments?.length\" :class=\"cls.e('message-attachments')\">\n <img\n v-for=\"att in message.attachments\"\n :key=\"att.dataUrl\"\n :src=\"att.dataUrl\"\n :alt=\"att.name\"\n :class=\"cls.e('message-attachment')\"\n />\n </div>\n <div v-if=\"message.content\" :class=\"cls.e('message-text')\">{{ message.content }}</div>\n </div>\n </template>\n\n <!-- assistant 消息 -->\n <template v-else>\n <div\n v-if=\"message.reasoning\"\n :class=\"[cls.e('reasoning'), bem.is('expanded', reasoningExpanded)]\"\n >\n <div :class=\"cls.e('reasoning-header')\" @click=\"reasoningExpanded = !reasoningExpanded\">\n <UIcon :class=\"[cls.e('reasoning-icon'), isThinking && cls.is('loading')]\">\n <Loading v-if=\"isThinking\" />\n <InfoCircle v-else />\n </UIcon>\n <span>{{ isThinking ? '思考中…' : '思考过程' }}</span>\n <UIcon :class=\"cls.e('reasoning-chevron')\"><ArrowDown /></UIcon>\n </div>\n <div v-show=\"reasoningExpanded\" :class=\"cls.e('reasoning-content')\">\n {{ message.reasoning }}\n </div>\n </div>\n\n <MarkdownRender\n v-if=\"message.content || isStreaming\"\n mode=\"chat\"\n :class=\"cls.e('message-content')\"\n :content=\"message.content\"\n :final=\"!isStreaming\"\n :fade=\"false\"\n v-bind=\"rendererProps\"\n />\n\n <ToolCallCard\n v-for=\"call in message.toolCalls ?? []\"\n :key=\"call.id\"\n :tool-call=\"call\"\n @respond=\"(approved) => emit('respond', call.id, approved)\"\n />\n\n <div v-if=\"message.status === 'error'\" :class=\"cls.e('message-status')\">生成出错</div>\n <div v-else-if=\"message.status === 'aborted'\" :class=\"cls.e('message-status')\">\n 已停止生成\n </div>\n\n <div v-if=\"showActions\" :class=\"cls.e('message-actions')\" @click=\"emit('regenerate')\">\n <UIcon title=\"重新生成\"><Refresh /></UIcon>\n </div>\n </template>\n </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { UIcon } from '@veltra/desktop'\nimport { ArrowDown, InfoCircle, Loading, Refresh } from '@veltra/icons/normal'\nimport { bem } from '@veltra/utils'\nimport MarkdownRender from 'markstream-vue'\nimport { computed, inject, ref, watch } from 'vue'\n\nimport type { ChatMessage } from '../../chat/types'\nimport { AiChatDIKey } from './di'\nimport ToolCallCard from './tool-call.vue'\n\ndefineOptions({ name: 'UAiChatMessageItem' })\n\nconst props = defineProps<{\n message: ChatMessage\n /** 是否为最后一条可见消息(决定是否显示重新生成) */\n isLast: boolean\n /** 透传给 MarkdownRender 的属性 */\n rendererProps?: Record<string, unknown>\n}>()\n\nconst emit = defineEmits<{\n (e: 'respond', toolCallId: string, approved: boolean): void\n (e: 'regenerate'): void\n}>()\n\nconst di = inject(AiChatDIKey)\nconst cls = di?.cls ?? bem('ai-chat')\n\nconst isStreaming = computed(() => props.message.status === 'streaming')\n/** 思考中:流式且正文还未开始输出 */\nconst isThinking = computed(() => isStreaming.value && !props.message.content)\n\nconst reasoningExpanded = ref(true)\n\n// 流式结束后自动折叠思考过程\nwatch(\n () => props.message.status,\n (status) => {\n if (status === 'streaming') {\n reasoningExpanded.value = true\n } else if (status) {\n reasoningExpanded.value = false\n }\n }\n)\n\nconst showActions = computed(() => {\n if (props.message.role !== 'assistant' || !props.isLast) return false\n return ['done', 'error', 'aborted'].includes(props.message.status ?? '')\n})\n</script>\n"],"mappings":"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"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { AiChatDIKey } from "./di.js";
|
|
2
|
+
import _sfc_main$1 from "./message-item.js";
|
|
3
|
+
import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createTextVNode, defineComponent, inject, nextTick, normalizeClass, openBlock, ref, renderList, renderSlot, shallowRef, toDisplayString, unref, watch, withCtx } from "vue";
|
|
4
|
+
import { bem } from "@veltra/utils";
|
|
5
|
+
import { UScroll } from "@veltra/desktop";
|
|
6
|
+
//#region src/components/ai-chat/message-list.vue
|
|
7
|
+
const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
8
|
+
name: "UAiChatMessageList",
|
|
9
|
+
__name: "message-list",
|
|
10
|
+
props: {
|
|
11
|
+
messages: {},
|
|
12
|
+
welcome: {},
|
|
13
|
+
rendererProps: {}
|
|
14
|
+
},
|
|
15
|
+
emits: ["respond", "regenerate"],
|
|
16
|
+
setup(__props, { emit: __emit }) {
|
|
17
|
+
const props = __props;
|
|
18
|
+
const emit = __emit;
|
|
19
|
+
const cls = inject(AiChatDIKey)?.cls ?? bem("ai-chat");
|
|
20
|
+
/** tool 消息不单独渲染,其内容体现在工具卡片的结果区 */
|
|
21
|
+
const visibleMessages = computed(() => props.messages.filter((msg) => msg.role !== "tool"));
|
|
22
|
+
const scrollRef = shallowRef();
|
|
23
|
+
/** 是否吸附底部(用户上翻浏览历史时取消吸附) */
|
|
24
|
+
const stickToBottom = ref(true);
|
|
25
|
+
const handleScroll = (position) => {
|
|
26
|
+
stickToBottom.value = position.sh - position.ch - position.y < 60;
|
|
27
|
+
};
|
|
28
|
+
watch(() => {
|
|
29
|
+
const list = visibleMessages.value;
|
|
30
|
+
const last = list[list.length - 1];
|
|
31
|
+
return [
|
|
32
|
+
list.length,
|
|
33
|
+
last?.content.length ?? 0,
|
|
34
|
+
last?.reasoning?.length ?? 0,
|
|
35
|
+
last?.toolCalls?.length ?? 0
|
|
36
|
+
].join("|");
|
|
37
|
+
}, async () => {
|
|
38
|
+
if (!stickToBottom.value) return;
|
|
39
|
+
await nextTick();
|
|
40
|
+
scrollRef.value?.scrollTo({ y: Number.MAX_SAFE_INTEGER });
|
|
41
|
+
});
|
|
42
|
+
return (_ctx, _cache) => {
|
|
43
|
+
return openBlock(), createBlock(unref(UScroll), {
|
|
44
|
+
class: normalizeClass(unref(cls).e("list")),
|
|
45
|
+
ref_key: "scrollRef",
|
|
46
|
+
ref: scrollRef,
|
|
47
|
+
always: "",
|
|
48
|
+
onScroll: handleScroll
|
|
49
|
+
}, {
|
|
50
|
+
default: withCtx(() => [!visibleMessages.value.length ? (openBlock(), createElementBlock("div", {
|
|
51
|
+
key: 0,
|
|
52
|
+
class: normalizeClass(unref(cls).e("welcome"))
|
|
53
|
+
}, [renderSlot(_ctx.$slots, "welcome", {}, () => [createTextVNode(toDisplayString(__props.welcome), 1)])], 2)) : createCommentVNode("v-if", true), (openBlock(true), createElementBlock(Fragment, null, renderList(visibleMessages.value, (msg, index) => {
|
|
54
|
+
return openBlock(), createBlock(_sfc_main$1, {
|
|
55
|
+
key: msg.id,
|
|
56
|
+
message: msg,
|
|
57
|
+
"is-last": index === visibleMessages.value.length - 1,
|
|
58
|
+
"renderer-props": __props.rendererProps,
|
|
59
|
+
onRespond: _cache[0] || (_cache[0] = (id, approved) => emit("respond", id, approved)),
|
|
60
|
+
onRegenerate: _cache[1] || (_cache[1] = ($event) => emit("regenerate"))
|
|
61
|
+
}, null, 8, [
|
|
62
|
+
"message",
|
|
63
|
+
"is-last",
|
|
64
|
+
"renderer-props"
|
|
65
|
+
]);
|
|
66
|
+
}), 128))]),
|
|
67
|
+
_: 3
|
|
68
|
+
}, 8, ["class"]);
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
//#endregion
|
|
73
|
+
export { _sfc_main as default };
|
|
74
|
+
|
|
75
|
+
//# sourceMappingURL=message-list.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message-list.js","names":[],"sources":["../../../src/components/ai-chat/message-list.vue"],"sourcesContent":["<template>\n <UScroll :class=\"cls.e('list')\" ref=\"scrollRef\" always @scroll=\"handleScroll\">\n <div v-if=\"!visibleMessages.length\" :class=\"cls.e('welcome')\">\n <slot name=\"welcome\">{{ welcome }}</slot>\n </div>\n\n <MessageItem\n v-for=\"(msg, index) in visibleMessages\"\n :key=\"msg.id\"\n :message=\"msg\"\n :is-last=\"index === visibleMessages.length - 1\"\n :renderer-props=\"rendererProps\"\n @respond=\"(id, approved) => emit('respond', id, approved)\"\n @regenerate=\"emit('regenerate')\"\n />\n </UScroll>\n</template>\n\n<script lang=\"ts\" setup>\nimport { UScroll } from '@veltra/desktop'\nimport type { ScrollPosition } from '@veltra/desktop'\nimport { bem } from '@veltra/utils'\nimport { computed, inject, nextTick, ref, shallowRef, watch } from 'vue'\n\nimport type { ChatMessage } from '../../chat/types'\nimport { AiChatDIKey } from './di'\nimport MessageItem from './message-item.vue'\n\ndefineOptions({ name: 'UAiChatMessageList' })\n\nconst props = defineProps<{\n messages: ChatMessage[]\n /** 空状态欢迎语 */\n welcome?: string\n /** 透传给 MarkdownRender 的属性 */\n rendererProps?: Record<string, unknown>\n}>()\n\nconst emit = defineEmits<{\n (e: 'respond', toolCallId: string, approved: boolean): void\n (e: 'regenerate'): void\n}>()\n\ndefineSlots<{\n /** 空状态欢迎区插槽 */\n welcome(): any\n}>()\n\nconst di = inject(AiChatDIKey)\nconst cls = di?.cls ?? bem('ai-chat')\n\n/** tool 消息不单独渲染,其内容体现在工具卡片的结果区 */\nconst visibleMessages = computed(() => props.messages.filter((msg) => msg.role !== 'tool'))\n\nconst scrollRef = shallowRef<{ scrollTo: (position: ScrollPosition) => void }>()\n/** 是否吸附底部(用户上翻浏览历史时取消吸附) */\nconst stickToBottom = ref(true)\n\nconst handleScroll = (position: Required<ScrollPosition>) => {\n stickToBottom.value = position.sh - position.ch - position.y < 60\n}\n\n// 消息数量或流式内容变化时,若处于吸附状态则滚动到底部\nwatch(\n () => {\n const list = visibleMessages.value\n const last = list[list.length - 1]\n return [\n list.length,\n last?.content.length ?? 0,\n last?.reasoning?.length ?? 0,\n last?.toolCalls?.length ?? 0\n ].join('|')\n },\n async () => {\n if (!stickToBottom.value) return\n await nextTick()\n scrollRef.value?.scrollTo({ y: Number.MAX_SAFE_INTEGER })\n }\n)\n</script>\n"],"mappings":";;;;;;;;;;;;;;;;EA8BA,MAAM,QAAQ;EAQd,MAAM,OAAO;EAWb,MAAM,MADK,OAAO,WACN,CAAA,EAAI,OAAO,IAAI,SAAS;;EAGpC,MAAM,kBAAkB,eAAe,MAAM,SAAS,QAAQ,QAAQ,IAAI,SAAS,MAAM,CAAC;EAE1F,MAAM,YAAY,WAA6D;;EAE/E,MAAM,gBAAgB,IAAI,IAAI;EAE9B,MAAM,gBAAgB,aAAuC;GAC3D,cAAc,QAAQ,SAAS,KAAK,SAAS,KAAK,SAAS,IAAI;EACjE;EAGA,YACQ;GACJ,MAAM,OAAO,gBAAgB;GAC7B,MAAM,OAAO,KAAK,KAAK,SAAS;GAChC,OAAO;IACL,KAAK;IACL,MAAM,QAAQ,UAAU;IACxB,MAAM,WAAW,UAAU;IAC3B,MAAM,WAAW,UAAU;GAC7B,CAAC,CAAC,KAAK,GAAG;EACZ,GACA,YAAY;GACV,IAAI,CAAC,cAAc,OAAO;GAC1B,MAAM,SAAS;GACf,UAAU,OAAO,SAAS,EAAE,GAAG,OAAO,iBAAiB,CAAC;EAC1D,CACF;;uBA9EE,YAcU,MAAA,OAAA,GAAA;IAdA,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,MAAA,CAAA;aAAc;IAAJ,KAAI;IAAY,QAAA;IAAQ,UAAQ;;2BAGxD,CAAA,CAFM,gBAAA,MAAgB,UAAA,UAAA,GAA5B,mBAEM,OAAA;;KAF+B,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,SAAA,CAAA;QAC/C,WAAyC,KAAA,QAAA,WAAA,CAAA,SAAA,CAAA,gBAAA,gBAAjB,QAAA,OAAO,GAAA,CAAA,CAAA,CAAA,CAAA,GAAA,CAAA,KAAA,mBAAA,QAAA,IAAA,IAAA,UAAA,IAAA,GAGjC,mBAQE,UAAA,MAAA,WAPuB,gBAAA,QAAf,KAAK,UAAK;yBADpB,YAQE,aAAA;MANC,KAAK,IAAI;MACT,SAAS;MACT,WAAS,UAAU,gBAAA,MAAgB,SAAM;MACzC,kBAAgB,QAAA;MAChB,WAAO,OAAA,OAAA,OAAA,MAAG,IAAI,aAAa,KAAI,WAAY,IAAI,QAAQ;MACvD,cAAU,OAAA,OAAA,OAAA,MAAA,WAAE,KAAI,YAAA"}
|
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
@charset "UTF-8";
|
|
2
|
+
/** flex布局 */
|
|
3
|
+
/**
|
|
4
|
+
* 设置css变量
|
|
5
|
+
* 参数1:变量前缀
|
|
6
|
+
* 参数2: 一个由值组成的列表或者由key, value组成的map
|
|
7
|
+
* css-var(height, (
|
|
8
|
+
* large: 40px
|
|
9
|
+
* default: 32px
|
|
10
|
+
* small: 24px
|
|
11
|
+
* ))
|
|
12
|
+
* 生成的css变量:
|
|
13
|
+
* --u-height-large: 40px;
|
|
14
|
+
* --u-height-default: 32px;
|
|
15
|
+
* --u-height-small: 24px;
|
|
16
|
+
*/
|
|
17
|
+
/** 暗色:data-theme 与系统偏好,与 UITheme.injectBuiltInThemes 选择器策略一致 */
|
|
18
|
+
.u-ai-chat {
|
|
19
|
+
display: flex;
|
|
20
|
+
flex-direction: column;
|
|
21
|
+
height: 100%;
|
|
22
|
+
min-height: 0;
|
|
23
|
+
font-size: var(--u-font-size-main-default);
|
|
24
|
+
color: var(--u-text-color-main);
|
|
25
|
+
}
|
|
26
|
+
.u-ai-chat__list {
|
|
27
|
+
flex: 1;
|
|
28
|
+
min-height: 0;
|
|
29
|
+
}
|
|
30
|
+
.u-ai-chat__welcome {
|
|
31
|
+
height: 100%;
|
|
32
|
+
display: flex;
|
|
33
|
+
justify-content: center;
|
|
34
|
+
align-items: center;
|
|
35
|
+
flex-wrap: nowrap;
|
|
36
|
+
color: var(--u-text-color-assist);
|
|
37
|
+
padding: var(--u-gap-default);
|
|
38
|
+
text-align: center;
|
|
39
|
+
}
|
|
40
|
+
.u-ai-chat__message {
|
|
41
|
+
padding: var(--u-gap-small) var(--u-gap-default);
|
|
42
|
+
}
|
|
43
|
+
.u-ai-chat__message--user {
|
|
44
|
+
display: flex;
|
|
45
|
+
justify-content: flex-end;
|
|
46
|
+
}
|
|
47
|
+
.u-ai-chat__message-bubble {
|
|
48
|
+
max-width: 80%;
|
|
49
|
+
padding: var(--u-gap-small) var(--u-gap-default);
|
|
50
|
+
border-radius: var(--u-radius-large);
|
|
51
|
+
background-color: var(--u-color-primary-light-9);
|
|
52
|
+
color: var(--u-text-color-main);
|
|
53
|
+
overflow: hidden;
|
|
54
|
+
}
|
|
55
|
+
.u-ai-chat__message-text {
|
|
56
|
+
white-space: pre-wrap;
|
|
57
|
+
word-break: break-word;
|
|
58
|
+
}
|
|
59
|
+
.u-ai-chat__message-attachments {
|
|
60
|
+
display: flex;
|
|
61
|
+
flex-wrap: wrap;
|
|
62
|
+
gap: var(--u-gap-small);
|
|
63
|
+
margin-bottom: var(--u-gap-small);
|
|
64
|
+
}
|
|
65
|
+
.u-ai-chat__message-attachment {
|
|
66
|
+
max-width: 160px;
|
|
67
|
+
max-height: 160px;
|
|
68
|
+
border-radius: var(--u-radius-default);
|
|
69
|
+
display: block;
|
|
70
|
+
}
|
|
71
|
+
.u-ai-chat__message-content {
|
|
72
|
+
line-height: 1.7;
|
|
73
|
+
}
|
|
74
|
+
.u-ai-chat__message-status {
|
|
75
|
+
margin-top: var(--u-gap-small);
|
|
76
|
+
color: var(--u-text-color-assist);
|
|
77
|
+
font-size: var(--u-font-size-main-small);
|
|
78
|
+
}
|
|
79
|
+
.u-ai-chat__message-actions {
|
|
80
|
+
margin-top: var(--u-gap-small);
|
|
81
|
+
color: var(--u-text-color-assist);
|
|
82
|
+
cursor: pointer;
|
|
83
|
+
display: inline-flex;
|
|
84
|
+
}
|
|
85
|
+
.u-ai-chat__message-actions:hover {
|
|
86
|
+
color: var(--u-color-primary);
|
|
87
|
+
}
|
|
88
|
+
.u-ai-chat__reasoning {
|
|
89
|
+
margin-bottom: var(--u-gap-small);
|
|
90
|
+
color: var(--u-text-color-assist);
|
|
91
|
+
}
|
|
92
|
+
.u-ai-chat__reasoning.is-expanded .u-ai-chat__reasoning-chevron {
|
|
93
|
+
transform: rotate(180deg);
|
|
94
|
+
}
|
|
95
|
+
.u-ai-chat__reasoning-header {
|
|
96
|
+
display: flex;
|
|
97
|
+
justify-content: flex-start;
|
|
98
|
+
align-items: center;
|
|
99
|
+
flex-wrap: nowrap;
|
|
100
|
+
gap: var(--u-gap-small);
|
|
101
|
+
cursor: pointer;
|
|
102
|
+
user-select: none;
|
|
103
|
+
font-size: var(--u-font-size-main-small);
|
|
104
|
+
}
|
|
105
|
+
.u-ai-chat__reasoning-chevron {
|
|
106
|
+
transition: transform 0.2s;
|
|
107
|
+
}
|
|
108
|
+
.u-ai-chat__reasoning-content {
|
|
109
|
+
margin-top: var(--u-gap-small);
|
|
110
|
+
padding-left: var(--u-gap-default);
|
|
111
|
+
border-left: var(--u-border-muted);
|
|
112
|
+
white-space: pre-wrap;
|
|
113
|
+
word-break: break-word;
|
|
114
|
+
font-size: var(--u-font-size-main-small);
|
|
115
|
+
line-height: 1.6;
|
|
116
|
+
}
|
|
117
|
+
.u-ai-chat__tool-call {
|
|
118
|
+
margin: var(--u-gap-small) 0;
|
|
119
|
+
}
|
|
120
|
+
.u-ai-chat__tool-call-status {
|
|
121
|
+
flex-shrink: 0;
|
|
122
|
+
}
|
|
123
|
+
.u-ai-chat__tool-call-status.is-loading {
|
|
124
|
+
color: var(--u-color-primary);
|
|
125
|
+
animation: u-ai-chat-rotate 1s linear infinite;
|
|
126
|
+
}
|
|
127
|
+
.u-ai-chat__tool-call-status.is-warning {
|
|
128
|
+
color: var(--u-color-warning);
|
|
129
|
+
}
|
|
130
|
+
.u-ai-chat__tool-call-status.is-success {
|
|
131
|
+
color: var(--u-color-success);
|
|
132
|
+
}
|
|
133
|
+
.u-ai-chat__tool-call-status.is-danger {
|
|
134
|
+
color: var(--u-color-danger);
|
|
135
|
+
}
|
|
136
|
+
.u-ai-chat__tool-call-name {
|
|
137
|
+
font-family: var(--u-font-family-code);
|
|
138
|
+
font-size: var(--u-font-size-main-small);
|
|
139
|
+
color: var(--u-text-color-title);
|
|
140
|
+
flex-shrink: 0;
|
|
141
|
+
}
|
|
142
|
+
.u-ai-chat__tool-call-summary {
|
|
143
|
+
flex: 1;
|
|
144
|
+
min-width: 0;
|
|
145
|
+
overflow: hidden;
|
|
146
|
+
text-overflow: ellipsis;
|
|
147
|
+
white-space: nowrap;
|
|
148
|
+
color: var(--u-text-color-assist);
|
|
149
|
+
font-size: var(--u-font-size-main-small);
|
|
150
|
+
}
|
|
151
|
+
.u-ai-chat__tool-call-confirm {
|
|
152
|
+
flex-shrink: 0;
|
|
153
|
+
display: flex;
|
|
154
|
+
justify-content: flex-start;
|
|
155
|
+
align-items: center;
|
|
156
|
+
flex-wrap: nowrap;
|
|
157
|
+
gap: var(--u-gap-small);
|
|
158
|
+
}
|
|
159
|
+
.u-ai-chat__tool-call-section {
|
|
160
|
+
font-size: var(--u-font-size-main-small);
|
|
161
|
+
color: var(--u-text-color-assist);
|
|
162
|
+
margin: var(--u-gap-small) 0;
|
|
163
|
+
}
|
|
164
|
+
.u-ai-chat__tool-call-code {
|
|
165
|
+
margin: 0;
|
|
166
|
+
padding: var(--u-gap-small);
|
|
167
|
+
border-radius: var(--u-radius-default);
|
|
168
|
+
background-color: var(--u-bg-color-bottom);
|
|
169
|
+
font-family: var(--u-font-family-code);
|
|
170
|
+
font-size: var(--u-font-size-main-small);
|
|
171
|
+
white-space: pre-wrap;
|
|
172
|
+
word-break: break-all;
|
|
173
|
+
max-height: 240px;
|
|
174
|
+
overflow: auto;
|
|
175
|
+
}
|
|
176
|
+
.u-ai-chat__ask-question {
|
|
177
|
+
display: flex;
|
|
178
|
+
flex-direction: column;
|
|
179
|
+
gap: var(--u-gap-small);
|
|
180
|
+
font-size: var(--u-font-size-main-small);
|
|
181
|
+
}
|
|
182
|
+
.u-ai-chat__ask-question-progress {
|
|
183
|
+
display: flex;
|
|
184
|
+
justify-content: space-between;
|
|
185
|
+
align-items: center;
|
|
186
|
+
flex-wrap: nowrap;
|
|
187
|
+
color: var(--u-text-color-assist);
|
|
188
|
+
}
|
|
189
|
+
.u-ai-chat__ask-question-dots {
|
|
190
|
+
display: flex;
|
|
191
|
+
justify-content: flex-start;
|
|
192
|
+
align-items: center;
|
|
193
|
+
flex-wrap: nowrap;
|
|
194
|
+
gap: 6px;
|
|
195
|
+
}
|
|
196
|
+
.u-ai-chat__ask-question-dot {
|
|
197
|
+
width: 6px;
|
|
198
|
+
height: 6px;
|
|
199
|
+
border-radius: 50%;
|
|
200
|
+
background-color: var(--u-text-color-placeholder);
|
|
201
|
+
cursor: pointer;
|
|
202
|
+
transition: background-color 0.2s, box-shadow 0.2s;
|
|
203
|
+
}
|
|
204
|
+
.u-ai-chat__ask-question-dot.is-answered {
|
|
205
|
+
background-color: var(--u-color-primary);
|
|
206
|
+
}
|
|
207
|
+
.u-ai-chat__ask-question-dot.is-current {
|
|
208
|
+
box-shadow: 0 0 0 3px var(--u-color-primary-light-7);
|
|
209
|
+
}
|
|
210
|
+
.u-ai-chat__ask-question-q {
|
|
211
|
+
display: flex;
|
|
212
|
+
justify-content: flex-start;
|
|
213
|
+
align-items: center;
|
|
214
|
+
flex-wrap: nowrap;
|
|
215
|
+
gap: var(--u-gap-small);
|
|
216
|
+
color: var(--u-text-color-title);
|
|
217
|
+
font-weight: 500;
|
|
218
|
+
line-height: 1.5;
|
|
219
|
+
}
|
|
220
|
+
.u-ai-chat__ask-question-no {
|
|
221
|
+
flex-shrink: 0;
|
|
222
|
+
width: 18px;
|
|
223
|
+
height: 18px;
|
|
224
|
+
border-radius: 50%;
|
|
225
|
+
display: flex;
|
|
226
|
+
justify-content: center;
|
|
227
|
+
align-items: center;
|
|
228
|
+
flex-wrap: nowrap;
|
|
229
|
+
background-color: var(--u-color-primary-light-9);
|
|
230
|
+
color: var(--u-color-primary);
|
|
231
|
+
font-size: 11px;
|
|
232
|
+
}
|
|
233
|
+
.u-ai-chat__ask-question-options {
|
|
234
|
+
display: flex;
|
|
235
|
+
flex-direction: column;
|
|
236
|
+
gap: var(--u-gap-small);
|
|
237
|
+
}
|
|
238
|
+
.u-ai-chat__ask-question-opt {
|
|
239
|
+
display: flex;
|
|
240
|
+
justify-content: flex-start;
|
|
241
|
+
align-items: center;
|
|
242
|
+
flex-wrap: nowrap;
|
|
243
|
+
gap: var(--u-gap-small);
|
|
244
|
+
padding: var(--u-gap-small) var(--u-gap-default);
|
|
245
|
+
border: var(--u-border-muted);
|
|
246
|
+
border-radius: var(--u-radius-default);
|
|
247
|
+
background: none;
|
|
248
|
+
cursor: pointer;
|
|
249
|
+
text-align: left;
|
|
250
|
+
color: var(--u-text-color-main);
|
|
251
|
+
font-size: inherit;
|
|
252
|
+
font-family: inherit;
|
|
253
|
+
line-height: 1.5;
|
|
254
|
+
transition: border-color 0.2s, background-color 0.2s;
|
|
255
|
+
}
|
|
256
|
+
.u-ai-chat__ask-question-opt:hover {
|
|
257
|
+
border-color: var(--u-color-primary);
|
|
258
|
+
}
|
|
259
|
+
.u-ai-chat__ask-question-opt.is-selected {
|
|
260
|
+
border-color: var(--u-color-primary);
|
|
261
|
+
background-color: var(--u-color-primary-light-9);
|
|
262
|
+
}
|
|
263
|
+
.u-ai-chat__ask-question-glyph {
|
|
264
|
+
flex-shrink: 0;
|
|
265
|
+
position: relative;
|
|
266
|
+
width: 14px;
|
|
267
|
+
height: 14px;
|
|
268
|
+
border-radius: 50%;
|
|
269
|
+
border: var(--u-border-muted);
|
|
270
|
+
transition: border-color 0.2s;
|
|
271
|
+
}
|
|
272
|
+
.u-ai-chat__ask-question-glyph.is-selected {
|
|
273
|
+
border-color: var(--u-color-primary);
|
|
274
|
+
}
|
|
275
|
+
.u-ai-chat__ask-question-glyph.is-selected::after {
|
|
276
|
+
content: "";
|
|
277
|
+
position: absolute;
|
|
278
|
+
inset: 3px;
|
|
279
|
+
border-radius: 50%;
|
|
280
|
+
background-color: var(--u-color-primary);
|
|
281
|
+
}
|
|
282
|
+
.u-ai-chat__ask-question-actions {
|
|
283
|
+
display: flex;
|
|
284
|
+
justify-content: flex-end;
|
|
285
|
+
align-items: center;
|
|
286
|
+
flex-wrap: nowrap;
|
|
287
|
+
gap: var(--u-gap-small);
|
|
288
|
+
}
|
|
289
|
+
.u-ai-chat__ask-question-result {
|
|
290
|
+
display: flex;
|
|
291
|
+
flex-direction: column;
|
|
292
|
+
gap: 4px;
|
|
293
|
+
}
|
|
294
|
+
.u-ai-chat__ask-question-result-q {
|
|
295
|
+
display: flex;
|
|
296
|
+
justify-content: flex-start;
|
|
297
|
+
align-items: center;
|
|
298
|
+
flex-wrap: nowrap;
|
|
299
|
+
gap: 6px;
|
|
300
|
+
color: var(--u-text-color-assist);
|
|
301
|
+
}
|
|
302
|
+
.u-ai-chat__ask-question-result-no {
|
|
303
|
+
flex-shrink: 0;
|
|
304
|
+
width: 16px;
|
|
305
|
+
height: 16px;
|
|
306
|
+
border-radius: 50%;
|
|
307
|
+
display: flex;
|
|
308
|
+
justify-content: center;
|
|
309
|
+
align-items: center;
|
|
310
|
+
flex-wrap: nowrap;
|
|
311
|
+
background-color: var(--u-color-primary-light-9);
|
|
312
|
+
color: var(--u-color-primary);
|
|
313
|
+
font-size: 11px;
|
|
314
|
+
}
|
|
315
|
+
.u-ai-chat__ask-question-result-a {
|
|
316
|
+
color: var(--u-text-color-main);
|
|
317
|
+
line-height: 1.5;
|
|
318
|
+
}
|
|
319
|
+
.u-ai-chat__ask-question-chip {
|
|
320
|
+
display: inline-block;
|
|
321
|
+
padding: 2px var(--u-gap-small);
|
|
322
|
+
border-radius: var(--u-radius-default);
|
|
323
|
+
background-color: var(--u-color-primary-light-9);
|
|
324
|
+
color: var(--u-color-primary);
|
|
325
|
+
}
|
|
326
|
+
.u-ai-chat__ask-question-status {
|
|
327
|
+
color: var(--u-text-color-assist);
|
|
328
|
+
}
|
|
329
|
+
.u-ai-chat__input-area {
|
|
330
|
+
flex-shrink: 0;
|
|
331
|
+
margin: var(--u-gap-default);
|
|
332
|
+
margin-top: var(--u-gap-small);
|
|
333
|
+
border: var(--u-border-muted);
|
|
334
|
+
border-radius: var(--u-radius-large);
|
|
335
|
+
background-color: var(--u-bg-color-top);
|
|
336
|
+
padding: var(--u-gap-small);
|
|
337
|
+
}
|
|
338
|
+
.u-ai-chat__input-area:focus-within {
|
|
339
|
+
border-color: var(--u-color-primary);
|
|
340
|
+
}
|
|
341
|
+
.u-ai-chat__input {
|
|
342
|
+
width: 100%;
|
|
343
|
+
border: none;
|
|
344
|
+
outline: none;
|
|
345
|
+
resize: none;
|
|
346
|
+
background: transparent;
|
|
347
|
+
color: var(--u-text-color-main);
|
|
348
|
+
font-size: var(--u-font-size-main-default);
|
|
349
|
+
font-family: inherit;
|
|
350
|
+
line-height: 1.6;
|
|
351
|
+
padding: var(--u-gap-small);
|
|
352
|
+
box-sizing: border-box;
|
|
353
|
+
}
|
|
354
|
+
.u-ai-chat__input::placeholder {
|
|
355
|
+
color: var(--u-text-color-placeholder);
|
|
356
|
+
}
|
|
357
|
+
.u-ai-chat__input-toolbar {
|
|
358
|
+
display: flex;
|
|
359
|
+
justify-content: space-between;
|
|
360
|
+
align-items: center;
|
|
361
|
+
flex-wrap: nowrap;
|
|
362
|
+
padding: 0 var(--u-gap-small);
|
|
363
|
+
}
|
|
364
|
+
.u-ai-chat__input-attach {
|
|
365
|
+
color: var(--u-text-color-assist);
|
|
366
|
+
cursor: pointer;
|
|
367
|
+
font-size: 18px;
|
|
368
|
+
}
|
|
369
|
+
.u-ai-chat__input-attach:hover {
|
|
370
|
+
color: var(--u-color-primary);
|
|
371
|
+
}
|
|
372
|
+
.u-ai-chat__input-stop {
|
|
373
|
+
display: block;
|
|
374
|
+
width: 10px;
|
|
375
|
+
height: 10px;
|
|
376
|
+
border-radius: 2px;
|
|
377
|
+
background-color: currentColor;
|
|
378
|
+
}
|
|
379
|
+
.u-ai-chat__input-attachments {
|
|
380
|
+
display: flex;
|
|
381
|
+
flex-wrap: wrap;
|
|
382
|
+
gap: var(--u-gap-small);
|
|
383
|
+
padding: var(--u-gap-small);
|
|
384
|
+
}
|
|
385
|
+
.u-ai-chat__input-attachment {
|
|
386
|
+
position: relative;
|
|
387
|
+
width: 48px;
|
|
388
|
+
height: 48px;
|
|
389
|
+
border-radius: var(--u-radius-default);
|
|
390
|
+
overflow: hidden;
|
|
391
|
+
border: var(--u-border-muted);
|
|
392
|
+
}
|
|
393
|
+
.u-ai-chat__input-attachment img {
|
|
394
|
+
width: 100%;
|
|
395
|
+
height: 100%;
|
|
396
|
+
object-fit: cover;
|
|
397
|
+
display: block;
|
|
398
|
+
}
|
|
399
|
+
.u-ai-chat__input-attachment-remove {
|
|
400
|
+
position: absolute;
|
|
401
|
+
top: 0;
|
|
402
|
+
right: 0;
|
|
403
|
+
background-color: var(--u-shadow-color-a-52);
|
|
404
|
+
color: var(--u-text-color-white);
|
|
405
|
+
cursor: pointer;
|
|
406
|
+
font-size: 12px;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
@keyframes u-ai-chat-rotate {
|
|
410
|
+
from {
|
|
411
|
+
transform: rotate(0deg);
|
|
412
|
+
}
|
|
413
|
+
to {
|
|
414
|
+
transform: rotate(360deg);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { AiChatDIKey } from "./di.js";
|
|
2
|
+
import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, inject, normalizeClass, openBlock, ref, resolveDynamicComponent, toDisplayString, unref, watch, withCtx, withModifiers } from "vue";
|
|
3
|
+
import { bem } from "@veltra/utils";
|
|
4
|
+
import { UButton, UCollapseItem, UIcon } from "@veltra/desktop";
|
|
5
|
+
import { CircleCheckFilled, CircleClose, Close, Loading, WarningFilled } from "@veltra/icons/normal";
|
|
6
|
+
import "@veltra/desktop/components/collapse/style";
|
|
7
|
+
//#region src/components/ai-chat/tool-call.vue
|
|
8
|
+
const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
9
|
+
name: "UAiChatToolCall",
|
|
10
|
+
__name: "tool-call",
|
|
11
|
+
props: { toolCall: {} },
|
|
12
|
+
emits: ["respond"],
|
|
13
|
+
setup(__props, { emit: __emit }) {
|
|
14
|
+
const props = __props;
|
|
15
|
+
const emit = __emit;
|
|
16
|
+
const di = inject(AiChatDIKey);
|
|
17
|
+
const cls = di?.cls ?? bem("ai-chat");
|
|
18
|
+
/** 当前调用对应的工具定义(解析 icon/label/render/autoCollapse) */
|
|
19
|
+
const tool = computed(() => di?.tools.value[props.toolCall.name]);
|
|
20
|
+
const isExpanded = ref(false);
|
|
21
|
+
/** 用户手动切换过后,不再随 status 自动展开/折叠 */
|
|
22
|
+
let userToggled = false;
|
|
23
|
+
function markUserToggled() {
|
|
24
|
+
userToggled = true;
|
|
25
|
+
}
|
|
26
|
+
/** 完成后是否自动折叠:缺省有 render 时不折叠,否则折叠 */
|
|
27
|
+
const autoCollapse = computed(() => tool.value?.autoCollapse ?? !tool.value?.render);
|
|
28
|
+
watch(() => props.toolCall.status, (status) => {
|
|
29
|
+
if (userToggled) return;
|
|
30
|
+
if (status === "running" || status === "awaiting-confirm") isExpanded.value = true;
|
|
31
|
+
else isExpanded.value = !autoCollapse.value;
|
|
32
|
+
}, { immediate: true });
|
|
33
|
+
const STATUS_META = {
|
|
34
|
+
pending: {
|
|
35
|
+
icon: Loading,
|
|
36
|
+
class: "is-loading"
|
|
37
|
+
},
|
|
38
|
+
running: {
|
|
39
|
+
icon: Loading,
|
|
40
|
+
class: "is-loading"
|
|
41
|
+
},
|
|
42
|
+
"awaiting-confirm": {
|
|
43
|
+
icon: WarningFilled,
|
|
44
|
+
class: "is-warning"
|
|
45
|
+
},
|
|
46
|
+
success: {
|
|
47
|
+
icon: CircleCheckFilled,
|
|
48
|
+
class: "is-success"
|
|
49
|
+
},
|
|
50
|
+
error: {
|
|
51
|
+
icon: CircleClose,
|
|
52
|
+
class: "is-danger"
|
|
53
|
+
},
|
|
54
|
+
rejected: {
|
|
55
|
+
icon: Close,
|
|
56
|
+
class: "is-danger"
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
const statusIcon = computed(() => STATUS_META[props.toolCall.status]?.icon ?? Loading);
|
|
60
|
+
const statusClass = computed(() => STATUS_META[props.toolCall.status]?.class ?? "");
|
|
61
|
+
/** 头部图标:工具自定义图标优先,缺省用状态图标 */
|
|
62
|
+
const headerIcon = computed(() => tool.value?.icon ?? statusIcon.value);
|
|
63
|
+
const parsedArguments = computed(() => {
|
|
64
|
+
if (!props.toolCall.arguments) return null;
|
|
65
|
+
try {
|
|
66
|
+
return JSON.parse(props.toolCall.arguments);
|
|
67
|
+
} catch {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
/** 折叠态参数摘要:取第一个字符串参数值 */
|
|
72
|
+
const summary = computed(() => {
|
|
73
|
+
const args = parsedArguments.value;
|
|
74
|
+
if (!args) return "";
|
|
75
|
+
for (const value of Object.values(args)) if (typeof value === "string" && value) return value.length > 40 ? `${value.slice(0, 40)}…` : value;
|
|
76
|
+
return "";
|
|
77
|
+
});
|
|
78
|
+
const prettyArguments = computed(() => {
|
|
79
|
+
const args = parsedArguments.value;
|
|
80
|
+
if (args) return JSON.stringify(args, null, 2);
|
|
81
|
+
return props.toolCall.arguments || "";
|
|
82
|
+
});
|
|
83
|
+
const hasResult = computed(() => {
|
|
84
|
+
return props.toolCall.result != null || props.toolCall.error != null;
|
|
85
|
+
});
|
|
86
|
+
const displayResult = computed(() => {
|
|
87
|
+
const raw = props.toolCall.error ?? props.toolCall.result ?? "";
|
|
88
|
+
try {
|
|
89
|
+
return JSON.stringify(JSON.parse(raw), null, 2);
|
|
90
|
+
} catch {
|
|
91
|
+
return raw;
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
/** 使用方通过 tool-<name> 插槽自定义该工具的展示(有结果时替换整个 body) */
|
|
95
|
+
const slotBody = computed(() => {
|
|
96
|
+
const slot = di?.slots[`tool-${props.toolCall.name}`];
|
|
97
|
+
return slot ? () => slot({ toolCall: props.toolCall }) : void 0;
|
|
98
|
+
});
|
|
99
|
+
return (_ctx, _cache) => {
|
|
100
|
+
return openBlock(), createElementBlock("div", { class: normalizeClass([
|
|
101
|
+
unref(cls).e("tool-call"),
|
|
102
|
+
unref(bem).is(__props.toolCall.status),
|
|
103
|
+
unref(bem).is("expanded", isExpanded.value)
|
|
104
|
+
]) }, [createVNode(unref(UCollapseItem), {
|
|
105
|
+
modelValue: isExpanded.value,
|
|
106
|
+
"onUpdate:modelValue": _cache[3] || (_cache[3] = ($event) => isExpanded.value = $event),
|
|
107
|
+
onChange: markUserToggled
|
|
108
|
+
}, {
|
|
109
|
+
title: withCtx(() => [
|
|
110
|
+
createVNode(unref(UIcon), { class: normalizeClass([unref(cls).e("tool-call-status"), statusClass.value]) }, {
|
|
111
|
+
default: withCtx(() => [(openBlock(), createBlock(resolveDynamicComponent(headerIcon.value)))]),
|
|
112
|
+
_: 1
|
|
113
|
+
}, 8, ["class"]),
|
|
114
|
+
createElementVNode("span", { class: normalizeClass(unref(cls).e("tool-call-name")) }, toDisplayString(tool.value?.label ?? __props.toolCall.name), 3),
|
|
115
|
+
summary.value ? (openBlock(), createElementBlock("span", {
|
|
116
|
+
key: 0,
|
|
117
|
+
class: normalizeClass(unref(cls).e("tool-call-summary"))
|
|
118
|
+
}, toDisplayString(summary.value), 3)) : createCommentVNode("v-if", true),
|
|
119
|
+
__props.toolCall.status === "awaiting-confirm" ? (openBlock(), createElementBlock("span", {
|
|
120
|
+
key: 1,
|
|
121
|
+
class: normalizeClass(unref(cls).e("tool-call-confirm")),
|
|
122
|
+
onClick: _cache[2] || (_cache[2] = withModifiers(() => {}, ["stop"]))
|
|
123
|
+
}, [createVNode(unref(UButton), {
|
|
124
|
+
size: "small",
|
|
125
|
+
type: "primary",
|
|
126
|
+
onClick: _cache[0] || (_cache[0] = ($event) => emit("respond", true))
|
|
127
|
+
}, {
|
|
128
|
+
default: withCtx(() => [..._cache[4] || (_cache[4] = [createTextVNode("允许", -1)])]),
|
|
129
|
+
_: 1
|
|
130
|
+
}), createVNode(unref(UButton), {
|
|
131
|
+
size: "small",
|
|
132
|
+
text: "",
|
|
133
|
+
onClick: _cache[1] || (_cache[1] = ($event) => emit("respond", false))
|
|
134
|
+
}, {
|
|
135
|
+
default: withCtx(() => [..._cache[5] || (_cache[5] = [createTextVNode("拒绝", -1)])]),
|
|
136
|
+
_: 1
|
|
137
|
+
})], 2)) : createCommentVNode("v-if", true)
|
|
138
|
+
]),
|
|
139
|
+
default: withCtx(() => [tool.value?.render ? (openBlock(), createBlock(resolveDynamicComponent(tool.value?.render), {
|
|
140
|
+
key: 0,
|
|
141
|
+
"tool-call": __props.toolCall
|
|
142
|
+
}, null, 8, ["tool-call"])) : slotBody.value && hasResult.value ? (openBlock(), createBlock(resolveDynamicComponent(slotBody.value), { key: 1 })) : (openBlock(), createElementBlock(Fragment, { key: 2 }, [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))]),
|
|
143
|
+
_: 1
|
|
144
|
+
}, 8, ["modelValue"])], 2);
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
//#endregion
|
|
149
|
+
export { _sfc_main as default };
|
|
150
|
+
|
|
151
|
+
//# sourceMappingURL=tool-call.js.map
|