@veltra/ai 1.4.0 → 2.0.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 +2 -2
- package/dist/chat/transports/index.d.ts +2 -2
- package/dist/chat/transports/openai.d.ts +16 -12
- package/dist/chat/transports/openai.js +55 -14
- package/dist/chat/transports/openai.js.map +1 -1
- package/dist/chat/types.d.ts +4 -0
- package/dist/chat/use-chat.d.ts +3 -0
- package/dist/chat/use-chat.js +49 -5
- package/dist/chat/use-chat.js.map +1 -1
- package/dist/components/ai-chat/ai-chat.js +27 -7
- package/dist/components/ai-chat/ai-chat.js.map +1 -1
- package/dist/components/ai-chat/ask-question.js +2 -2
- package/dist/components/ai-chat/ask-question.js.map +1 -1
- package/dist/components/ai-chat/chat-input.js +50 -38
- package/dist/components/ai-chat/chat-input.js.map +1 -1
- package/dist/components/ai-chat/message-item.js +2 -2
- package/dist/components/ai-chat/message-item.js.map +1 -1
- package/dist/components/ai-chat/message-list.js +1 -1
- package/dist/components/ai-chat/message-list.js.map +1 -1
- package/dist/components/ai-chat/model-picker.js +117 -0
- package/dist/components/ai-chat/model-picker.js.map +1 -0
- package/dist/components/ai-chat/style.d.ts +2 -0
- package/dist/components/ai-chat/style.js +2 -0
- package/dist/components/ai-chat/style2.css +128 -0
- package/dist/components/ai-chat/tool-call.js +2 -2
- package/dist/components/ai-chat/tool-call.js.map +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.js +1 -2
- package/dist/providers/types.d.ts +47 -0
- package/dist/tools/ask-question/ask-question.d.ts +2 -17
- package/dist/tools/ask-question/index.d.ts +2 -2
- package/dist/tools/index.d.ts +3 -2
- package/dist/tools/index.js +10 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/types/ai-chat.d.ts +14 -0
- package/package.json +8 -8
- package/src/chat/__test__/use-chat.test.ts +71 -0
- package/src/chat/transports/__test__/openai.test.ts +239 -0
- package/src/chat/transports/openai.ts +86 -24
- package/src/chat/types.ts +4 -0
- package/src/chat/use-chat.ts +67 -7
- package/src/components/ai-chat/__test__/ai-chat.test.ts +102 -1
- package/src/components/ai-chat/ai-chat.vue +22 -8
- package/src/components/ai-chat/chat-input.vue +43 -26
- package/src/components/ai-chat/model-picker.vue +126 -0
- package/src/components/ai-chat/style.scss +125 -0
- package/src/components/ai-chat/style.ts +2 -0
- package/src/index.ts +8 -1
- package/src/providers/index.ts +1 -0
- package/src/providers/types.ts +46 -0
- package/src/tools/ask-question/__test__/ask-question.test.ts +3 -3
- package/src/tools/index.ts +14 -1
- package/src/types/ai-chat.ts +14 -0
- package/dist/components/ai-chat/ai-chat.vue.d.ts +0 -36
- package/dist/components/ai-chat/index.d.ts +0 -2
- package/dist/components/index.d.ts +0 -3
|
@@ -1,33 +1,41 @@
|
|
|
1
1
|
import { AiChatDIKey } from "./di.js";
|
|
2
|
-
import
|
|
2
|
+
import _sfc_main$1 from "./model-picker.js";
|
|
3
|
+
import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, inject, mergeModels, normalizeClass, openBlock, ref, renderList, shallowRef, unref, useModel, vModelText, withCtx, withDirectives, withKeys, withModifiers } from "vue";
|
|
4
|
+
import { Attach, Close, Up } from "@veltra/icons/normal";
|
|
5
|
+
import { UButton, UFilePicker, UIcon } from "@veltra/desktop";
|
|
3
6
|
import { bem } from "@veltra/utils";
|
|
4
|
-
import { UButton, UIcon } from "@veltra/desktop";
|
|
5
|
-
import { Attach, Close, Send } from "@veltra/icons/normal";
|
|
6
7
|
//#region src/components/ai-chat/chat-input.vue
|
|
7
8
|
const _hoisted_1 = ["src", "alt"];
|
|
8
9
|
const _hoisted_2 = ["placeholder", "onKeydown"];
|
|
9
|
-
const _hoisted_3 = ["accept"];
|
|
10
10
|
const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
11
11
|
name: "UAiChatInput",
|
|
12
12
|
__name: "chat-input",
|
|
13
|
-
props: {
|
|
13
|
+
props: /*@__PURE__*/ mergeModels({
|
|
14
14
|
running: { type: Boolean },
|
|
15
|
+
models: {},
|
|
15
16
|
placeholder: {},
|
|
16
17
|
accept: {},
|
|
17
18
|
maxAttachmentSize: {}
|
|
18
|
-
},
|
|
19
|
-
|
|
19
|
+
}, {
|
|
20
|
+
"model": {},
|
|
21
|
+
"modelModifiers": {},
|
|
22
|
+
"reasoningLevel": {},
|
|
23
|
+
"reasoningLevelModifiers": {}
|
|
24
|
+
}),
|
|
25
|
+
emits: /*@__PURE__*/ mergeModels(["send", "abort"], ["update:model", "update:reasoningLevel"]),
|
|
20
26
|
setup(__props, { emit: __emit }) {
|
|
21
27
|
const props = __props;
|
|
28
|
+
const model = useModel(__props, "model");
|
|
29
|
+
const reasoningLevel = useModel(__props, "reasoningLevel");
|
|
22
30
|
const emit = __emit;
|
|
23
31
|
const cls = inject(AiChatDIKey)?.cls ?? bem("ai-chat");
|
|
24
32
|
const text = ref("");
|
|
25
33
|
const attachments = ref([]);
|
|
26
34
|
const textareaRef = shallowRef();
|
|
27
|
-
const fileRef = shallowRef();
|
|
28
35
|
const canSend = computed(() => {
|
|
29
36
|
return !props.running && (!!text.value.trim() || attachments.value.length > 0);
|
|
30
37
|
});
|
|
38
|
+
/** 多行自适应高度,上限 160px */
|
|
31
39
|
const autoResize = () => {
|
|
32
40
|
const el = textareaRef.value;
|
|
33
41
|
if (!el) return;
|
|
@@ -41,11 +49,9 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
41
49
|
attachments.value = [];
|
|
42
50
|
autoResize();
|
|
43
51
|
};
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const
|
|
47
|
-
input.value = "";
|
|
48
|
-
const maxSize = props.maxAttachmentSize ?? 10 * 1024 * 1024;
|
|
52
|
+
/** 将拾取的文件转为 dataUrl 附件(超限忽略) */
|
|
53
|
+
const handlePick = (files) => {
|
|
54
|
+
const maxSize = props.maxAttachmentSize ?? 10485760;
|
|
49
55
|
for (const file of files) {
|
|
50
56
|
if (file.size > maxSize) {
|
|
51
57
|
console.warn(`[UAiChat] 附件 ${file.name} 超过大小限制,已忽略`);
|
|
@@ -94,44 +100,50 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
|
94
100
|
onInput: autoResize
|
|
95
101
|
}, null, 42, _hoisted_2), [[vModelText, text.value]]),
|
|
96
102
|
createElementVNode("div", { class: normalizeClass(unref(cls).e("input-toolbar")) }, [
|
|
97
|
-
createVNode(unref(
|
|
98
|
-
class: normalizeClass(unref(cls).e("input-attach")),
|
|
99
|
-
title: "添加图片",
|
|
100
|
-
onClick: _cache[1] || (_cache[1] = ($event) => fileRef.value?.click())
|
|
101
|
-
}, {
|
|
102
|
-
default: withCtx(() => [createVNode(unref(Attach))]),
|
|
103
|
-
_: 1
|
|
104
|
-
}, 8, ["class"]),
|
|
105
|
-
createElementVNode("input", {
|
|
106
|
-
ref_key: "fileRef",
|
|
107
|
-
ref: fileRef,
|
|
108
|
-
type: "file",
|
|
109
|
-
hidden: "",
|
|
103
|
+
createElementVNode("div", { class: normalizeClass(unref(cls).e("input-toolbar-left")) }, [createVNode(unref(UFilePicker), {
|
|
110
104
|
multiple: "",
|
|
111
105
|
accept: __props.accept ?? "image/*",
|
|
112
|
-
|
|
113
|
-
},
|
|
114
|
-
|
|
106
|
+
onPick: handlePick
|
|
107
|
+
}, {
|
|
108
|
+
default: withCtx(() => [createVNode(unref(UIcon), {
|
|
109
|
+
class: normalizeClass(unref(cls).e("input-attach")),
|
|
110
|
+
title: "添加图片"
|
|
111
|
+
}, {
|
|
112
|
+
default: withCtx(() => [createVNode(unref(Attach))]),
|
|
113
|
+
_: 1
|
|
114
|
+
}, 8, ["class"])]),
|
|
115
|
+
_: 1
|
|
116
|
+
}, 8, ["accept"])], 2),
|
|
117
|
+
createCommentVNode(" 右簇:模型/推理选择 → 发送/停止,贴近发送按钮 "),
|
|
118
|
+
createElementVNode("div", { class: normalizeClass(unref(cls).e("input-toolbar-right")) }, [__props.models?.length ? (openBlock(), createBlock(_sfc_main$1, {
|
|
115
119
|
key: 0,
|
|
120
|
+
model: model.value,
|
|
121
|
+
"onUpdate:model": _cache[1] || (_cache[1] = ($event) => model.value = $event),
|
|
122
|
+
"reasoning-level": reasoningLevel.value,
|
|
123
|
+
"onUpdate:reasoningLevel": _cache[2] || (_cache[2] = ($event) => reasoningLevel.value = $event),
|
|
124
|
+
models: __props.models
|
|
125
|
+
}, null, 8, [
|
|
126
|
+
"model",
|
|
127
|
+
"reasoning-level",
|
|
128
|
+
"models"
|
|
129
|
+
])) : createCommentVNode("v-if", true), __props.running ? (openBlock(), createBlock(unref(UButton), {
|
|
130
|
+
key: 1,
|
|
131
|
+
size: "small",
|
|
116
132
|
type: "danger",
|
|
117
133
|
circle: "",
|
|
118
|
-
onClick: _cache[
|
|
134
|
+
onClick: _cache[3] || (_cache[3] = ($event) => emit("abort"))
|
|
119
135
|
}, {
|
|
120
136
|
default: withCtx(() => [createElementVNode("span", { class: normalizeClass(unref(cls).e("input-stop")) }, null, 2)]),
|
|
121
137
|
_: 1
|
|
122
138
|
})) : (openBlock(), createBlock(unref(UButton), {
|
|
123
|
-
key:
|
|
139
|
+
key: 2,
|
|
140
|
+
size: "small",
|
|
124
141
|
type: "primary",
|
|
125
142
|
circle: "",
|
|
126
143
|
disabled: !canSend.value,
|
|
144
|
+
icon: unref(Up),
|
|
127
145
|
onClick: handleSend
|
|
128
|
-
},
|
|
129
|
-
default: withCtx(() => [createVNode(unref(UIcon), null, {
|
|
130
|
-
default: withCtx(() => [createVNode(unref(Send))]),
|
|
131
|
-
_: 1
|
|
132
|
-
})]),
|
|
133
|
-
_: 1
|
|
134
|
-
}, 8, ["disabled"]))
|
|
146
|
+
}, null, 8, ["disabled", "icon"]))], 2)
|
|
135
147
|
], 2)
|
|
136
148
|
], 2);
|
|
137
149
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat-input.js","names":[],"sources":["../../../src/components/ai-chat/chat-input.vue"],"sourcesContent":["<template>\n <div :class=\"cls.e('input-area')\">\n <div v-if=\"attachments.length\" :class=\"cls.e('input-attachments')\">\n <div\n v-for=\"(att, index) in attachments\"\n :key=\"att.dataUrl\"\n :class=\"cls.e('input-attachment')\"\n >\n <img :src=\"att.dataUrl\" :alt=\"att.name\" />\n <UIcon :class=\"cls.e('input-attachment-remove')\" @click=\"attachments.splice(index, 1)\">\n <Close />\n </UIcon>\n </div>\n </div>\n\n <textarea\n ref=\"textareaRef\"\n v-model=\"text\"\n :class=\"cls.e('input')\"\n :placeholder=\"placeholder ?? '输入消息,Enter 发送,Shift + Enter 换行'\"\n rows=\"1\"\n @keydown.enter.exact.prevent=\"handleSend\"\n @input=\"autoResize\"\n />\n\n <div :class=\"cls.e('input-toolbar')\">\n <
|
|
1
|
+
{"version":3,"file":"chat-input.js","names":[],"sources":["../../../src/components/ai-chat/chat-input.vue"],"sourcesContent":["<template>\n <div :class=\"cls.e('input-area')\">\n <div v-if=\"attachments.length\" :class=\"cls.e('input-attachments')\">\n <div\n v-for=\"(att, index) in attachments\"\n :key=\"att.dataUrl\"\n :class=\"cls.e('input-attachment')\"\n >\n <img :src=\"att.dataUrl\" :alt=\"att.name\" />\n <UIcon :class=\"cls.e('input-attachment-remove')\" @click=\"attachments.splice(index, 1)\">\n <Close />\n </UIcon>\n </div>\n </div>\n\n <textarea\n ref=\"textareaRef\"\n v-model=\"text\"\n :class=\"cls.e('input')\"\n :placeholder=\"placeholder ?? '输入消息,Enter 发送,Shift + Enter 换行'\"\n rows=\"1\"\n @keydown.enter.exact.prevent=\"handleSend\"\n @input=\"autoResize\"\n />\n\n <div :class=\"cls.e('input-toolbar')\">\n <div :class=\"cls.e('input-toolbar-left')\">\n <UFilePicker multiple :accept=\"accept ?? 'image/*'\" @pick=\"handlePick\">\n <UIcon :class=\"cls.e('input-attach')\" title=\"添加图片\">\n <Attach />\n </UIcon>\n </UFilePicker>\n </div>\n\n <!-- 右簇:模型/推理选择 → 发送/停止,贴近发送按钮 -->\n <div :class=\"cls.e('input-toolbar-right')\">\n <ModelPicker\n v-if=\"models?.length\"\n v-model:model=\"model\"\n v-model:reasoning-level=\"reasoningLevel\"\n :models=\"models\"\n />\n\n <UButton v-if=\"running\" size=\"small\" type=\"danger\" circle @click=\"emit('abort')\">\n <span :class=\"cls.e('input-stop')\" />\n </UButton>\n <UButton\n v-else\n size=\"small\"\n type=\"primary\"\n circle\n :disabled=\"!canSend\"\n :icon=\"Up\"\n @click=\"handleSend\"\n >\n </UButton>\n </div>\n </div>\n </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { UButton, UFilePicker, UIcon } from '@veltra/desktop'\nimport { Attach, Close, Up } from '@veltra/icons/normal'\nimport { bem } from '@veltra/utils'\nimport { computed, inject, ref, shallowRef } from 'vue'\n\nimport type { ChatAttachment } from '../../chat/types'\nimport type { ChatModelOption } from '../../providers'\nimport { AiChatDIKey } from './di'\nimport ModelPicker from './model-picker.vue'\n\ndefineOptions({ name: 'UAiChatInput' })\n\nconst props = defineProps<{\n /** 是否生成中(显示停止按钮) */\n running: boolean\n /** 可选模型列表;有值则显示选择器 */\n models?: ChatModelOption[]\n /** 输入框占位文本 */\n placeholder?: string\n /** 附件 accept 类型 */\n accept?: string\n /** 单个附件最大字节数 */\n maxAttachmentSize?: number\n}>()\n\nconst model = defineModel<string>('model')\nconst reasoningLevel = defineModel<string>('reasoningLevel')\n\nconst emit = defineEmits<{\n (e: 'send', content: string, attachments: ChatAttachment[]): void\n (e: 'abort'): void\n}>()\n\nconst di = inject(AiChatDIKey)\nconst cls = di?.cls ?? bem('ai-chat')\n\nconst text = ref('')\nconst attachments = ref<ChatAttachment[]>([])\nconst textareaRef = shallowRef<HTMLTextAreaElement>()\n\nconst canSend = computed(() => {\n return !props.running && (!!text.value.trim() || attachments.value.length > 0)\n})\n\n/** 多行自适应高度,上限 160px */\nconst autoResize = () => {\n const el = textareaRef.value\n if (!el) return\n el.style.height = 'auto'\n el.style.height = `${Math.min(el.scrollHeight, 160)}px`\n}\n\nconst handleSend = () => {\n if (!canSend.value) return\n emit('send', text.value.trim(), attachments.value)\n text.value = ''\n attachments.value = []\n autoResize()\n}\n\n/** 将拾取的文件转为 dataUrl 附件(超限忽略) */\nconst handlePick = (files: File[]) => {\n const maxSize = props.maxAttachmentSize ?? 10 * 1024 * 1024\n\n for (const file of files) {\n if (file.size > maxSize) {\n console.warn(`[UAiChat] 附件 ${file.name} 超过大小限制,已忽略`)\n continue\n }\n const reader = new FileReader()\n reader.onload = () => {\n attachments.value.push({\n name: file.name,\n mimeType: file.type,\n size: file.size,\n dataUrl: String(reader.result)\n })\n }\n reader.readAsDataURL(file)\n }\n}\n</script>\n"],"mappings":"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"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { AiChatDIKey } from "./di.js";
|
|
2
2
|
import _sfc_main$1 from "./tool-call.js";
|
|
3
3
|
import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, inject, mergeProps, normalizeClass, openBlock, ref, renderList, toDisplayString, unref, vShow, watch, withCtx, withDirectives } from "vue";
|
|
4
|
-
import { bem } from "@veltra/utils";
|
|
5
|
-
import { UIcon } from "@veltra/desktop";
|
|
6
4
|
import { ArrowDown, InfoCircle, Loading, Refresh } from "@veltra/icons/normal";
|
|
5
|
+
import { UIcon } from "@veltra/desktop";
|
|
6
|
+
import { bem } from "@veltra/utils";
|
|
7
7
|
import MarkdownRender from "markstream-vue";
|
|
8
8
|
//#region src/components/ai-chat/message-item.vue
|
|
9
9
|
const _hoisted_1 = ["src", "alt"];
|
|
@@ -1 +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":";;;;;;;;;;;;;;;;;;;EAiFA,MAAM,QAAQ;EAQd,MAAM,OAAO;EAMb,MAAM,MADK,OAAO,WACN,CAAA,EAAI,OAAO,IAAI,SAAS;EAEpC,MAAM,cAAc,eAAe,MAAM,QAAQ,WAAW,WAAW;;EAEvE,MAAM,aAAa,eAAe,YAAY,SAAS,CAAC,MAAM,QAAQ,OAAO;EAE7E,MAAM,oBAAoB,IAAI,IAAI;EAGlC,YACQ,MAAM,QAAQ,SACnB,WAAW;GACV,IAAI,WAAW,aACb,kBAAkB,QAAQ;QACrB,IAAI,QACT,kBAAkB,QAAQ;EAE9B,CACF;EAEA,MAAM,cAAc,eAAe;GACjC,IAAI,MAAM,QAAQ,SAAS,eAAe,CAAC,MAAM,QAAQ,OAAO;GAChE,OAAO;IAAC;IAAQ;IAAS;GAAS,CAAC,CAAC,SAAS,MAAM,QAAQ,UAAU,EAAE;EACzE,CAAC;;
|
|
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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"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { AiChatDIKey } from "./di.js";
|
|
2
2
|
import _sfc_main$1 from "./message-item.js";
|
|
3
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
4
|
import { UScroll } from "@veltra/desktop";
|
|
5
|
+
import { bem } from "@veltra/utils";
|
|
6
6
|
//#region src/components/ai-chat/message-list.vue
|
|
7
7
|
const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
8
8
|
name: "UAiChatMessageList",
|
|
@@ -1 +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;;
|
|
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;;GA9EE,OAAA,UAAA,GAAA,YAcU,MAAA,OAAA,GAAA;IAdA,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,MAAA,CAAA;IAAc,SAAA;IAAJ,KAAI;IAAY,QAAA;IAAQ,UAAQ;;IAGxD,SAAA,cAAA,CAAA,CAFM,gBAAA,MAAgB,UAAA,UAAA,GAA5B,mBAEM,OAAA;;KAF+B,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,SAAA,CAAA;IAC/C,GAAA,CAAA,WAAyC,KAAA,QAAA,WAAA,CAAA,SAAA,CAAA,gBAAjB,gBAAA,QAAA,OAAO,GAAA,CAAA,CAAA,CAAA,CAAA,GAAA,CAGjC,KAAA,mBAAA,QAAA,IAAA,IAAA,UAAA,IAAA,GAAA,mBAQE,UAAA,MAAA,WAPuB,gBAAA,QAAf,KAAK,UAAK;KADpB,OAAA,UAAA,GAAA,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,117 @@
|
|
|
1
|
+
import { AiChatDIKey } from "./di.js";
|
|
2
|
+
import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, inject, mergeModels, nextTick, normalizeClass, openBlock, ref, renderList, toDisplayString, unref, useModel, useTemplateRef, withCtx } from "vue";
|
|
3
|
+
import { ArrowDown, ArrowRight, Check } from "@veltra/icons/normal";
|
|
4
|
+
import { UDropdown, UIcon } from "@veltra/desktop";
|
|
5
|
+
import { bem } from "@veltra/utils";
|
|
6
|
+
//#region src/components/ai-chat/model-picker.vue
|
|
7
|
+
const _hoisted_1 = ["onClick"];
|
|
8
|
+
const _hoisted_2 = ["onClick"];
|
|
9
|
+
const _sfc_main = /*@__PURE__*/ defineComponent({
|
|
10
|
+
name: "UAiChatModelPicker",
|
|
11
|
+
__name: "model-picker",
|
|
12
|
+
props: /*@__PURE__*/ mergeModels({ models: {} }, {
|
|
13
|
+
"model": {},
|
|
14
|
+
"modelModifiers": {},
|
|
15
|
+
"reasoningLevel": {},
|
|
16
|
+
"reasoningLevelModifiers": {}
|
|
17
|
+
}),
|
|
18
|
+
emits: ["update:model", "update:reasoningLevel"],
|
|
19
|
+
setup(__props) {
|
|
20
|
+
const props = __props;
|
|
21
|
+
const model = useModel(__props, "model");
|
|
22
|
+
const reasoningLevel = useModel(__props, "reasoningLevel");
|
|
23
|
+
const cls = inject(AiChatDIKey)?.cls ?? bem("ai-chat");
|
|
24
|
+
const dropdownRef = useTemplateRef("dropdownRef");
|
|
25
|
+
/** 思考强度子列表是否展开 */
|
|
26
|
+
const reasoningExpanded = ref(false);
|
|
27
|
+
const currentModel = computed(() => {
|
|
28
|
+
return props.models.find((m) => m.id === model.value);
|
|
29
|
+
});
|
|
30
|
+
const reasoningLevels = computed(() => currentModel.value?.reasoningLevels ?? []);
|
|
31
|
+
const currentReasoningLabel = computed(() => {
|
|
32
|
+
return reasoningLevels.value.find((l) => l.value === reasoningLevel.value)?.label;
|
|
33
|
+
});
|
|
34
|
+
/** 展开/收起思考强度子列表,并重置面板位置防止溢出 */
|
|
35
|
+
const toggleReasoning = () => {
|
|
36
|
+
reasoningExpanded.value = !reasoningExpanded.value;
|
|
37
|
+
nextTick(() => dropdownRef.value?.updateDropdown());
|
|
38
|
+
};
|
|
39
|
+
/** 选中模型(推理等级校正在 useChat 中完成) */
|
|
40
|
+
const handleSelectModel = (id) => {
|
|
41
|
+
model.value = id;
|
|
42
|
+
reasoningExpanded.value = false;
|
|
43
|
+
dropdownRef.value?.close();
|
|
44
|
+
};
|
|
45
|
+
const handleSelectReasoning = (value) => {
|
|
46
|
+
reasoningLevel.value = value;
|
|
47
|
+
dropdownRef.value?.close();
|
|
48
|
+
};
|
|
49
|
+
return (_ctx, _cache) => {
|
|
50
|
+
return openBlock(), createBlock(unref(UDropdown), {
|
|
51
|
+
ref_key: "dropdownRef",
|
|
52
|
+
ref: dropdownRef,
|
|
53
|
+
trigger: "click",
|
|
54
|
+
"min-width": "220px",
|
|
55
|
+
"content-class": unref(cls).e("model-panel")
|
|
56
|
+
}, {
|
|
57
|
+
trigger: withCtx(() => [createElementVNode("button", {
|
|
58
|
+
type: "button",
|
|
59
|
+
class: normalizeClass(unref(cls).e("model-trigger"))
|
|
60
|
+
}, [
|
|
61
|
+
createElementVNode("span", { class: normalizeClass(unref(cls).e("model-trigger-label")) }, toDisplayString(currentModel.value?.label ?? model.value ?? "模型"), 3),
|
|
62
|
+
currentReasoningLabel.value ? (openBlock(), createElementBlock("span", {
|
|
63
|
+
key: 0,
|
|
64
|
+
class: normalizeClass(unref(cls).e("model-trigger-reasoning"))
|
|
65
|
+
}, toDisplayString(currentReasoningLabel.value), 3)) : createCommentVNode("v-if", true),
|
|
66
|
+
createVNode(unref(UIcon), { class: normalizeClass(unref(cls).e("model-trigger-arrow")) }, {
|
|
67
|
+
default: withCtx(() => [createVNode(unref(ArrowDown))]),
|
|
68
|
+
_: 1
|
|
69
|
+
}, 8, ["class"])
|
|
70
|
+
], 2)]),
|
|
71
|
+
content: withCtx(() => [(openBlock(true), createElementBlock(Fragment, null, renderList(__props.models, (item) => {
|
|
72
|
+
return openBlock(), createElementBlock("div", {
|
|
73
|
+
key: item.id,
|
|
74
|
+
class: normalizeClass([unref(cls).e("model-option"), unref(bem).is("active", item.id === model.value)]),
|
|
75
|
+
onClick: ($event) => handleSelectModel(item.id)
|
|
76
|
+
}, [createElementVNode("div", { class: normalizeClass(unref(cls).e("model-option-head")) }, [createElementVNode("span", null, toDisplayString(item.label ?? item.id), 1), item.id === model.value ? (openBlock(), createBlock(unref(UIcon), {
|
|
77
|
+
key: 0,
|
|
78
|
+
class: normalizeClass(unref(cls).e("model-option-check"))
|
|
79
|
+
}, {
|
|
80
|
+
default: withCtx(() => [createVNode(unref(Check))]),
|
|
81
|
+
_: 1
|
|
82
|
+
}, 8, ["class"])) : createCommentVNode("v-if", true)], 2), item.description ? (openBlock(), createElementBlock("div", {
|
|
83
|
+
key: 0,
|
|
84
|
+
class: normalizeClass(unref(cls).e("model-option-desc"))
|
|
85
|
+
}, toDisplayString(item.description), 3)) : createCommentVNode("v-if", true)], 10, _hoisted_1);
|
|
86
|
+
}), 128)), reasoningLevels.value.length ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [
|
|
87
|
+
createElementVNode("div", { class: normalizeClass(unref(cls).e("model-divider")) }, null, 2),
|
|
88
|
+
createElementVNode("div", {
|
|
89
|
+
class: normalizeClass(unref(cls).e("reasoning-row")),
|
|
90
|
+
onClick: toggleReasoning
|
|
91
|
+
}, [_cache[0] || (_cache[0] = createElementVNode("span", null, "思考强度", -1)), createElementVNode("span", { class: normalizeClass(unref(cls).e("reasoning-row-value")) }, [createTextVNode(toDisplayString(currentReasoningLabel.value) + " ", 1), createVNode(unref(UIcon), null, {
|
|
92
|
+
default: withCtx(() => [reasoningExpanded.value ? (openBlock(), createBlock(unref(ArrowDown), { key: 0 })) : (openBlock(), createBlock(unref(ArrowRight), { key: 1 }))]),
|
|
93
|
+
_: 1
|
|
94
|
+
})], 2)], 2),
|
|
95
|
+
reasoningExpanded.value ? (openBlock(true), createElementBlock(Fragment, { key: 0 }, renderList(reasoningLevels.value, (level) => {
|
|
96
|
+
return openBlock(), createElementBlock("div", {
|
|
97
|
+
key: level.value,
|
|
98
|
+
class: normalizeClass([unref(cls).e("reasoning-option"), unref(bem).is("active", level.value === reasoningLevel.value)]),
|
|
99
|
+
onClick: ($event) => handleSelectReasoning(level.value)
|
|
100
|
+
}, [createElementVNode("span", null, toDisplayString(level.label), 1), level.value === reasoningLevel.value ? (openBlock(), createBlock(unref(UIcon), {
|
|
101
|
+
key: 0,
|
|
102
|
+
class: normalizeClass(unref(cls).e("model-option-check"))
|
|
103
|
+
}, {
|
|
104
|
+
default: withCtx(() => [createVNode(unref(Check))]),
|
|
105
|
+
_: 1
|
|
106
|
+
}, 8, ["class"])) : createCommentVNode("v-if", true)], 10, _hoisted_2);
|
|
107
|
+
}), 128)) : createCommentVNode("v-if", true)
|
|
108
|
+
], 64)) : createCommentVNode("v-if", true)]),
|
|
109
|
+
_: 1
|
|
110
|
+
}, 8, ["content-class"]);
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
//#endregion
|
|
115
|
+
export { _sfc_main as default };
|
|
116
|
+
|
|
117
|
+
//# sourceMappingURL=model-picker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model-picker.js","names":[],"sources":["../../../src/components/ai-chat/model-picker.vue"],"sourcesContent":["<template>\n <UDropdown\n ref=\"dropdownRef\"\n trigger=\"click\"\n min-width=\"220px\"\n :content-class=\"cls.e('model-panel')\"\n >\n <template #trigger>\n <button type=\"button\" :class=\"cls.e('model-trigger')\">\n <span :class=\"cls.e('model-trigger-label')\">\n {{ currentModel?.label ?? model ?? '模型' }}\n </span>\n <span v-if=\"currentReasoningLabel\" :class=\"cls.e('model-trigger-reasoning')\">\n {{ currentReasoningLabel }}\n </span>\n <UIcon :class=\"cls.e('model-trigger-arrow')\">\n <ArrowDown />\n </UIcon>\n </button>\n </template>\n\n <template #content>\n <div\n v-for=\"item in models\"\n :key=\"item.id\"\n :class=\"[cls.e('model-option'), bem.is('active', item.id === model)]\"\n @click=\"handleSelectModel(item.id)\"\n >\n <div :class=\"cls.e('model-option-head')\">\n <span>{{ item.label ?? item.id }}</span>\n <UIcon v-if=\"item.id === model\" :class=\"cls.e('model-option-check')\">\n <Check />\n </UIcon>\n </div>\n <div v-if=\"item.description\" :class=\"cls.e('model-option-desc')\">\n {{ item.description }}\n </div>\n </div>\n\n <template v-if=\"reasoningLevels.length\">\n <div :class=\"cls.e('model-divider')\" />\n\n <div :class=\"cls.e('reasoning-row')\" @click=\"toggleReasoning\">\n <span>思考强度</span>\n <span :class=\"cls.e('reasoning-row-value')\">\n {{ currentReasoningLabel }}\n <UIcon>\n <ArrowDown v-if=\"reasoningExpanded\" />\n <ArrowRight v-else />\n </UIcon>\n </span>\n </div>\n\n <template v-if=\"reasoningExpanded\">\n <div\n v-for=\"level in reasoningLevels\"\n :key=\"level.value\"\n :class=\"[cls.e('reasoning-option'), bem.is('active', level.value === reasoningLevel)]\"\n @click=\"handleSelectReasoning(level.value)\"\n >\n <span>{{ level.label }}</span>\n <UIcon v-if=\"level.value === reasoningLevel\" :class=\"cls.e('model-option-check')\">\n <Check />\n </UIcon>\n </div>\n </template>\n </template>\n </template>\n </UDropdown>\n</template>\n\n<script lang=\"ts\" setup>\nimport { UDropdown, UIcon } from '@veltra/desktop'\nimport { ArrowDown, ArrowRight, Check } from '@veltra/icons/normal'\nimport { bem } from '@veltra/utils'\nimport { computed, inject, nextTick, ref, useTemplateRef } from 'vue'\n\nimport type { ChatModelOption } from '../../providers'\nimport { AiChatDIKey } from './di'\n\ndefineOptions({ name: 'UAiChatModelPicker' })\n\nconst props = defineProps<{\n /** 可选模型列表 */\n models: ChatModelOption[]\n}>()\n\nconst model = defineModel<string>('model')\nconst reasoningLevel = defineModel<string>('reasoningLevel')\n\nconst di = inject(AiChatDIKey)\nconst cls = di?.cls ?? bem('ai-chat')\n\nconst dropdownRef = useTemplateRef('dropdownRef')\n\n/** 思考强度子列表是否展开 */\nconst reasoningExpanded = ref(false)\n\nconst currentModel = computed(() => {\n return props.models.find((m) => m.id === model.value)\n})\n\nconst reasoningLevels = computed(() => currentModel.value?.reasoningLevels ?? [])\n\nconst currentReasoningLabel = computed(() => {\n return reasoningLevels.value.find((l) => l.value === reasoningLevel.value)?.label\n})\n\n/** 展开/收起思考强度子列表,并重置面板位置防止溢出 */\nconst toggleReasoning = () => {\n reasoningExpanded.value = !reasoningExpanded.value\n nextTick(() => dropdownRef.value?.updateDropdown())\n}\n\n/** 选中模型(推理等级校正在 useChat 中完成) */\nconst handleSelectModel = (id: string) => {\n model.value = id\n reasoningExpanded.value = false\n dropdownRef.value?.close()\n}\n\nconst handleSelectReasoning = (value: string) => {\n reasoningLevel.value = value\n dropdownRef.value?.close()\n}\n</script>\n"],"mappings":";;;;;;;;;;;;;;;;;;;EAkFA,MAAM,QAAQ;EAKd,MAAM,QAAQ,SAAmB,SAAC,OAAO;EACzC,MAAM,iBAAiB,SAAmB,SAAC,gBAAgB;EAG3D,MAAM,MADK,OAAO,WACN,CAAA,EAAI,OAAO,IAAI,SAAS;EAEpC,MAAM,cAAc,eAAe,aAAa;;EAGhD,MAAM,oBAAoB,IAAI,KAAK;EAEnC,MAAM,eAAe,eAAe;GAClC,OAAO,MAAM,OAAO,MAAM,MAAM,EAAE,OAAO,MAAM,KAAK;EACtD,CAAC;EAED,MAAM,kBAAkB,eAAe,aAAa,OAAO,mBAAmB,CAAC,CAAC;EAEhF,MAAM,wBAAwB,eAAe;GAC3C,OAAO,gBAAgB,MAAM,MAAM,MAAM,EAAE,UAAU,eAAe,KAAK,CAAC,EAAE;EAC9E,CAAC;;EAGD,MAAM,wBAAwB;GAC5B,kBAAkB,QAAQ,CAAC,kBAAkB;GAC7C,eAAe,YAAY,OAAO,eAAe,CAAC;EACpD;;EAGA,MAAM,qBAAqB,OAAe;GACxC,MAAM,QAAQ;GACd,kBAAkB,QAAQ;GAC1B,YAAY,OAAO,MAAM;EAC3B;EAEA,MAAM,yBAAyB,UAAkB;GAC/C,eAAe,QAAQ;GACvB,YAAY,OAAO,MAAM;EAC3B;;GA3HE,OAAA,UAAA,GAAA,YAmEY,MAAA,SAAA,GAAA;IAlEN,SAAA;IAAJ,KAAI;IACJ,SAAQ;IACR,aAAU;IACT,iBAAe,MAAA,GAAA,CAAG,CAAC,EAAC,aAAA;;IAEV,SAAO,cAWP,CAVT,mBAUS,UAAA;KAVD,MAAK;KAAU,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,eAAA,CAAA;;KACjC,mBAEO,QAAA,EAFA,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,qBAAA,CAAA,EAAA,GACd,gBAAA,aAAA,OAAc,SAAS,MAAA,SAAK,IAAA,GAAA,CAAA;KAErB,sBAAA,SAAA,UAAA,GAAZ,mBAEO,QAAA;;MAF6B,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,yBAAA,CAAA;KAC3C,GAAA,gBAAA,sBAAA,KAAqB,GAAA,CAAA,KAAA,mBAAA,QAAA,IAAA;KAE1B,YAEQ,MAAA,KAAA,GAAA,EAFA,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,qBAAA,CAAA,EAAA,GAAA;MACL,SAAA,cAAA,CAAb,YAAa,MAAA,SAAA,CAAA,CAAA,CAAA;;;;IAKR,SAAO,cAEQ,EAAA,UAAA,IAAA,GADxB,mBAeM,UAAA,MAAA,WAdW,QAAA,SAAR,SAAI;KADb,OAAA,UAAA,GAAA,mBAeM,OAAA;MAbH,KAAK,KAAK;MACV,OAAK,eAAA,CAAG,MAAA,GAAA,CAAG,CAAC,EAAC,cAAA,GAAkB,MAAA,GAAA,CAAG,CAAC,GAAE,UAAW,KAAK,OAAO,MAAA,KAAK,CAAA,CAAA;MACjE,UAAK,WAAE,kBAAkB,KAAK,EAAE;KAEjC,GAAA,CAAA,mBAKM,OAAA,EALA,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,mBAAA,CAAA,EAAA,GAChB,CAAA,mBAAwC,QAAA,MAAA,gBAA/B,KAAK,SAAS,KAAK,EAAE,GAAA,CAAA,GACjB,KAAK,OAAO,MAAA,SAAA,UAAA,GAAzB,YAEQ,MAAA,KAAA,GAAA;;MAFyB,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,oBAAA,CAAA;;MAClC,SAAA,cAAA,CAAT,YAAS,MAAA,KAAA,CAAA,CAAA,CAAA;;KAGF,GAAA,GAAA,CAAA,OAAA,CAAA,KAAA,mBAAA,QAAA,IAAA,CAAA,GAAA,CAAA,GAAA,KAAK,eAAA,UAAA,GAAhB,mBAEM,OAAA;;MAFwB,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,mBAAA,CAAA;KACrC,GAAA,gBAAA,KAAK,WAAW,GAAA,CAAA,KAAA,mBAAA,QAAA,IAAA,CAAA,GAAA,IAAA,UAAA;IAIP,CAAA,GAAA,GAAA,IAAA,gBAAA,MAAgB,UAAA,UAAA,GAAhC,mBA2BW,UAAA,EAAA,KAAA,EAAA,GAAA;KA1BT,mBAAuC,OAAA,EAAjC,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,eAAA,CAAA,EAAA,GAAA,MAAA,CAAA;KAElB,mBASM,OAAA;MATA,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,eAAA,CAAA;MAAoB,SAAO;KAC3C,GAAA,CAAA,OAAA,OAAA,OAAA,KAAA,mBAAiB,QAAA,MAAX,QAAI,EAAA,IACV,mBAMO,QAAA,EANA,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,qBAAA,CAAA,EAAA,GAAA,CAAA,gBACd,gBAAA,sBAAA,KAAqB,IAAG,KAC3B,CAAA,GAAA,YAGQ,MAAA,KAAA,GAAA,MAAA;MAFgC,SAAA,cAAA,CAArB,kBAAA,SAAA,UAAA,GAAjB,YAAsC,MAAA,SAAA,GAAA,EAAA,KAAA,EAAA,CAAA,MAAA,UAAA,GACtC,YAAqB,MAAA,UAAA,GAAA,EAAA,KAAA,EAAA,CAAA,EAAA,CAAA;;;KAKX,kBAAA,SAAA,UAAA,IAAA,GACd,mBAUM,UAAA,EAAA,KAAA,EAAA,GAAA,WATY,gBAAA,QAAT,UAAK;MADd,OAAA,UAAA,GAAA,mBAUM,OAAA;OARH,KAAK,MAAM;OACX,OAAK,eAAA,CAAG,MAAA,GAAA,CAAG,CAAC,EAAC,kBAAA,GAAsB,MAAA,GAAA,CAAG,CAAC,GAAE,UAAW,MAAM,UAAU,eAAA,KAAc,CAAA,CAAA;OAClF,UAAK,WAAE,sBAAsB,MAAM,KAAK;MAEzC,GAAA,CAAA,mBAA8B,QAAA,MAAA,gBAArB,MAAM,KAAK,GAAA,CAAA,GACP,MAAM,UAAU,eAAA,SAAA,UAAA,GAA7B,YAEQ,MAAA,KAAA,GAAA;;OAFsC,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,oBAAA,CAAA;;OAC/C,SAAA,cAAA,CAAT,YAAS,MAAA,KAAA,CAAA,CAAA,CAAA"}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import "@veltra/desktop/components/button/style";
|
|
2
|
+
import "@veltra/desktop/components/file-picker/style";
|
|
2
3
|
import "@veltra/desktop/components/icon/style";
|
|
3
4
|
import "@veltra/desktop/components/input/style";
|
|
4
5
|
import "@veltra/desktop/components/scroll/style";
|
|
6
|
+
import "@veltra/desktop/components/select/style";
|
|
5
7
|
import "markstream-vue/index.css";
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import "./style2.css";
|
|
2
2
|
import "@veltra/desktop/components/button/style";
|
|
3
|
+
import "@veltra/desktop/components/file-picker/style";
|
|
3
4
|
import "@veltra/desktop/components/icon/style";
|
|
4
5
|
import "@veltra/desktop/components/input/style";
|
|
5
6
|
import "@veltra/desktop/components/scroll/style";
|
|
7
|
+
import "@veltra/desktop/components/select/style";
|
|
6
8
|
import "markstream-vue/index.css";
|
|
@@ -359,12 +359,64 @@
|
|
|
359
359
|
justify-content: space-between;
|
|
360
360
|
align-items: center;
|
|
361
361
|
flex-wrap: nowrap;
|
|
362
|
+
gap: var(--u-gap-small);
|
|
362
363
|
padding: 0 var(--u-gap-small);
|
|
363
364
|
}
|
|
365
|
+
.u-ai-chat__input-toolbar-left {
|
|
366
|
+
display: flex;
|
|
367
|
+
justify-content: flex-start;
|
|
368
|
+
align-items: center;
|
|
369
|
+
flex-wrap: nowrap;
|
|
370
|
+
gap: var(--u-gap-small);
|
|
371
|
+
flex-shrink: 0;
|
|
372
|
+
}
|
|
373
|
+
.u-ai-chat__input-toolbar-right {
|
|
374
|
+
display: flex;
|
|
375
|
+
justify-content: flex-end;
|
|
376
|
+
align-items: center;
|
|
377
|
+
flex-wrap: nowrap;
|
|
378
|
+
gap: var(--u-gap-small);
|
|
379
|
+
min-width: 0;
|
|
380
|
+
}
|
|
381
|
+
.u-ai-chat__model-trigger {
|
|
382
|
+
display: flex;
|
|
383
|
+
justify-content: flex-start;
|
|
384
|
+
align-items: center;
|
|
385
|
+
flex-wrap: nowrap;
|
|
386
|
+
gap: 4px;
|
|
387
|
+
max-width: 220px;
|
|
388
|
+
padding: 2px var(--u-gap-small);
|
|
389
|
+
border: none;
|
|
390
|
+
border-radius: var(--u-radius-default);
|
|
391
|
+
background: transparent;
|
|
392
|
+
color: var(--u-text-color-main);
|
|
393
|
+
font-family: inherit;
|
|
394
|
+
font-size: inherit;
|
|
395
|
+
cursor: pointer;
|
|
396
|
+
transition: background-color 0.15s ease;
|
|
397
|
+
}
|
|
398
|
+
.u-ai-chat__model-trigger:hover {
|
|
399
|
+
background-color: var(--u-nav-hover-bg);
|
|
400
|
+
}
|
|
401
|
+
.u-ai-chat__model-trigger-label {
|
|
402
|
+
overflow: hidden;
|
|
403
|
+
text-overflow: ellipsis;
|
|
404
|
+
white-space: nowrap;
|
|
405
|
+
}
|
|
406
|
+
.u-ai-chat__model-trigger-reasoning {
|
|
407
|
+
flex-shrink: 0;
|
|
408
|
+
color: var(--u-text-color-assist);
|
|
409
|
+
}
|
|
410
|
+
.u-ai-chat__model-trigger-arrow {
|
|
411
|
+
flex-shrink: 0;
|
|
412
|
+
font-size: 12px;
|
|
413
|
+
color: var(--u-text-color-assist);
|
|
414
|
+
}
|
|
364
415
|
.u-ai-chat__input-attach {
|
|
365
416
|
color: var(--u-text-color-assist);
|
|
366
417
|
cursor: pointer;
|
|
367
418
|
font-size: 18px;
|
|
419
|
+
flex-shrink: 0;
|
|
368
420
|
}
|
|
369
421
|
.u-ai-chat__input-attach:hover {
|
|
370
422
|
color: var(--u-color-primary);
|
|
@@ -414,3 +466,79 @@
|
|
|
414
466
|
transform: rotate(360deg);
|
|
415
467
|
}
|
|
416
468
|
}
|
|
469
|
+
.u-ai-chat__model-panel {
|
|
470
|
+
display: flex;
|
|
471
|
+
flex-direction: column;
|
|
472
|
+
gap: 1px;
|
|
473
|
+
padding: 4px;
|
|
474
|
+
user-select: none;
|
|
475
|
+
}
|
|
476
|
+
.u-ai-chat__model-panel .u-ai-chat__model-option {
|
|
477
|
+
padding: 6px 8px;
|
|
478
|
+
border-radius: calc(var(--u-radius-default) + 1px);
|
|
479
|
+
cursor: pointer;
|
|
480
|
+
transition: background-color 0.15s ease;
|
|
481
|
+
}
|
|
482
|
+
.u-ai-chat__model-panel .u-ai-chat__model-option:hover {
|
|
483
|
+
background-color: var(--u-nav-hover-bg);
|
|
484
|
+
}
|
|
485
|
+
.u-ai-chat__model-panel .u-ai-chat__model-option-head {
|
|
486
|
+
display: flex;
|
|
487
|
+
justify-content: space-between;
|
|
488
|
+
align-items: center;
|
|
489
|
+
flex-wrap: nowrap;
|
|
490
|
+
gap: 16px;
|
|
491
|
+
color: var(--u-text-color-main);
|
|
492
|
+
}
|
|
493
|
+
.u-ai-chat__model-panel .u-ai-chat__model-option-check {
|
|
494
|
+
flex-shrink: 0;
|
|
495
|
+
font-size: 14px;
|
|
496
|
+
color: var(--u-color-primary);
|
|
497
|
+
}
|
|
498
|
+
.u-ai-chat__model-panel .u-ai-chat__model-option-desc {
|
|
499
|
+
margin-top: 2px;
|
|
500
|
+
font-size: var(--u-font-size-main-small);
|
|
501
|
+
color: var(--u-text-color-assist);
|
|
502
|
+
}
|
|
503
|
+
.u-ai-chat__model-panel .u-ai-chat__model-divider {
|
|
504
|
+
height: 0;
|
|
505
|
+
margin: 4px 8px;
|
|
506
|
+
border-top: 1px solid var(--u-border-color);
|
|
507
|
+
}
|
|
508
|
+
.u-ai-chat__model-panel .u-ai-chat__reasoning-row {
|
|
509
|
+
display: flex;
|
|
510
|
+
justify-content: space-between;
|
|
511
|
+
align-items: center;
|
|
512
|
+
flex-wrap: nowrap;
|
|
513
|
+
padding: 6px 8px;
|
|
514
|
+
border-radius: calc(var(--u-radius-default) + 1px);
|
|
515
|
+
color: var(--u-text-color-main);
|
|
516
|
+
cursor: pointer;
|
|
517
|
+
transition: background-color 0.15s ease;
|
|
518
|
+
}
|
|
519
|
+
.u-ai-chat__model-panel .u-ai-chat__reasoning-row:hover {
|
|
520
|
+
background-color: var(--u-nav-hover-bg);
|
|
521
|
+
}
|
|
522
|
+
.u-ai-chat__model-panel .u-ai-chat__reasoning-row-value {
|
|
523
|
+
display: flex;
|
|
524
|
+
justify-content: flex-start;
|
|
525
|
+
align-items: center;
|
|
526
|
+
flex-wrap: nowrap;
|
|
527
|
+
gap: 4px;
|
|
528
|
+
color: var(--u-text-color-assist);
|
|
529
|
+
}
|
|
530
|
+
.u-ai-chat__model-panel .u-ai-chat__reasoning-option {
|
|
531
|
+
display: flex;
|
|
532
|
+
justify-content: space-between;
|
|
533
|
+
align-items: center;
|
|
534
|
+
flex-wrap: nowrap;
|
|
535
|
+
gap: 16px;
|
|
536
|
+
padding: 6px 8px 6px 20px;
|
|
537
|
+
border-radius: calc(var(--u-radius-default) + 1px);
|
|
538
|
+
color: var(--u-text-color-main);
|
|
539
|
+
cursor: pointer;
|
|
540
|
+
transition: background-color 0.15s ease;
|
|
541
|
+
}
|
|
542
|
+
.u-ai-chat__model-panel .u-ai-chat__reasoning-option:hover {
|
|
543
|
+
background-color: var(--u-nav-hover-bg);
|
|
544
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { AiChatDIKey } from "./di.js";
|
|
2
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
3
|
import { CircleCheckFilled, CircleClose, Close, Loading, WarningFilled } from "@veltra/icons/normal";
|
|
4
|
+
import { UButton, UCollapseItem, UIcon } from "@veltra/desktop";
|
|
5
|
+
import { bem } from "@veltra/utils";
|
|
6
6
|
import "@veltra/desktop/components/collapse/style";
|
|
7
7
|
//#region src/components/ai-chat/tool-call.vue
|
|
8
8
|
const _sfc_main = /*@__PURE__*/ defineComponent({
|