@siact/sime-x-vue 0.0.9 → 0.0.11
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/sime-x-vue.mjs +874 -964
- package/dist/sime-x-vue.mjs.map +1 -1
- package/dist/sime-x-vue.umd.js +877 -966
- package/dist/sime-x-vue.umd.js.map +1 -1
- package/dist/style.css +373 -797
- package/package.json +6 -5
- package/types/DevApp.vue.d.ts +2 -2
- package/types/components/ai-chat.vue.d.ts +47 -0
- package/types/components/sime-provider.vue.d.ts +26 -49
- package/types/components/test.vue.d.ts +2 -2
- package/types/components/tool-card.vue.d.ts +12 -28
- package/types/components/voice-assistant.vue.d.ts +24 -44
- package/types/components/voice-status.vue.d.ts +8 -20
- package/types/composables/index.d.ts +6 -6
- package/types/composables/use-agent-invoke.d.ts +79 -79
- package/types/composables/use-bubble.d.ts +41 -41
- package/types/composables/use-tts.d.ts +18 -18
- package/types/composables/use-voice-recognition.d.ts +28 -28
- package/types/index.d.ts +7 -5
- package/types/injection-key.d.ts +6 -6
- package/types/lib/agent-chat-transport.d.ts +35 -0
- package/types/lib/data-stream-parser.d.ts +84 -84
- package/types/lib/utils.d.ts +14 -14
- package/types/main.d.ts +1 -1
- package/types/types.d.ts +45 -54
- package/types/utils/command-manager.d.ts +16 -0
- package/types/utils/command-types.d.ts +20 -0
- package/types/utils/index.d.ts +2 -0
- package/types/components/execution-status.vue.d.ts +0 -17
- package/types/components/sime-x.vue.d.ts +0 -43
- package/types/debug-page.vue.d.ts +0 -2
package/dist/sime-x-vue.umd.js
CHANGED
|
@@ -1,1001 +1,810 @@
|
|
|
1
1
|
(function (global, factory) {
|
|
2
|
-
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('vue'), require('@
|
|
3
|
-
typeof define === 'function' && define.amd ? define(['exports', 'vue', '@
|
|
4
|
-
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.SimeXVue = {}, global.Vue, global.
|
|
5
|
-
})(this, (function (exports, vue,
|
|
2
|
+
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('vue'), require('@ai-sdk/vue'), require('ai'), require('web-voice-kit')) :
|
|
3
|
+
typeof define === 'function' && define.amd ? define(['exports', 'vue', '@ai-sdk/vue', 'ai', 'web-voice-kit'], factory) :
|
|
4
|
+
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.SimeXVue = {}, global.Vue, global.AiSdkVue, global.AI, global.WebVoiceKit));
|
|
5
|
+
})(this, (function (exports, vue, vue$1, ai, webVoiceKit) { 'use strict';
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
7
|
+
function createAgentChatTransport(options) {
|
|
8
|
+
const { api, projectId, getCommands, headers: extraHeaders, body: extraBody } = options;
|
|
9
|
+
return new ai.DefaultChatTransport({
|
|
10
|
+
api,
|
|
11
|
+
headers: extraHeaders,
|
|
12
|
+
prepareSendMessagesRequest({ messages }) {
|
|
13
|
+
const lastUserMessage = findLastUserMessage(messages);
|
|
14
|
+
const input = lastUserMessage ? lastUserMessage.parts.filter((p) => p.type === "text").map((p) => p.text).join("") : "";
|
|
15
|
+
const historyMessages = buildHistoryMessages(messages, lastUserMessage?.id);
|
|
16
|
+
const resolvedExtraBody = typeof extraBody === "function" ? extraBody() : extraBody || {};
|
|
17
|
+
return {
|
|
18
|
+
body: {
|
|
19
|
+
input,
|
|
20
|
+
projectId: projectId || "",
|
|
21
|
+
messages: historyMessages.length > 0 ? historyMessages : void 0,
|
|
22
|
+
...resolvedExtraBody
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
});
|
|
21
27
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
appToken: {},
|
|
43
|
-
voiceConfig: {}
|
|
44
|
-
},
|
|
45
|
-
setup(__props) {
|
|
46
|
-
const props = __props;
|
|
47
|
-
const hostBridge = vue.shallowRef(new simeBridge.HostBridge({ debug: false }));
|
|
48
|
-
const startListeningRef = vue.shallowRef(async () => {
|
|
49
|
-
});
|
|
50
|
-
const stopListeningRef = vue.shallowRef(async () => {
|
|
51
|
-
});
|
|
52
|
-
const toggleCollapseRef = vue.shallowRef(async () => {
|
|
53
|
-
});
|
|
54
|
-
const openDialogRef = vue.shallowRef(async () => {
|
|
55
|
-
});
|
|
56
|
-
const closeDialogRef = vue.shallowRef(async () => {
|
|
57
|
-
});
|
|
58
|
-
vue.provide(AiChatbotXKey, {
|
|
59
|
-
chatbotUrl: () => props.chatbotUrl,
|
|
60
|
-
appId: () => props.appId,
|
|
61
|
-
appToken: () => props.appToken,
|
|
62
|
-
voiceConfig: () => props.voiceConfig || { appId: "", apiKey: "", websocketUrl: "" },
|
|
63
|
-
startListening: () => startListeningRef.value(),
|
|
64
|
-
stopListening: () => stopListeningRef.value(),
|
|
65
|
-
toggleCollapse: () => toggleCollapseRef.value(),
|
|
66
|
-
openDialog: () => openDialogRef.value(),
|
|
67
|
-
closeDialog: () => closeDialogRef.value(),
|
|
68
|
-
registerVoiceMethods: (methods) => {
|
|
69
|
-
startListeningRef.value = methods.start;
|
|
70
|
-
stopListeningRef.value = methods.stop;
|
|
71
|
-
toggleCollapseRef.value = methods.toggleCollapse;
|
|
72
|
-
openDialogRef.value = methods.openDialog;
|
|
73
|
-
closeDialogRef.value = methods.closeDialog;
|
|
74
|
-
},
|
|
75
|
-
clientCommand: () => hostBridge.value.clientCommands(),
|
|
76
|
-
hostCommads: () => hostBridge.value.hostCommands(),
|
|
77
|
-
registerCommand: (cmd) => {
|
|
78
|
-
hostBridge.value.registerCommand(cmd);
|
|
79
|
-
},
|
|
80
|
-
unregisterCommand: (name) => {
|
|
81
|
-
hostBridge.value.unregisterCommand(name);
|
|
82
|
-
},
|
|
83
|
-
async appendMessage(message) {
|
|
84
|
-
await hostBridge.value.executeClientCommand(clientCommandKey.APPEND_MESSAGE, [message]);
|
|
85
|
-
},
|
|
86
|
-
async setTheme(theme) {
|
|
87
|
-
await hostBridge.value.executeClientCommand(clientCommandKey.SET_THEME, [theme]);
|
|
88
|
-
},
|
|
89
|
-
async weak() {
|
|
90
|
-
await hostBridge.value.executeClientCommand(clientCommandKey.WAKE);
|
|
91
|
-
},
|
|
92
|
-
async startNewConversation() {
|
|
93
|
-
await hostBridge.value.executeClientCommand(clientCommandKey.START_NEW_CONVERSATION);
|
|
94
|
-
},
|
|
95
|
-
setIframeElement(iframe) {
|
|
96
|
-
hostBridge.value.setIframe(iframe);
|
|
97
|
-
},
|
|
98
|
-
async recognition(message, commands) {
|
|
99
|
-
return await hostBridge.value.executeClientCommand(clientCommandKey.RECOGNITION, [message, commands]);
|
|
100
|
-
},
|
|
101
|
-
async executeCommand(commandName, args = []) {
|
|
102
|
-
return await hostBridge.value.executeCommand(commandName, args);
|
|
28
|
+
class AgentChatTransport extends ai.DefaultChatTransport {
|
|
29
|
+
agentOptions;
|
|
30
|
+
constructor(options) {
|
|
31
|
+
const { api, headers: extraHeaders, body: extraBody, projectId } = options;
|
|
32
|
+
super({
|
|
33
|
+
api,
|
|
34
|
+
headers: extraHeaders,
|
|
35
|
+
prepareSendMessagesRequest({ messages }) {
|
|
36
|
+
const lastUserMessage = findLastUserMessage(messages);
|
|
37
|
+
const input = lastUserMessage ? lastUserMessage.parts.filter((p) => p.type === "text").map((p) => p.text).join("") : "";
|
|
38
|
+
const historyMessages = buildHistoryMessages(messages, lastUserMessage?.id);
|
|
39
|
+
const resolvedExtraBody = typeof extraBody === "function" ? extraBody() : extraBody || {};
|
|
40
|
+
return {
|
|
41
|
+
body: {
|
|
42
|
+
input,
|
|
43
|
+
projectId: projectId || "",
|
|
44
|
+
messages: historyMessages.length > 0 ? historyMessages : void 0,
|
|
45
|
+
...resolvedExtraBody
|
|
46
|
+
}
|
|
47
|
+
};
|
|
103
48
|
}
|
|
104
49
|
});
|
|
105
|
-
|
|
106
|
-
return vue.renderSlot(_ctx.$slots, "default");
|
|
107
|
-
};
|
|
50
|
+
this.agentOptions = options;
|
|
108
51
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const _hoisted_1$3 = { class: "content-container" };
|
|
112
|
-
const _hoisted_2$3 = { class: "status-header" };
|
|
113
|
-
const _hoisted_3$2 = { class: "status-text" };
|
|
114
|
-
const _hoisted_4$2 = {
|
|
115
|
-
key: 0,
|
|
116
|
-
class: "transcription-content"
|
|
117
|
-
};
|
|
118
|
-
const _hoisted_5$2 = {
|
|
119
|
-
key: 1,
|
|
120
|
-
class: "placeholder-text"
|
|
121
|
-
};
|
|
122
|
-
const _sfc_main$3 = /* @__PURE__ */ vue.defineComponent({
|
|
123
|
-
__name: "voice-status",
|
|
124
|
-
props: {
|
|
125
|
-
status: {},
|
|
126
|
-
transcriptionText: {},
|
|
127
|
-
isTranscribing: { type: Boolean }
|
|
128
|
-
},
|
|
129
|
-
setup(__props) {
|
|
130
|
-
const props = __props;
|
|
131
|
-
const currentMode = vue.computed(() => {
|
|
132
|
-
if (props.isTranscribing) return "mode-transcribing";
|
|
133
|
-
if (props.status === "wake") return "mode-wake";
|
|
134
|
-
return "mode-standby";
|
|
135
|
-
});
|
|
136
|
-
const statusLabel = vue.computed(() => {
|
|
137
|
-
if (props.isTranscribing) return "正在聆听您的问题...";
|
|
138
|
-
if (props.status === "wake") return "您好,有什么可以帮助您的吗?";
|
|
139
|
-
return "Standby";
|
|
140
|
-
});
|
|
141
|
-
return (_ctx, _cache) => {
|
|
142
|
-
return vue.openBlock(), vue.createBlock(vue.Transition, { name: "voice-panel" }, {
|
|
143
|
-
default: vue.withCtx(() => [
|
|
144
|
-
__props.status === "wake" || __props.isTranscribing ? (vue.openBlock(), vue.createElementBlock("div", {
|
|
145
|
-
key: 0,
|
|
146
|
-
class: vue.normalizeClass(["voice-status-wrapper", currentMode.value])
|
|
147
|
-
}, [
|
|
148
|
-
_cache[0] || (_cache[0] = vue.createElementVNode("div", { class: "indicator-container" }, [
|
|
149
|
-
vue.createElementVNode("div", { class: "ambient-glow" }),
|
|
150
|
-
vue.createElementVNode("div", { class: "ripple-layer" }, [
|
|
151
|
-
vue.createElementVNode("div", { class: "ripple delay-1" }),
|
|
152
|
-
vue.createElementVNode("div", { class: "ripple delay-2" }),
|
|
153
|
-
vue.createElementVNode("div", { class: "ripple delay-3" })
|
|
154
|
-
]),
|
|
155
|
-
vue.createElementVNode("div", { class: "icon-core" }, [
|
|
156
|
-
vue.createElementVNode("svg", {
|
|
157
|
-
class: "mic-icon",
|
|
158
|
-
viewBox: "0 0 24 24",
|
|
159
|
-
fill: "none",
|
|
160
|
-
stroke: "currentColor",
|
|
161
|
-
"stroke-width": "2",
|
|
162
|
-
"stroke-linecap": "round",
|
|
163
|
-
"stroke-linejoin": "round"
|
|
164
|
-
}, [
|
|
165
|
-
vue.createElementVNode("rect", {
|
|
166
|
-
x: "9",
|
|
167
|
-
y: "2",
|
|
168
|
-
width: "6",
|
|
169
|
-
height: "12",
|
|
170
|
-
rx: "3",
|
|
171
|
-
class: "mic-capsule"
|
|
172
|
-
}),
|
|
173
|
-
vue.createElementVNode("path", {
|
|
174
|
-
d: "M5 10C5 13.866 8.13401 17 12 17C15.866 17 19 13.866 19 10",
|
|
175
|
-
class: "mic-stand"
|
|
176
|
-
}),
|
|
177
|
-
vue.createElementVNode("path", {
|
|
178
|
-
d: "M12 17V21M8 21H16",
|
|
179
|
-
class: "mic-base"
|
|
180
|
-
})
|
|
181
|
-
])
|
|
182
|
-
])
|
|
183
|
-
], -1)),
|
|
184
|
-
vue.createElementVNode("div", _hoisted_1$3, [
|
|
185
|
-
vue.createElementVNode("div", _hoisted_2$3, [
|
|
186
|
-
vue.createElementVNode("span", _hoisted_3$2, vue.toDisplayString(statusLabel.value), 1)
|
|
187
|
-
]),
|
|
188
|
-
vue.createElementVNode("div", {
|
|
189
|
-
class: vue.normalizeClass(["text-window", { "has-text": !!__props.transcriptionText }])
|
|
190
|
-
}, [
|
|
191
|
-
vue.createVNode(vue.Transition, {
|
|
192
|
-
name: "fade-slide",
|
|
193
|
-
mode: "out-in"
|
|
194
|
-
}, {
|
|
195
|
-
default: vue.withCtx(() => [
|
|
196
|
-
__props.transcriptionText ? (vue.openBlock(), vue.createElementBlock("p", _hoisted_4$2, vue.toDisplayString(__props.transcriptionText), 1)) : __props.status === "wake" ? (vue.openBlock(), vue.createElementBlock("p", _hoisted_5$2, "Listening...")) : vue.createCommentVNode("", true)
|
|
197
|
-
]),
|
|
198
|
-
_: 1
|
|
199
|
-
})
|
|
200
|
-
], 2)
|
|
201
|
-
])
|
|
202
|
-
], 2)) : vue.createCommentVNode("", true)
|
|
203
|
-
]),
|
|
204
|
-
_: 1
|
|
205
|
-
});
|
|
206
|
-
};
|
|
207
|
-
}
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
const _export_sfc = (sfc, props) => {
|
|
211
|
-
const target = sfc.__vccOpts || sfc;
|
|
212
|
-
for (const [key, val] of props) {
|
|
213
|
-
target[key] = val;
|
|
214
|
-
}
|
|
215
|
-
return target;
|
|
216
|
-
};
|
|
217
|
-
|
|
218
|
-
const VoiceStatus = /* @__PURE__ */ _export_sfc(_sfc_main$3, [["__scopeId", "data-v-c9fa6caf"]]);
|
|
219
|
-
|
|
220
|
-
const _hoisted_1$2 = {
|
|
221
|
-
key: 0,
|
|
222
|
-
class: "execution-bubble"
|
|
223
|
-
};
|
|
224
|
-
const _hoisted_2$2 = { class: "exec-text" };
|
|
225
|
-
const _sfc_main$2 = /* @__PURE__ */ vue.defineComponent({
|
|
226
|
-
__name: "execution-status",
|
|
227
|
-
props: {
|
|
228
|
-
visible: { type: Boolean },
|
|
229
|
-
text: {}
|
|
230
|
-
},
|
|
231
|
-
setup(__props) {
|
|
232
|
-
return (_ctx, _cache) => {
|
|
233
|
-
return vue.openBlock(), vue.createBlock(vue.Transition, { name: "exec-bubble" }, {
|
|
234
|
-
default: vue.withCtx(() => [
|
|
235
|
-
__props.visible ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_1$2, [
|
|
236
|
-
vue.createElementVNode("span", _hoisted_2$2, vue.toDisplayString(__props.text || "执行中"), 1),
|
|
237
|
-
_cache[0] || (_cache[0] = vue.createElementVNode("div", { class: "loading-dots" }, [
|
|
238
|
-
vue.createElementVNode("span", { class: "dot" }),
|
|
239
|
-
vue.createElementVNode("span", { class: "dot" }),
|
|
240
|
-
vue.createElementVNode("span", { class: "dot" })
|
|
241
|
-
], -1))
|
|
242
|
-
])) : vue.createCommentVNode("", true)
|
|
243
|
-
]),
|
|
244
|
-
_: 1
|
|
245
|
-
});
|
|
246
|
-
};
|
|
247
|
-
}
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
const ExecutionStatus = /* @__PURE__ */ _export_sfc(_sfc_main$2, [["__scopeId", "data-v-8244ff0d"]]);
|
|
251
|
-
|
|
252
|
-
const ensureMicrophonePermission = async () => {
|
|
253
|
-
if (typeof navigator === "undefined" || typeof window === "undefined") {
|
|
254
|
-
console.log("当前环境不支持麦克风访问");
|
|
255
|
-
return false;
|
|
256
|
-
}
|
|
257
|
-
if (!navigator.mediaDevices?.getUserMedia || !navigator.mediaDevices?.enumerateDevices) {
|
|
258
|
-
console.log("当前环境不支持麦克风访问");
|
|
259
|
-
return false;
|
|
260
|
-
}
|
|
261
|
-
try {
|
|
262
|
-
const devices = await navigator.mediaDevices.enumerateDevices();
|
|
263
|
-
const audioInputDevices = devices.filter((device) => device.kind === "audioinput");
|
|
264
|
-
if (audioInputDevices.length === 0) {
|
|
265
|
-
console.log("未检测到麦克风设备,请连接麦克风后重试。");
|
|
266
|
-
return false;
|
|
267
|
-
}
|
|
268
|
-
if ("permissions" in navigator && navigator.permissions?.query) {
|
|
52
|
+
async sendMessages(options) {
|
|
53
|
+
if (this.agentOptions.getCommands) {
|
|
269
54
|
try {
|
|
270
|
-
const
|
|
271
|
-
if (
|
|
272
|
-
|
|
273
|
-
|
|
55
|
+
const commands = await this.agentOptions.getCommands();
|
|
56
|
+
if (commands && commands.length > 0) {
|
|
57
|
+
options.body = {
|
|
58
|
+
...options.body || {},
|
|
59
|
+
commands
|
|
60
|
+
};
|
|
274
61
|
}
|
|
275
|
-
} catch
|
|
276
|
-
console.warn("Permission query not supported:", e);
|
|
62
|
+
} catch {
|
|
277
63
|
}
|
|
278
64
|
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
}
|
|
287
|
-
});
|
|
288
|
-
const audioTracks = stream.getAudioTracks();
|
|
289
|
-
if (audioTracks.length === 0) {
|
|
290
|
-
console.log("无法获取麦克风音频轨道。");
|
|
291
|
-
return false;
|
|
292
|
-
}
|
|
293
|
-
const activeTrack = audioTracks[0];
|
|
294
|
-
if (!activeTrack.enabled || activeTrack.readyState !== "live") {
|
|
295
|
-
console.log("麦克风设备不可用,请检查设备连接。");
|
|
296
|
-
return false;
|
|
297
|
-
}
|
|
298
|
-
return true;
|
|
299
|
-
} finally {
|
|
300
|
-
if (stream) {
|
|
301
|
-
stream.getTracks().forEach((track) => track.stop());
|
|
302
|
-
}
|
|
65
|
+
return super.sendMessages(options);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function findLastUserMessage(messages) {
|
|
69
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
70
|
+
if (messages[i].role === "user") {
|
|
71
|
+
return messages[i];
|
|
303
72
|
}
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
73
|
+
}
|
|
74
|
+
return void 0;
|
|
75
|
+
}
|
|
76
|
+
function buildHistoryMessages(messages, excludeId) {
|
|
77
|
+
const history = [];
|
|
78
|
+
for (const msg of messages) {
|
|
79
|
+
if (msg.id === excludeId) continue;
|
|
80
|
+
if (msg.role !== "user" && msg.role !== "assistant") continue;
|
|
81
|
+
const textContent = msg.parts.filter((p) => p.type === "text").map((p) => p.text).join("");
|
|
82
|
+
if (textContent.trim()) {
|
|
83
|
+
history.push({
|
|
84
|
+
role: msg.role,
|
|
85
|
+
content: textContent
|
|
86
|
+
});
|
|
314
87
|
}
|
|
315
|
-
return false;
|
|
316
88
|
}
|
|
317
|
-
|
|
89
|
+
return history;
|
|
90
|
+
}
|
|
318
91
|
|
|
319
|
-
const _hoisted_1$1 =
|
|
320
|
-
const _hoisted_2$1 = { class: "fab-avatar-wrapper" };
|
|
321
|
-
const _hoisted_3$1 = ["src"];
|
|
322
|
-
const _hoisted_4$1 = { class: "header-left" };
|
|
323
|
-
const _hoisted_5$1 = { class: "logo-icon" };
|
|
324
|
-
const _hoisted_6$1 = ["src"];
|
|
325
|
-
const _hoisted_7$1 = { class: "title" };
|
|
326
|
-
const _hoisted_8$1 = { class: "actions" };
|
|
327
|
-
const _hoisted_9$1 = ["title"];
|
|
328
|
-
const _hoisted_10$1 = {
|
|
92
|
+
const _hoisted_1$1 = {
|
|
329
93
|
key: 0,
|
|
330
|
-
class: "
|
|
94
|
+
class: "ai-chat__welcome"
|
|
331
95
|
};
|
|
332
|
-
const
|
|
333
|
-
const
|
|
96
|
+
const _hoisted_2$1 = { class: "ai-chat__welcome-header" };
|
|
97
|
+
const _hoisted_3$1 = { class: "ai-chat__welcome-title" };
|
|
98
|
+
const _hoisted_4$1 = { class: "ai-chat__welcome-desc" };
|
|
99
|
+
const _hoisted_5$1 = { class: "ai-chat__input-area" };
|
|
100
|
+
const _hoisted_6$1 = { class: "ai-chat__input-wrapper" };
|
|
101
|
+
const _hoisted_7$1 = ["disabled"];
|
|
102
|
+
const _hoisted_8$1 = {
|
|
103
|
+
key: 0,
|
|
104
|
+
class: "ai-chat__suggestions"
|
|
105
|
+
};
|
|
106
|
+
const _hoisted_9$1 = ["onClick"];
|
|
107
|
+
const _hoisted_10$1 = { class: "ai-chat__messages-inner" };
|
|
108
|
+
const _hoisted_11$1 = { class: "ai-chat__message-content" };
|
|
109
|
+
const _hoisted_12$1 = ["innerHTML"];
|
|
334
110
|
const _hoisted_13$1 = {
|
|
335
|
-
|
|
336
|
-
|
|
111
|
+
key: 1,
|
|
112
|
+
class: "ai-chat__reasoning"
|
|
113
|
+
};
|
|
114
|
+
const _hoisted_14 = ["onClick"];
|
|
115
|
+
const _hoisted_15 = {
|
|
116
|
+
key: 0,
|
|
117
|
+
class: "ai-chat__reasoning-streaming"
|
|
118
|
+
};
|
|
119
|
+
const _hoisted_16 = {
|
|
120
|
+
key: 0,
|
|
121
|
+
class: "ai-chat__reasoning-content"
|
|
122
|
+
};
|
|
123
|
+
const _hoisted_17 = {
|
|
124
|
+
key: 2,
|
|
125
|
+
class: "ai-chat__tool"
|
|
126
|
+
};
|
|
127
|
+
const _hoisted_18 = { class: "ai-chat__tool-icon" };
|
|
128
|
+
const _hoisted_19 = {
|
|
129
|
+
key: 0,
|
|
130
|
+
class: "ai-chat__tool-spinner",
|
|
131
|
+
width: "14",
|
|
132
|
+
height: "14",
|
|
337
133
|
viewBox: "0 0 24 24",
|
|
338
134
|
fill: "none"
|
|
339
135
|
};
|
|
340
|
-
const
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
136
|
+
const _hoisted_20 = {
|
|
137
|
+
key: 1,
|
|
138
|
+
width: "14",
|
|
139
|
+
height: "14",
|
|
140
|
+
viewBox: "0 0 24 24",
|
|
141
|
+
fill: "none"
|
|
142
|
+
};
|
|
143
|
+
const _hoisted_21 = {
|
|
144
|
+
key: 2,
|
|
145
|
+
width: "14",
|
|
146
|
+
height: "14",
|
|
147
|
+
viewBox: "0 0 24 24",
|
|
148
|
+
fill: "none"
|
|
149
|
+
};
|
|
150
|
+
const _hoisted_22 = { class: "ai-chat__tool-name" };
|
|
151
|
+
const _hoisted_23 = {
|
|
152
|
+
key: 0,
|
|
153
|
+
class: "ai-chat__message-actions"
|
|
154
|
+
};
|
|
155
|
+
const _hoisted_24 = ["onClick"];
|
|
156
|
+
const _hoisted_25 = ["onClick"];
|
|
157
|
+
const _hoisted_26 = {
|
|
158
|
+
key: 0,
|
|
159
|
+
width: "12",
|
|
160
|
+
height: "12",
|
|
161
|
+
viewBox: "0 0 24 24",
|
|
162
|
+
fill: "none",
|
|
163
|
+
stroke: "currentColor",
|
|
164
|
+
"stroke-width": "2",
|
|
165
|
+
"stroke-linecap": "round",
|
|
166
|
+
"stroke-linejoin": "round"
|
|
167
|
+
};
|
|
168
|
+
const _hoisted_27 = {
|
|
169
|
+
key: 1,
|
|
170
|
+
width: "12",
|
|
171
|
+
height: "12",
|
|
172
|
+
viewBox: "0 0 24 24",
|
|
173
|
+
fill: "none",
|
|
174
|
+
stroke: "currentColor",
|
|
175
|
+
"stroke-width": "2",
|
|
176
|
+
"stroke-linecap": "round",
|
|
177
|
+
"stroke-linejoin": "round"
|
|
178
|
+
};
|
|
179
|
+
const _hoisted_28 = {
|
|
180
|
+
key: 0,
|
|
181
|
+
class: "ai-chat__thinking"
|
|
182
|
+
};
|
|
183
|
+
const _hoisted_29 = {
|
|
184
|
+
key: 0,
|
|
185
|
+
class: "ai-chat__input-area ai-chat__input-area--bottom"
|
|
186
|
+
};
|
|
187
|
+
const _hoisted_30 = { class: "ai-chat__input-wrapper" };
|
|
188
|
+
const _hoisted_31 = ["disabled"];
|
|
189
|
+
const _sfc_main$2 = /* @__PURE__ */ vue.defineComponent({
|
|
190
|
+
__name: "ai-chat",
|
|
345
191
|
props: {
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
192
|
+
api: {},
|
|
193
|
+
projectId: {},
|
|
194
|
+
isReadonly: { type: Boolean, default: false },
|
|
195
|
+
welcomeTitle: { default: "你好,有什么可以帮助你的吗?" },
|
|
196
|
+
welcomeDescription: { default: "我可以帮你回答问题、分析数据、生成报告等。" },
|
|
197
|
+
suggestions: { default: () => [] },
|
|
198
|
+
fullWidth: { type: Boolean, default: false },
|
|
199
|
+
toolNames: { default: () => ({}) },
|
|
200
|
+
transportOptions: {},
|
|
201
|
+
headers: {},
|
|
202
|
+
body: {}
|
|
353
203
|
},
|
|
354
|
-
emits: ["
|
|
355
|
-
setup(__props, { emit: __emit }) {
|
|
204
|
+
emits: ["error", "finish"],
|
|
205
|
+
setup(__props, { expose: __expose, emit: __emit }) {
|
|
206
|
+
const toolDisplayNames = {
|
|
207
|
+
generateReport: "生成报告",
|
|
208
|
+
searchKnowledge: "知识库检索",
|
|
209
|
+
resolveInstanceTargets: "解析实例目标",
|
|
210
|
+
getHistoryMetrics: "历史数据查询",
|
|
211
|
+
getRealtimeMetrics: "实时数据查询",
|
|
212
|
+
queryBitableData: "多维表格查询",
|
|
213
|
+
searchUser: "搜索用户",
|
|
214
|
+
createBitableRecord: "创建表格记录",
|
|
215
|
+
timeTool: "时间工具",
|
|
216
|
+
loadSkill: "加载技能",
|
|
217
|
+
executeCommand: "执行命令",
|
|
218
|
+
dataAnalyzer: "数据分析",
|
|
219
|
+
dataPredictor: "数据预测"
|
|
220
|
+
};
|
|
356
221
|
const props = __props;
|
|
357
222
|
const emit = __emit;
|
|
358
|
-
const
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
const
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
const getSystemTheme = () => {
|
|
374
|
-
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
375
|
-
};
|
|
376
|
-
const currentTheme = vue.ref(props.xTheme === "system" ? getSystemTheme() : props.xTheme || "light");
|
|
377
|
-
const cycleTheme = async () => {
|
|
378
|
-
currentTheme.value = currentTheme.value === "light" ? "dark" : "light";
|
|
379
|
-
aiChatbotX.setTheme(currentTheme.value);
|
|
380
|
-
};
|
|
381
|
-
const startNewConversation = () => {
|
|
382
|
-
aiChatbotX.startNewConversation();
|
|
383
|
-
};
|
|
384
|
-
const themeTooltip = vue.computed(() => currentTheme.value === "light" ? "切换到深色模式" : "切换到浅色模式");
|
|
385
|
-
const voiceButtonTooltip = vue.computed(() => {
|
|
386
|
-
if (isInitializing.value) return "初始化中...";
|
|
387
|
-
if (initError.value) return `错误: ${initError.value}`;
|
|
388
|
-
switch (voiceStatus.value) {
|
|
389
|
-
case "standby":
|
|
390
|
-
return "开启语音监听";
|
|
391
|
-
case "listening":
|
|
392
|
-
return "监听中,等待唤醒词...";
|
|
393
|
-
case "wake":
|
|
394
|
-
return "已唤醒!";
|
|
395
|
-
default:
|
|
396
|
-
return "语音监听";
|
|
223
|
+
const transport = new AgentChatTransport({
|
|
224
|
+
api: props.api,
|
|
225
|
+
projectId: props.projectId,
|
|
226
|
+
headers: props.headers,
|
|
227
|
+
body: props.body,
|
|
228
|
+
...props.transportOptions
|
|
229
|
+
});
|
|
230
|
+
const chat = new vue$1.Chat({
|
|
231
|
+
transport,
|
|
232
|
+
onError: (error) => {
|
|
233
|
+
console.error("[AiChat] error:", error);
|
|
234
|
+
emit("error", error instanceof Error ? error : new Error(String(error)));
|
|
235
|
+
},
|
|
236
|
+
onFinish: ({ message }) => {
|
|
237
|
+
emit("finish", message);
|
|
397
238
|
}
|
|
398
239
|
});
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
240
|
+
const inputText = vue.ref("");
|
|
241
|
+
const textareaRef = vue.ref(null);
|
|
242
|
+
const messagesContainerRef = vue.ref(null);
|
|
243
|
+
const showScrollButton = vue.ref(false);
|
|
244
|
+
const copiedId = vue.ref(null);
|
|
245
|
+
const reasoningOpen = vue.reactive({});
|
|
246
|
+
const isEmpty = vue.computed(() => chat.messages.length === 0);
|
|
247
|
+
const isLoading = vue.computed(() => chat.status === "streaming" || chat.status === "submitted");
|
|
248
|
+
const scrollToBottom = () => {
|
|
249
|
+
vue.nextTick(() => {
|
|
250
|
+
const el = messagesContainerRef.value;
|
|
251
|
+
if (el) {
|
|
252
|
+
el.scrollTop = el.scrollHeight;
|
|
253
|
+
}
|
|
407
254
|
});
|
|
408
|
-
}
|
|
409
|
-
const initVoiceDetector = () => {
|
|
410
|
-
if (detector || isInitializing.value) return;
|
|
411
|
-
isInitializing.value = true;
|
|
412
|
-
initError.value = "";
|
|
413
|
-
if (!props.modelPath) {
|
|
414
|
-
initError.value = "未提供语音模型文件";
|
|
415
|
-
isInitializing.value = false;
|
|
416
|
-
return;
|
|
417
|
-
}
|
|
418
|
-
try {
|
|
419
|
-
detector = new webVoiceKit.WakeWordDetectorStandalone({
|
|
420
|
-
modelPath: props.modelPath,
|
|
421
|
-
sampleRate: 16e3,
|
|
422
|
-
usePartial: true,
|
|
423
|
-
autoReset: {
|
|
424
|
-
enabled: true,
|
|
425
|
-
resetDelayMs: 5e3
|
|
426
|
-
}
|
|
427
|
-
});
|
|
428
|
-
const wakeWords = props.wakeWords || ["你好", "您好"];
|
|
429
|
-
detector.setWakeWords(wakeWords);
|
|
430
|
-
detector.onWake(() => {
|
|
431
|
-
console.log("[VoiceDetector] 检测到唤醒词");
|
|
432
|
-
wakeAnimating.value = true;
|
|
433
|
-
playWakeResponse();
|
|
434
|
-
emit("wakeUp", true);
|
|
435
|
-
aiChatbotX.weak();
|
|
436
|
-
aiChatbotX.openDialog();
|
|
437
|
-
setTimeout(() => {
|
|
438
|
-
wakeAnimating.value = false;
|
|
439
|
-
}, 1500);
|
|
440
|
-
voiceStatus.value = "listening";
|
|
441
|
-
});
|
|
442
|
-
detector.onError((error) => {
|
|
443
|
-
console.error("[VoiceDetector] 错误:", error);
|
|
444
|
-
initError.value = error.message;
|
|
445
|
-
voiceStatus.value = "standby";
|
|
446
|
-
isTranscribing.value = false;
|
|
447
|
-
if (error.message.includes("permission")) {
|
|
448
|
-
transcriptionText.value = "需要麦克风权限";
|
|
449
|
-
} else if (error.message.includes("model")) {
|
|
450
|
-
transcriptionText.value = "模型加载失败";
|
|
451
|
-
} else {
|
|
452
|
-
transcriptionText.value = "初始化失败";
|
|
453
|
-
}
|
|
454
|
-
setTimeout(() => {
|
|
455
|
-
transcriptionText.value = "";
|
|
456
|
-
}, 3e3);
|
|
457
|
-
});
|
|
458
|
-
console.log("[VoiceDetector] 初始化成功");
|
|
459
|
-
} catch (error) {
|
|
460
|
-
console.error("[VoiceDetector] 初始化失败:", error);
|
|
461
|
-
initError.value = error instanceof Error ? error.message : "初始化失败";
|
|
462
|
-
voiceStatus.value = "standby";
|
|
463
|
-
isTranscribing.value = false;
|
|
464
|
-
} finally {
|
|
465
|
-
isInitializing.value = false;
|
|
466
|
-
}
|
|
467
255
|
};
|
|
468
|
-
const
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
256
|
+
const handleScroll = () => {
|
|
257
|
+
const el = messagesContainerRef.value;
|
|
258
|
+
if (!el) return;
|
|
259
|
+
const threshold = 100;
|
|
260
|
+
showScrollButton.value = el.scrollHeight - el.scrollTop - el.clientHeight > threshold;
|
|
261
|
+
};
|
|
262
|
+
vue.watch(
|
|
263
|
+
() => chat.messages.length,
|
|
264
|
+
() => {
|
|
265
|
+
if (!showScrollButton.value) {
|
|
266
|
+
scrollToBottom();
|
|
473
267
|
}
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
const
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
268
|
+
}
|
|
269
|
+
);
|
|
270
|
+
vue.watch(
|
|
271
|
+
() => {
|
|
272
|
+
const msgs = chat.messages;
|
|
273
|
+
if (msgs.length === 0) return "";
|
|
274
|
+
const last = msgs[msgs.length - 1];
|
|
275
|
+
return last.parts.filter((p) => p.type === "text").map((p) => p.text).join("");
|
|
276
|
+
},
|
|
277
|
+
() => {
|
|
278
|
+
if (!showScrollButton.value) {
|
|
279
|
+
scrollToBottom();
|
|
485
280
|
}
|
|
486
|
-
window.speechSynthesis.speak(utterance);
|
|
487
|
-
} catch (error) {
|
|
488
|
-
console.error("[TTS] 播报失败:", error);
|
|
489
281
|
}
|
|
282
|
+
);
|
|
283
|
+
const autoResize = () => {
|
|
284
|
+
const el = textareaRef.value;
|
|
285
|
+
if (!el) return;
|
|
286
|
+
el.style.height = "auto";
|
|
287
|
+
el.style.height = `${Math.min(el.scrollHeight, 200)}px`;
|
|
490
288
|
};
|
|
491
|
-
const
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
if (!permission) return;
|
|
496
|
-
if (isInitializing.value) return;
|
|
497
|
-
if (!detector) {
|
|
498
|
-
await initVoiceDetector();
|
|
499
|
-
if (!detector) return;
|
|
500
|
-
}
|
|
501
|
-
const isCurrentlyListening = voiceStatus.value === "listening";
|
|
502
|
-
const shouldStart = targetState !== void 0 ? targetState : !isCurrentlyListening;
|
|
503
|
-
if (shouldStart === isCurrentlyListening) return;
|
|
504
|
-
try {
|
|
505
|
-
if (shouldStart) {
|
|
506
|
-
console.log("[VoiceDetector] 强制/自动启动监听...");
|
|
507
|
-
await detector.start();
|
|
508
|
-
voiceStatus.value = "listening";
|
|
509
|
-
transcriptionText.value = "";
|
|
510
|
-
isTranscribing.value = false;
|
|
511
|
-
} else {
|
|
512
|
-
console.log("[VoiceDetector] 强制/自动停止监听...");
|
|
513
|
-
await detector.stop();
|
|
514
|
-
stopTranscribing();
|
|
515
|
-
voiceStatus.value = "standby";
|
|
516
|
-
transcriptionText.value = "";
|
|
517
|
-
isTranscribing.value = false;
|
|
518
|
-
}
|
|
519
|
-
} catch (error) {
|
|
520
|
-
console.error("[VoiceDetector] 操作失败:", error);
|
|
521
|
-
voiceStatus.value = "standby";
|
|
522
|
-
transcriptionText.value = "操作失败";
|
|
523
|
-
isTranscribing.value = false;
|
|
524
|
-
setTimeout(() => {
|
|
525
|
-
transcriptionText.value = "";
|
|
526
|
-
}, 2e3);
|
|
289
|
+
const handleKeydown = (e) => {
|
|
290
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
291
|
+
e.preventDefault();
|
|
292
|
+
handleSubmit();
|
|
527
293
|
}
|
|
528
294
|
};
|
|
529
|
-
const
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
return {
|
|
540
|
-
x: Math.max(margin, Math.min(x, maxX)),
|
|
541
|
-
y: Math.max(margin, Math.min(y, maxY))
|
|
542
|
-
};
|
|
295
|
+
const handleSubmit = () => {
|
|
296
|
+
const text = inputText.value.trim();
|
|
297
|
+
if (!text || isLoading.value) return;
|
|
298
|
+
chat.sendMessage({ text });
|
|
299
|
+
inputText.value = "";
|
|
300
|
+
vue.nextTick(() => {
|
|
301
|
+
if (textareaRef.value) {
|
|
302
|
+
textareaRef.value.style.height = "auto";
|
|
303
|
+
}
|
|
304
|
+
});
|
|
543
305
|
};
|
|
544
|
-
const
|
|
545
|
-
|
|
546
|
-
const overlapY = Math.max(0, Math.min(rectA.bottom, rectB.bottom) - Math.max(rectA.top, rectB.top));
|
|
547
|
-
return overlapX * overlapY;
|
|
306
|
+
const handleSuggestionClick = (suggestion) => {
|
|
307
|
+
chat.sendMessage({ text: suggestion });
|
|
548
308
|
};
|
|
549
|
-
const
|
|
550
|
-
|
|
551
|
-
const fabRect = fabRef.value.getBoundingClientRect();
|
|
552
|
-
const safeFabRect = {
|
|
553
|
-
left: fabRect.left - FAB_SAFE_GAP,
|
|
554
|
-
right: fabRect.right + FAB_SAFE_GAP,
|
|
555
|
-
top: fabRect.top - FAB_SAFE_GAP,
|
|
556
|
-
bottom: fabRect.bottom + FAB_SAFE_GAP
|
|
557
|
-
};
|
|
558
|
-
const candidates = [
|
|
559
|
-
{ x, y },
|
|
560
|
-
{ x: safeFabRect.left - width - FAB_SAFE_GAP, y },
|
|
561
|
-
{ x: safeFabRect.right + FAB_SAFE_GAP, y },
|
|
562
|
-
{ x, y: safeFabRect.top - height - FAB_SAFE_GAP },
|
|
563
|
-
{ x, y: safeFabRect.bottom + FAB_SAFE_GAP }
|
|
564
|
-
];
|
|
565
|
-
let best = validatePosition(candidates[0].x, candidates[0].y, width, height);
|
|
566
|
-
let bestOverlap = getOverlapArea(
|
|
567
|
-
{ left: best.x, right: best.x + width, top: best.y, bottom: best.y + height },
|
|
568
|
-
safeFabRect
|
|
569
|
-
);
|
|
570
|
-
for (let i = 1; i < candidates.length; i++) {
|
|
571
|
-
const validated = validatePosition(candidates[i].x, candidates[i].y, width, height);
|
|
572
|
-
const overlap = getOverlapArea(
|
|
573
|
-
{ left: validated.x, right: validated.x + width, top: validated.y, bottom: validated.y + height },
|
|
574
|
-
safeFabRect
|
|
575
|
-
);
|
|
576
|
-
if (overlap < bestOverlap) {
|
|
577
|
-
best = validated;
|
|
578
|
-
bestOverlap = overlap;
|
|
579
|
-
if (overlap === 0) break;
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
return best;
|
|
309
|
+
const handleStop = () => {
|
|
310
|
+
chat.stop();
|
|
583
311
|
};
|
|
584
|
-
const
|
|
585
|
-
|
|
586
|
-
const
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
let x = 0;
|
|
594
|
-
let y = 0;
|
|
595
|
-
const leftX = fabRect.left - dialogWidth - margin;
|
|
596
|
-
if (leftX >= minMargin) {
|
|
597
|
-
x = leftX;
|
|
598
|
-
y = fabRect.top;
|
|
599
|
-
if (y + dialogHeight > viewportHeight - minMargin) {
|
|
600
|
-
y = viewportHeight - dialogHeight - minMargin;
|
|
601
|
-
}
|
|
602
|
-
if (y < minMargin) {
|
|
603
|
-
y = minMargin;
|
|
604
|
-
}
|
|
605
|
-
} else {
|
|
606
|
-
const rightX = fabRect.right + margin;
|
|
607
|
-
if (rightX + dialogWidth <= viewportWidth - minMargin) {
|
|
608
|
-
x = rightX;
|
|
609
|
-
y = fabRect.top;
|
|
610
|
-
if (y + dialogHeight > viewportHeight - minMargin) {
|
|
611
|
-
y = viewportHeight - dialogHeight - minMargin;
|
|
612
|
-
}
|
|
613
|
-
if (y < minMargin) {
|
|
614
|
-
y = minMargin;
|
|
615
|
-
}
|
|
616
|
-
} else {
|
|
617
|
-
x = (viewportWidth - dialogWidth) / 2;
|
|
618
|
-
const aboveY = fabRect.top - dialogHeight - margin;
|
|
619
|
-
if (aboveY >= minMargin) {
|
|
620
|
-
y = aboveY;
|
|
621
|
-
} else {
|
|
622
|
-
const belowY = fabRect.bottom + margin;
|
|
623
|
-
if (belowY + dialogHeight <= viewportHeight - minMargin) {
|
|
624
|
-
y = belowY;
|
|
625
|
-
} else {
|
|
626
|
-
y = (viewportHeight - dialogHeight) / 2;
|
|
627
|
-
}
|
|
628
|
-
}
|
|
312
|
+
const handleRegenerate = (assistantMessageId) => {
|
|
313
|
+
const msgs = chat.messages;
|
|
314
|
+
const index = msgs.findIndex((m) => m.id === assistantMessageId);
|
|
315
|
+
if (index <= 0) return;
|
|
316
|
+
let userMsg = null;
|
|
317
|
+
for (let i = index - 1; i >= 0; i--) {
|
|
318
|
+
if (msgs[i].role === "user") {
|
|
319
|
+
userMsg = msgs[i];
|
|
320
|
+
break;
|
|
629
321
|
}
|
|
630
322
|
}
|
|
631
|
-
|
|
632
|
-
const
|
|
633
|
-
|
|
634
|
-
position.y = separated.y;
|
|
635
|
-
};
|
|
636
|
-
const toggleCollapse = async () => {
|
|
637
|
-
isCollapsed.value = !isCollapsed.value;
|
|
323
|
+
if (!userMsg) return;
|
|
324
|
+
const userText = userMsg.parts.filter((p) => p.type === "text").map((p) => p.text).join("");
|
|
325
|
+
chat.messages = msgs.slice(0, msgs.indexOf(userMsg));
|
|
638
326
|
vue.nextTick(() => {
|
|
639
|
-
|
|
640
|
-
const validated = validatePosition(position.x, position.y, containerWidth.value, currentHeight);
|
|
641
|
-
const separated = keepDistanceFromFab(validated.x, validated.y, containerWidth.value, currentHeight);
|
|
642
|
-
position.x = separated.x;
|
|
643
|
-
position.y = separated.y;
|
|
327
|
+
chat.sendMessage({ text: userText });
|
|
644
328
|
});
|
|
645
329
|
};
|
|
646
|
-
const
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
drag.offsetX = position.x;
|
|
658
|
-
drag.offsetY = position.y;
|
|
659
|
-
document.addEventListener("mousemove", onDrag);
|
|
660
|
-
document.addEventListener("mouseup", stopDrag);
|
|
330
|
+
const handleCopy = async (message) => {
|
|
331
|
+
const text = message.parts.filter((p) => p.type === "text").map((p) => p.text).join("");
|
|
332
|
+
try {
|
|
333
|
+
await navigator.clipboard.writeText(text);
|
|
334
|
+
copiedId.value = message.id;
|
|
335
|
+
setTimeout(() => {
|
|
336
|
+
copiedId.value = null;
|
|
337
|
+
}, 2e3);
|
|
338
|
+
} catch {
|
|
339
|
+
console.error("Failed to copy");
|
|
340
|
+
}
|
|
661
341
|
};
|
|
662
|
-
const
|
|
663
|
-
|
|
664
|
-
const newX = drag.offsetX + (e.clientX - drag.startX);
|
|
665
|
-
const newY = drag.offsetY + (e.clientY - drag.startY);
|
|
666
|
-
const validated = validatePosition(newX, newY, containerWidth.value, isCollapsed.value ? 60 : containerHeight.value);
|
|
667
|
-
position.x = validated.x;
|
|
668
|
-
position.y = validated.y;
|
|
342
|
+
const isToolPart = (part) => {
|
|
343
|
+
return typeof part.type === "string" && part.type.startsWith("tool-");
|
|
669
344
|
};
|
|
670
|
-
const
|
|
671
|
-
|
|
672
|
-
document.removeEventListener("mousemove", onDrag);
|
|
673
|
-
document.removeEventListener("mouseup", stopDrag);
|
|
345
|
+
const isToolLoading = (part) => {
|
|
346
|
+
return part.state === "partial-call" || part.state === "call" || part.state === "input-streaming" || part.state === "input-available";
|
|
674
347
|
};
|
|
675
|
-
const
|
|
676
|
-
|
|
677
|
-
visible.value = true;
|
|
678
|
-
positionReady.value = false;
|
|
679
|
-
emit("wakeUp", true);
|
|
680
|
-
await vue.nextTick();
|
|
681
|
-
if (isFirstOpen.value) {
|
|
682
|
-
calculateInitialPosition();
|
|
683
|
-
isFirstOpen.value = false;
|
|
684
|
-
} else {
|
|
685
|
-
const validated = validatePosition(
|
|
686
|
-
position.x,
|
|
687
|
-
position.y,
|
|
688
|
-
containerWidth.value,
|
|
689
|
-
isCollapsed.value ? 60 : containerHeight.value
|
|
690
|
-
);
|
|
691
|
-
const separated = keepDistanceFromFab(
|
|
692
|
-
validated.x,
|
|
693
|
-
validated.y,
|
|
694
|
-
containerWidth.value,
|
|
695
|
-
isCollapsed.value ? 60 : containerHeight.value
|
|
696
|
-
);
|
|
697
|
-
position.x = separated.x;
|
|
698
|
-
position.y = separated.y;
|
|
699
|
-
}
|
|
700
|
-
await vue.nextTick();
|
|
701
|
-
positionReady.value = true;
|
|
702
|
-
} else {
|
|
703
|
-
positionReady.value = false;
|
|
704
|
-
visible.value = false;
|
|
705
|
-
isCollapsed.value = false;
|
|
706
|
-
emit("wakeUp", false);
|
|
707
|
-
}
|
|
348
|
+
const isToolDone = (part) => {
|
|
349
|
+
return part.state === "result" || part.state === "output-available";
|
|
708
350
|
};
|
|
709
|
-
const
|
|
710
|
-
|
|
711
|
-
aiChatbotX.setTheme(currentTheme.value);
|
|
351
|
+
const isToolError = (part) => {
|
|
352
|
+
return part.state === "error" || part.state === "output-error";
|
|
712
353
|
};
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
});
|
|
742
|
-
return (_ctx, _cache) => {
|
|
743
|
-
return vue.openBlock(), vue.createElementBlock("div", {
|
|
744
|
-
class: "
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
vue.createElementVNode("
|
|
750
|
-
ref_key: "fabRef",
|
|
751
|
-
ref: fabRef,
|
|
752
|
-
class: "assistant-fab",
|
|
753
|
-
onClick: _cache[0] || (_cache[0] = ($event) => toggleDialog(!visible.value))
|
|
754
|
-
}, [
|
|
755
|
-
!isProcessing.value ? (vue.openBlock(), vue.createBlock(VoiceStatus, {
|
|
756
|
-
key: 0,
|
|
757
|
-
class: "voice-status",
|
|
758
|
-
status: voiceStatus.value,
|
|
759
|
-
"transcription-text": transcriptionText.value,
|
|
760
|
-
"is-transcribing": isTranscribing.value,
|
|
761
|
-
style: { "width": "480px" }
|
|
762
|
-
}, null, 8, ["status", "transcription-text", "is-transcribing"])) : (vue.openBlock(), vue.createBlock(ExecutionStatus, {
|
|
763
|
-
key: 1,
|
|
764
|
-
class: "voice-status",
|
|
765
|
-
visible: isProcessing.value,
|
|
766
|
-
text: transcriptionText.value
|
|
767
|
-
}, null, 8, ["visible", "text"])),
|
|
768
|
-
vue.createElementVNode("div", _hoisted_2$1, [
|
|
769
|
-
vue.createElementVNode("img", {
|
|
770
|
-
src: __props.xLogo ? __props.xLogo : "/sime.png",
|
|
771
|
-
alt: "assistant",
|
|
772
|
-
style: vue.normalizeStyle({
|
|
773
|
-
width: __props.xSize?.width + "px"
|
|
774
|
-
})
|
|
775
|
-
}, null, 12, _hoisted_3$1),
|
|
776
|
-
vue.createVNode(vue.Transition, { name: "indicator-fade" }, {
|
|
777
|
-
default: vue.withCtx(() => [
|
|
778
|
-
voiceStatus.value === "listening" ? (vue.openBlock(), vue.createElementBlock("div", {
|
|
779
|
-
key: 0,
|
|
780
|
-
class: vue.normalizeClass(["listening-badge", { "wake-active": wakeAnimating.value }])
|
|
781
|
-
}, [..._cache[3] || (_cache[3] = [
|
|
782
|
-
vue.createElementVNode("div", { class: "listening-waves" }, [
|
|
783
|
-
vue.createElementVNode("div", { class: "wave wave-1" }),
|
|
784
|
-
vue.createElementVNode("div", { class: "wave wave-2" }),
|
|
785
|
-
vue.createElementVNode("div", { class: "wave wave-3" })
|
|
786
|
-
], -1),
|
|
787
|
-
vue.createElementVNode("div", { class: "listening-icon" }, [
|
|
788
|
-
vue.createElementVNode("svg", {
|
|
789
|
-
width: "24",
|
|
790
|
-
height: "24",
|
|
791
|
-
viewBox: "0 0 24 24",
|
|
792
|
-
fill: "none"
|
|
793
|
-
}, [
|
|
794
|
-
vue.createElementVNode("path", {
|
|
795
|
-
d: "M12 14c1.66 0 3-1.34 3-3V5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3z",
|
|
796
|
-
fill: "currentColor"
|
|
797
|
-
}),
|
|
798
|
-
vue.createElementVNode("path", {
|
|
799
|
-
d: "M17 11c0 2.76-2.24 5-5 5s-5-2.24-5-5",
|
|
800
|
-
stroke: "currentColor",
|
|
801
|
-
"stroke-width": "2",
|
|
802
|
-
"stroke-linecap": "round"
|
|
803
|
-
})
|
|
804
|
-
])
|
|
805
|
-
], -1)
|
|
806
|
-
])], 2)) : vue.createCommentVNode("", true)
|
|
807
|
-
]),
|
|
808
|
-
_: 1
|
|
809
|
-
})
|
|
810
|
-
]),
|
|
811
|
-
vue.createElementVNode("div", {
|
|
812
|
-
class: vue.normalizeClass(["fab-pulse", { active: voiceStatus.value === "listening" }])
|
|
813
|
-
}, null, 2)
|
|
814
|
-
], 512)
|
|
354
|
+
const getToolName = (part) => {
|
|
355
|
+
return part.toolName || part.type?.replace("tool-", "") || "unknown";
|
|
356
|
+
};
|
|
357
|
+
const getToolDisplayName = (name) => {
|
|
358
|
+
return props.toolNames?.[name] || toolDisplayNames[name] || name;
|
|
359
|
+
};
|
|
360
|
+
const isStreamingMessage = (messageId) => {
|
|
361
|
+
if (chat.status !== "streaming" && chat.status !== "submitted") return false;
|
|
362
|
+
const msgs = chat.messages;
|
|
363
|
+
return msgs.length > 0 && msgs[msgs.length - 1].id === messageId;
|
|
364
|
+
};
|
|
365
|
+
const toggleReasoning = (messageId) => {
|
|
366
|
+
reasoningOpen[messageId] = !reasoningOpen[messageId];
|
|
367
|
+
};
|
|
368
|
+
const renderMarkdown = (text) => {
|
|
369
|
+
if (!text) return "";
|
|
370
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>").replace(/\*(.*?)\*/g, "<em>$1</em>").replace(/`([^`]+)`/g, "<code>$1</code>").replace(/```(\w*)\n([\s\S]*?)```/g, '<pre><code class="language-$1">$2</code></pre>').replace(/\n/g, "<br/>");
|
|
371
|
+
};
|
|
372
|
+
vue.onMounted(() => {
|
|
373
|
+
autoResize();
|
|
374
|
+
});
|
|
375
|
+
__expose({
|
|
376
|
+
chat,
|
|
377
|
+
sendMessage: (text) => chat.sendMessage({ text }),
|
|
378
|
+
clearMessages: () => {
|
|
379
|
+
chat.messages = [];
|
|
380
|
+
},
|
|
381
|
+
stop: () => chat.stop()
|
|
382
|
+
});
|
|
383
|
+
return (_ctx, _cache) => {
|
|
384
|
+
return vue.openBlock(), vue.createElementBlock("div", {
|
|
385
|
+
class: vue.normalizeClass(["ai-chat", { "ai-chat--full-width": __props.fullWidth }])
|
|
386
|
+
}, [
|
|
387
|
+
isEmpty.value ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_1$1, [
|
|
388
|
+
vue.createElementVNode("div", _hoisted_2$1, [
|
|
389
|
+
vue.createElementVNode("h1", _hoisted_3$1, vue.toDisplayString(__props.welcomeTitle), 1),
|
|
390
|
+
vue.createElementVNode("p", _hoisted_4$1, vue.toDisplayString(__props.welcomeDescription), 1)
|
|
815
391
|
]),
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
vue.createElementVNode("div", {
|
|
821
|
-
ref: "dialogRef",
|
|
822
|
-
class: vue.normalizeClass(["x-dialog-container", {
|
|
823
|
-
collapsed: isCollapsed.value,
|
|
824
|
-
"is-hidden": !visible.value,
|
|
825
|
-
"position-ready": positionReady.value
|
|
826
|
-
}]),
|
|
827
|
-
style: vue.normalizeStyle({
|
|
828
|
-
width: containerWidth.value + "px",
|
|
829
|
-
height: isCollapsed.value ? "auto" : containerHeight.value + "px",
|
|
830
|
-
border: currentTheme.value === "light" && !isCollapsed.value ? "1px solid var(--border-color)" : "none",
|
|
831
|
-
"--dialog-x": position.x + "px",
|
|
832
|
-
"--dialog-y": position.y + "px"
|
|
833
|
-
}),
|
|
834
|
-
onMousedown: startDrag
|
|
392
|
+
vue.createElementVNode("div", _hoisted_5$1, [
|
|
393
|
+
vue.createElementVNode("form", {
|
|
394
|
+
class: "ai-chat__form",
|
|
395
|
+
onSubmit: vue.withModifiers(handleSubmit, ["prevent"])
|
|
835
396
|
}, [
|
|
836
|
-
vue.createElementVNode("div",
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
]
|
|
848
|
-
vue.createElementVNode("span", _hoisted_7$1, vue.toDisplayString(__props.xTitle), 1)
|
|
397
|
+
vue.createElementVNode("div", _hoisted_6$1, [
|
|
398
|
+
vue.withDirectives(vue.createElementVNode("textarea", {
|
|
399
|
+
ref_key: "textareaRef",
|
|
400
|
+
ref: textareaRef,
|
|
401
|
+
"onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => inputText.value = $event),
|
|
402
|
+
class: "ai-chat__textarea",
|
|
403
|
+
placeholder: "发送消息...",
|
|
404
|
+
rows: "1",
|
|
405
|
+
onKeydown: handleKeydown,
|
|
406
|
+
onInput: autoResize
|
|
407
|
+
}, null, 544), [
|
|
408
|
+
[vue.vModelText, inputText.value]
|
|
849
409
|
]),
|
|
850
|
-
vue.createElementVNode("
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
stroke: "currentColor",
|
|
865
|
-
"stroke-width": "2",
|
|
866
|
-
"stroke-linecap": "round"
|
|
867
|
-
})
|
|
868
|
-
], -1)
|
|
869
|
-
])]),
|
|
870
|
-
vue.createElementVNode("button", {
|
|
871
|
-
class: vue.normalizeClass(["action-btn theme-btn", {
|
|
872
|
-
active: voiceStatus.value !== "standby",
|
|
873
|
-
listening: voiceStatus.value === "listening",
|
|
874
|
-
woke: voiceStatus.value === "wake"
|
|
875
|
-
}]),
|
|
876
|
-
onClick: _cache[1] || (_cache[1] = vue.withModifiers(() => toggleVoiceMode(), ["stop"])),
|
|
877
|
-
title: voiceButtonTooltip.value
|
|
410
|
+
vue.createElementVNode("button", {
|
|
411
|
+
type: "submit",
|
|
412
|
+
class: "ai-chat__send-btn",
|
|
413
|
+
disabled: !inputText.value.trim() || isLoading.value
|
|
414
|
+
}, [..._cache[2] || (_cache[2] = [
|
|
415
|
+
vue.createElementVNode("svg", {
|
|
416
|
+
width: "16",
|
|
417
|
+
height: "16",
|
|
418
|
+
viewBox: "0 0 24 24",
|
|
419
|
+
fill: "none",
|
|
420
|
+
stroke: "currentColor",
|
|
421
|
+
"stroke-width": "2",
|
|
422
|
+
"stroke-linecap": "round",
|
|
423
|
+
"stroke-linejoin": "round"
|
|
878
424
|
}, [
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
425
|
+
vue.createElementVNode("line", {
|
|
426
|
+
x1: "22",
|
|
427
|
+
y1: "2",
|
|
428
|
+
x2: "11",
|
|
429
|
+
y2: "13"
|
|
430
|
+
}),
|
|
431
|
+
vue.createElementVNode("polygon", { points: "22 2 15 22 11 13 2 9 22 2" })
|
|
432
|
+
], -1)
|
|
433
|
+
])], 8, _hoisted_7$1)
|
|
434
|
+
])
|
|
435
|
+
], 32),
|
|
436
|
+
__props.suggestions.length > 0 ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_8$1, [
|
|
437
|
+
(vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(__props.suggestions, (suggestion) => {
|
|
438
|
+
return vue.openBlock(), vue.createElementBlock("button", {
|
|
439
|
+
key: suggestion,
|
|
440
|
+
class: "ai-chat__suggestion",
|
|
441
|
+
onClick: ($event) => handleSuggestionClick(suggestion)
|
|
442
|
+
}, vue.toDisplayString(suggestion), 9, _hoisted_9$1);
|
|
443
|
+
}), 128))
|
|
444
|
+
])) : vue.createCommentVNode("", true)
|
|
445
|
+
])
|
|
446
|
+
])) : (vue.openBlock(), vue.createElementBlock(vue.Fragment, { key: 1 }, [
|
|
447
|
+
vue.createElementVNode("div", {
|
|
448
|
+
ref_key: "messagesContainerRef",
|
|
449
|
+
ref: messagesContainerRef,
|
|
450
|
+
class: "ai-chat__messages",
|
|
451
|
+
onScroll: handleScroll
|
|
452
|
+
}, [
|
|
453
|
+
vue.createElementVNode("div", _hoisted_10$1, [
|
|
454
|
+
(vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(vue.unref(chat).messages, (message) => {
|
|
455
|
+
return vue.openBlock(), vue.createElementBlock("div", {
|
|
456
|
+
key: message.id,
|
|
457
|
+
class: "ai-chat__message-group"
|
|
458
|
+
}, [
|
|
459
|
+
(vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(message.parts, (part, partIndex) => {
|
|
460
|
+
return vue.openBlock(), vue.createElementBlock(vue.Fragment, {
|
|
461
|
+
key: `${message.id}-${partIndex}`
|
|
884
462
|
}, [
|
|
885
|
-
vue.
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
"
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
"
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
463
|
+
part.type === "text" ? (vue.openBlock(), vue.createElementBlock("div", {
|
|
464
|
+
key: 0,
|
|
465
|
+
class: vue.normalizeClass(["ai-chat__message", `ai-chat__message--${message.role}`])
|
|
466
|
+
}, [
|
|
467
|
+
vue.createElementVNode("div", _hoisted_11$1, [
|
|
468
|
+
vue.createElementVNode("div", {
|
|
469
|
+
class: "ai-chat__message-text",
|
|
470
|
+
innerHTML: renderMarkdown(part.text)
|
|
471
|
+
}, null, 8, _hoisted_12$1)
|
|
472
|
+
])
|
|
473
|
+
], 2)) : part.type === "reasoning" ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_13$1, [
|
|
474
|
+
vue.createElementVNode("button", {
|
|
475
|
+
class: "ai-chat__reasoning-trigger",
|
|
476
|
+
onClick: ($event) => toggleReasoning(message.id)
|
|
477
|
+
}, [
|
|
478
|
+
(vue.openBlock(), vue.createElementBlock("svg", {
|
|
479
|
+
class: vue.normalizeClass(["ai-chat__reasoning-icon", { "ai-chat__reasoning-icon--open": reasoningOpen[message.id] }]),
|
|
480
|
+
width: "12",
|
|
481
|
+
height: "12",
|
|
482
|
+
viewBox: "0 0 24 24",
|
|
483
|
+
fill: "none",
|
|
484
|
+
stroke: "currentColor",
|
|
485
|
+
"stroke-width": "2"
|
|
486
|
+
}, [..._cache[3] || (_cache[3] = [
|
|
487
|
+
vue.createElementVNode("polyline", { points: "9 18 15 12 9 6" }, null, -1)
|
|
488
|
+
])], 2)),
|
|
489
|
+
_cache[4] || (_cache[4] = vue.createElementVNode("span", null, "思考过程", -1)),
|
|
490
|
+
isStreamingMessage(message.id) && partIndex === message.parts.length - 1 ? (vue.openBlock(), vue.createElementBlock("span", _hoisted_15)) : vue.createCommentVNode("", true)
|
|
491
|
+
], 8, _hoisted_14),
|
|
492
|
+
reasoningOpen[message.id] ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_16, vue.toDisplayString(part.text), 1)) : vue.createCommentVNode("", true)
|
|
493
|
+
])) : isToolPart(part) ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_17, [
|
|
494
|
+
vue.createElementVNode("div", {
|
|
495
|
+
class: vue.normalizeClass(["ai-chat__tool-step", {
|
|
496
|
+
"ai-chat__tool-step--loading": isToolLoading(part),
|
|
497
|
+
"ai-chat__tool-step--done": isToolDone(part),
|
|
498
|
+
"ai-chat__tool-step--error": isToolError(part)
|
|
499
|
+
}])
|
|
500
|
+
}, [
|
|
501
|
+
vue.createElementVNode("span", _hoisted_18, [
|
|
502
|
+
isToolLoading(part) ? (vue.openBlock(), vue.createElementBlock("svg", _hoisted_19, [..._cache[5] || (_cache[5] = [
|
|
503
|
+
vue.createElementVNode("circle", {
|
|
504
|
+
cx: "12",
|
|
505
|
+
cy: "12",
|
|
506
|
+
r: "10",
|
|
507
|
+
stroke: "currentColor",
|
|
508
|
+
"stroke-width": "2.5",
|
|
509
|
+
"stroke-linecap": "round",
|
|
510
|
+
"stroke-dasharray": "31.4 31.4"
|
|
511
|
+
}, null, -1)
|
|
512
|
+
])])) : isToolDone(part) ? (vue.openBlock(), vue.createElementBlock("svg", _hoisted_20, [..._cache[6] || (_cache[6] = [
|
|
513
|
+
vue.createElementVNode("path", {
|
|
514
|
+
d: "M20 6L9 17l-5-5",
|
|
515
|
+
stroke: "currentColor",
|
|
516
|
+
"stroke-width": "2.5",
|
|
517
|
+
"stroke-linecap": "round",
|
|
518
|
+
"stroke-linejoin": "round"
|
|
519
|
+
}, null, -1)
|
|
520
|
+
])])) : isToolError(part) ? (vue.openBlock(), vue.createElementBlock("svg", _hoisted_21, [..._cache[7] || (_cache[7] = [
|
|
521
|
+
vue.createElementVNode("circle", {
|
|
522
|
+
cx: "12",
|
|
523
|
+
cy: "12",
|
|
524
|
+
r: "10",
|
|
525
|
+
stroke: "currentColor",
|
|
526
|
+
"stroke-width": "2"
|
|
527
|
+
}, null, -1),
|
|
528
|
+
vue.createElementVNode("path", {
|
|
529
|
+
d: "M15 9l-6 6M9 9l6 6",
|
|
530
|
+
stroke: "currentColor",
|
|
531
|
+
"stroke-width": "2",
|
|
532
|
+
"stroke-linecap": "round"
|
|
533
|
+
}, null, -1)
|
|
534
|
+
])])) : vue.createCommentVNode("", true)
|
|
535
|
+
]),
|
|
536
|
+
vue.createElementVNode("span", _hoisted_22, vue.toDisplayString(getToolDisplayName(getToolName(part))), 1)
|
|
537
|
+
], 2)
|
|
538
|
+
])) : vue.createCommentVNode("", true)
|
|
539
|
+
], 64);
|
|
540
|
+
}), 128)),
|
|
541
|
+
message.role === "assistant" && !isStreamingMessage(message.id) ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_23, [
|
|
542
|
+
vue.createElementVNode("button", {
|
|
543
|
+
class: "ai-chat__action-btn",
|
|
544
|
+
title: "重新生成",
|
|
545
|
+
onClick: ($event) => handleRegenerate(message.id)
|
|
546
|
+
}, [..._cache[8] || (_cache[8] = [
|
|
547
|
+
vue.createElementVNode("svg", {
|
|
548
|
+
width: "12",
|
|
549
|
+
height: "12",
|
|
550
|
+
viewBox: "0 0 24 24",
|
|
551
|
+
fill: "none",
|
|
908
552
|
stroke: "currentColor",
|
|
909
553
|
"stroke-width": "2",
|
|
910
554
|
"stroke-linecap": "round",
|
|
911
555
|
"stroke-linejoin": "round"
|
|
912
|
-
}
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
width: "16",
|
|
923
|
-
height: "16",
|
|
924
|
-
viewBox: "0 0 24 24",
|
|
925
|
-
fill: "none"
|
|
556
|
+
}, [
|
|
557
|
+
vue.createElementVNode("polyline", { points: "23 4 23 10 17 10" }),
|
|
558
|
+
vue.createElementVNode("polyline", { points: "1 20 1 14 7 14" }),
|
|
559
|
+
vue.createElementVNode("path", { d: "M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15" })
|
|
560
|
+
], -1)
|
|
561
|
+
])], 8, _hoisted_24),
|
|
562
|
+
vue.createElementVNode("button", {
|
|
563
|
+
class: "ai-chat__action-btn",
|
|
564
|
+
title: "复制",
|
|
565
|
+
onClick: ($event) => handleCopy(message)
|
|
926
566
|
}, [
|
|
927
|
-
vue.
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
567
|
+
!copiedId.value || copiedId.value !== message.id ? (vue.openBlock(), vue.createElementBlock("svg", _hoisted_26, [..._cache[9] || (_cache[9] = [
|
|
568
|
+
vue.createElementVNode("rect", {
|
|
569
|
+
x: "9",
|
|
570
|
+
y: "9",
|
|
571
|
+
width: "13",
|
|
572
|
+
height: "13",
|
|
573
|
+
rx: "2",
|
|
574
|
+
ry: "2"
|
|
575
|
+
}, null, -1),
|
|
576
|
+
vue.createElementVNode("path", { d: "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" }, null, -1)
|
|
577
|
+
])])) : (vue.openBlock(), vue.createElementBlock("svg", _hoisted_27, [..._cache[10] || (_cache[10] = [
|
|
578
|
+
vue.createElementVNode("polyline", { points: "20 6 9 17 4 12" }, null, -1)
|
|
579
|
+
])]))
|
|
580
|
+
], 8, _hoisted_25)
|
|
581
|
+
])) : vue.createCommentVNode("", true)
|
|
582
|
+
]);
|
|
583
|
+
}), 128)),
|
|
584
|
+
vue.unref(chat).status === "submitted" ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_28, [..._cache[11] || (_cache[11] = [
|
|
585
|
+
vue.createElementVNode("div", { class: "ai-chat__thinking-dots" }, [
|
|
586
|
+
vue.createElementVNode("span"),
|
|
587
|
+
vue.createElementVNode("span"),
|
|
588
|
+
vue.createElementVNode("span")
|
|
589
|
+
], -1)
|
|
590
|
+
])])) : vue.createCommentVNode("", true)
|
|
591
|
+
])
|
|
592
|
+
], 544),
|
|
593
|
+
vue.createVNode(vue.Transition, { name: "fade" }, {
|
|
594
|
+
default: vue.withCtx(() => [
|
|
595
|
+
showScrollButton.value ? (vue.openBlock(), vue.createElementBlock("button", {
|
|
596
|
+
key: 0,
|
|
597
|
+
class: "ai-chat__scroll-btn",
|
|
598
|
+
onClick: scrollToBottom
|
|
599
|
+
}, [..._cache[12] || (_cache[12] = [
|
|
600
|
+
vue.createElementVNode("svg", {
|
|
601
|
+
width: "16",
|
|
602
|
+
height: "16",
|
|
603
|
+
viewBox: "0 0 24 24",
|
|
604
|
+
fill: "none",
|
|
605
|
+
stroke: "currentColor",
|
|
606
|
+
"stroke-width": "2",
|
|
607
|
+
"stroke-linecap": "round",
|
|
608
|
+
"stroke-linejoin": "round"
|
|
609
|
+
}, [
|
|
610
|
+
vue.createElementVNode("polyline", { points: "6 9 12 15 18 9" })
|
|
611
|
+
], -1)
|
|
612
|
+
])])) : vue.createCommentVNode("", true)
|
|
613
|
+
]),
|
|
614
|
+
_: 1
|
|
615
|
+
}),
|
|
616
|
+
!__props.isReadonly ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_29, [
|
|
617
|
+
vue.createElementVNode("form", {
|
|
618
|
+
class: "ai-chat__form",
|
|
619
|
+
onSubmit: vue.withModifiers(handleSubmit, ["prevent"])
|
|
620
|
+
}, [
|
|
621
|
+
vue.createElementVNode("div", _hoisted_30, [
|
|
622
|
+
vue.withDirectives(vue.createElementVNode("textarea", {
|
|
623
|
+
ref_key: "textareaRef",
|
|
624
|
+
ref: textareaRef,
|
|
625
|
+
"onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => inputText.value = $event),
|
|
626
|
+
class: "ai-chat__textarea",
|
|
627
|
+
placeholder: "发送消息...",
|
|
628
|
+
rows: "1",
|
|
629
|
+
onKeydown: handleKeydown,
|
|
630
|
+
onInput: autoResize
|
|
631
|
+
}, null, 544), [
|
|
632
|
+
[vue.vModelText, inputText.value]
|
|
633
|
+
]),
|
|
634
|
+
vue.unref(chat).status === "streaming" || vue.unref(chat).status === "submitted" ? (vue.openBlock(), vue.createElementBlock("button", {
|
|
635
|
+
key: 0,
|
|
636
|
+
type: "button",
|
|
637
|
+
class: "ai-chat__stop-btn",
|
|
638
|
+
onClick: handleStop
|
|
639
|
+
}, [..._cache[13] || (_cache[13] = [
|
|
640
|
+
vue.createElementVNode("svg", {
|
|
641
|
+
width: "14",
|
|
642
|
+
height: "14",
|
|
643
|
+
viewBox: "0 0 24 24",
|
|
644
|
+
fill: "currentColor"
|
|
944
645
|
}, [
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
class: "x-iframe",
|
|
984
|
-
allow: "microphone *; storage-access *; camera *",
|
|
985
|
-
frameborder: "0",
|
|
986
|
-
onLoad: handleIframeLoad
|
|
987
|
-
}, null, 40, _hoisted_15)
|
|
988
|
-
], 6)
|
|
989
|
-
], 38)
|
|
990
|
-
]),
|
|
991
|
-
_: 1
|
|
992
|
-
})
|
|
993
|
-
], 8, _hoisted_1$1);
|
|
646
|
+
vue.createElementVNode("rect", {
|
|
647
|
+
x: "6",
|
|
648
|
+
y: "6",
|
|
649
|
+
width: "12",
|
|
650
|
+
height: "12",
|
|
651
|
+
rx: "2"
|
|
652
|
+
})
|
|
653
|
+
], -1)
|
|
654
|
+
])])) : (vue.openBlock(), vue.createElementBlock("button", {
|
|
655
|
+
key: 1,
|
|
656
|
+
type: "submit",
|
|
657
|
+
class: "ai-chat__send-btn",
|
|
658
|
+
disabled: !inputText.value.trim()
|
|
659
|
+
}, [..._cache[14] || (_cache[14] = [
|
|
660
|
+
vue.createElementVNode("svg", {
|
|
661
|
+
width: "16",
|
|
662
|
+
height: "16",
|
|
663
|
+
viewBox: "0 0 24 24",
|
|
664
|
+
fill: "none",
|
|
665
|
+
stroke: "currentColor",
|
|
666
|
+
"stroke-width": "2",
|
|
667
|
+
"stroke-linecap": "round",
|
|
668
|
+
"stroke-linejoin": "round"
|
|
669
|
+
}, [
|
|
670
|
+
vue.createElementVNode("line", {
|
|
671
|
+
x1: "22",
|
|
672
|
+
y1: "2",
|
|
673
|
+
x2: "11",
|
|
674
|
+
y2: "13"
|
|
675
|
+
}),
|
|
676
|
+
vue.createElementVNode("polygon", { points: "22 2 15 22 11 13 2 9 22 2" })
|
|
677
|
+
], -1)
|
|
678
|
+
])], 8, _hoisted_31))
|
|
679
|
+
])
|
|
680
|
+
], 32)
|
|
681
|
+
])) : vue.createCommentVNode("", true)
|
|
682
|
+
], 64))
|
|
683
|
+
], 2);
|
|
994
684
|
};
|
|
995
685
|
}
|
|
996
686
|
});
|
|
997
687
|
|
|
998
|
-
const
|
|
688
|
+
const _export_sfc = (sfc, props) => {
|
|
689
|
+
const target = sfc.__vccOpts || sfc;
|
|
690
|
+
for (const [key, val] of props) {
|
|
691
|
+
target[key] = val;
|
|
692
|
+
}
|
|
693
|
+
return target;
|
|
694
|
+
};
|
|
695
|
+
|
|
696
|
+
const aiChat = /* @__PURE__ */ _export_sfc(_sfc_main$2, [["__scopeId", "data-v-958fd919"]]);
|
|
697
|
+
|
|
698
|
+
class CommandManager {
|
|
699
|
+
commands = /* @__PURE__ */ new Map();
|
|
700
|
+
debug;
|
|
701
|
+
constructor(options = {}) {
|
|
702
|
+
this.debug = options.debug ?? false;
|
|
703
|
+
}
|
|
704
|
+
registerCommand(command) {
|
|
705
|
+
this.commands.set(command.name, command);
|
|
706
|
+
this.log("注册命令", `${command.name}: ${command.description}`);
|
|
707
|
+
}
|
|
708
|
+
unregisterCommand(name) {
|
|
709
|
+
const deleted = this.commands.delete(name);
|
|
710
|
+
if (deleted) {
|
|
711
|
+
this.log("命令已注销", name);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
async executeCommand(command, args = []) {
|
|
715
|
+
const commandDef = this.commands.get(command);
|
|
716
|
+
if (!commandDef) {
|
|
717
|
+
throw new Error(`命令 "${command}" 未找到`);
|
|
718
|
+
}
|
|
719
|
+
this.log("执行命令", command, args);
|
|
720
|
+
return await commandDef.handler(...args);
|
|
721
|
+
}
|
|
722
|
+
getCommands() {
|
|
723
|
+
return Array.from(this.commands.values()).map((cmd) => ({
|
|
724
|
+
name: cmd.name,
|
|
725
|
+
description: cmd.description,
|
|
726
|
+
parameters: cmd.parameters
|
|
727
|
+
}));
|
|
728
|
+
}
|
|
729
|
+
hasCommand(name) {
|
|
730
|
+
return this.commands.has(name);
|
|
731
|
+
}
|
|
732
|
+
clear() {
|
|
733
|
+
this.commands.clear();
|
|
734
|
+
this.log("", "所有命令已清空");
|
|
735
|
+
}
|
|
736
|
+
log(prefix, msg, ...args) {
|
|
737
|
+
(/* @__PURE__ */ new Date()).toLocaleTimeString([], {
|
|
738
|
+
hour: "2-digit",
|
|
739
|
+
minute: "2-digit",
|
|
740
|
+
second: "2-digit"
|
|
741
|
+
});
|
|
742
|
+
console.log(
|
|
743
|
+
`%c ${prefix}`,
|
|
744
|
+
"background:#7c3aed;color:white;padding:2px 6px;border-radius:3px 0 0 3px;font-weight:bold;",
|
|
745
|
+
`${msg}`
|
|
746
|
+
);
|
|
747
|
+
if (args.length > 0) {
|
|
748
|
+
console.log(...args);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
const AiChatbotXKey = Symbol("sime-x");
|
|
754
|
+
function injectStrict(key, defaultValue, treatDefaultAsFactory) {
|
|
755
|
+
let result;
|
|
756
|
+
if (defaultValue === void 0) {
|
|
757
|
+
result = vue.inject(key);
|
|
758
|
+
} else if (treatDefaultAsFactory === true) {
|
|
759
|
+
result = vue.inject(key, defaultValue, true);
|
|
760
|
+
} else {
|
|
761
|
+
result = vue.inject(key, defaultValue, false);
|
|
762
|
+
}
|
|
763
|
+
if (!result) {
|
|
764
|
+
throw new Error(`Could not resolve ${key.description}`);
|
|
765
|
+
}
|
|
766
|
+
return result;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
const _sfc_main$1 = /* @__PURE__ */ vue.defineComponent({
|
|
770
|
+
__name: "sime-provider",
|
|
771
|
+
props: {
|
|
772
|
+
project: {},
|
|
773
|
+
description: {},
|
|
774
|
+
debug: { type: Boolean },
|
|
775
|
+
chatbotUrl: {},
|
|
776
|
+
appId: {},
|
|
777
|
+
appToken: {}
|
|
778
|
+
},
|
|
779
|
+
setup(__props) {
|
|
780
|
+
const props = __props;
|
|
781
|
+
const commandManager = vue.shallowRef(new CommandManager({ debug: props.debug ?? false }));
|
|
782
|
+
const stopBroadcastRef = vue.shallowRef(async () => {
|
|
783
|
+
});
|
|
784
|
+
vue.provide(AiChatbotXKey, {
|
|
785
|
+
chatbotUrl: () => props.chatbotUrl,
|
|
786
|
+
appId: () => props.appId,
|
|
787
|
+
appToken: () => props.appToken,
|
|
788
|
+
stopBroadcast: () => stopBroadcastRef.value(),
|
|
789
|
+
registerVoiceMethods: (methods) => {
|
|
790
|
+
if (methods.stopBroadcast) stopBroadcastRef.value = methods.stopBroadcast;
|
|
791
|
+
},
|
|
792
|
+
getCommads: async () => commandManager.value.getCommands(),
|
|
793
|
+
registerCommand: (cmd) => {
|
|
794
|
+
commandManager.value.registerCommand(cmd);
|
|
795
|
+
},
|
|
796
|
+
unregisterCommand: (name) => {
|
|
797
|
+
commandManager.value.unregisterCommand(name);
|
|
798
|
+
},
|
|
799
|
+
async executeCommand(commandName, args = []) {
|
|
800
|
+
return await commandManager.value.executeCommand(commandName, args);
|
|
801
|
+
}
|
|
802
|
+
});
|
|
803
|
+
return (_ctx, _cache) => {
|
|
804
|
+
return vue.renderSlot(_ctx.$slots, "default");
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
});
|
|
999
808
|
|
|
1000
809
|
function useTTS(getVoiceConfig) {
|
|
1001
810
|
const isSpeaking = vue.ref(false);
|
|
@@ -1035,9 +844,9 @@
|
|
|
1035
844
|
apiSecret: vc.apiSecret,
|
|
1036
845
|
websocketUrl: vc.ttsWebsocketUrl || "wss://tts-api.xfyun.cn/v2/tts",
|
|
1037
846
|
vcn: vc.ttsVcn || "xiaoyan",
|
|
1038
|
-
speed:
|
|
1039
|
-
volume:
|
|
1040
|
-
pitch: 50,
|
|
847
|
+
speed: vc.speed || 55,
|
|
848
|
+
volume: vc.volume || 90,
|
|
849
|
+
pitch: vc.pitch || 50,
|
|
1041
850
|
aue: "raw",
|
|
1042
851
|
auf: "audio/L16;rate=16000",
|
|
1043
852
|
tte: "UTF8",
|
|
@@ -1234,6 +1043,73 @@
|
|
|
1234
1043
|
};
|
|
1235
1044
|
}
|
|
1236
1045
|
|
|
1046
|
+
const ensureMicrophonePermission = async () => {
|
|
1047
|
+
if (typeof navigator === "undefined" || typeof window === "undefined") {
|
|
1048
|
+
console.log("当前环境不支持麦克风访问");
|
|
1049
|
+
return false;
|
|
1050
|
+
}
|
|
1051
|
+
if (!navigator.mediaDevices?.getUserMedia || !navigator.mediaDevices?.enumerateDevices) {
|
|
1052
|
+
console.log("当前环境不支持麦克风访问");
|
|
1053
|
+
return false;
|
|
1054
|
+
}
|
|
1055
|
+
try {
|
|
1056
|
+
const devices = await navigator.mediaDevices.enumerateDevices();
|
|
1057
|
+
const audioInputDevices = devices.filter((device) => device.kind === "audioinput");
|
|
1058
|
+
if (audioInputDevices.length === 0) {
|
|
1059
|
+
console.log("未检测到麦克风设备,请连接麦克风后重试。");
|
|
1060
|
+
return false;
|
|
1061
|
+
}
|
|
1062
|
+
if ("permissions" in navigator && navigator.permissions?.query) {
|
|
1063
|
+
try {
|
|
1064
|
+
const status = await navigator.permissions.query({ name: "microphone" });
|
|
1065
|
+
if (status.state === "denied") {
|
|
1066
|
+
console.log("麦克风权限被禁用,请在浏览器设置中开启。");
|
|
1067
|
+
return false;
|
|
1068
|
+
}
|
|
1069
|
+
} catch (e) {
|
|
1070
|
+
console.warn("Permission query not supported:", e);
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
let stream = null;
|
|
1074
|
+
try {
|
|
1075
|
+
stream = await navigator.mediaDevices.getUserMedia({
|
|
1076
|
+
audio: {
|
|
1077
|
+
echoCancellation: true,
|
|
1078
|
+
noiseSuppression: true,
|
|
1079
|
+
autoGainControl: true
|
|
1080
|
+
}
|
|
1081
|
+
});
|
|
1082
|
+
const audioTracks = stream.getAudioTracks();
|
|
1083
|
+
if (audioTracks.length === 0) {
|
|
1084
|
+
console.log("无法获取麦克风音频轨道。");
|
|
1085
|
+
return false;
|
|
1086
|
+
}
|
|
1087
|
+
const activeTrack = audioTracks[0];
|
|
1088
|
+
if (!activeTrack.enabled || activeTrack.readyState !== "live") {
|
|
1089
|
+
console.log("麦克风设备不可用,请检查设备连接。");
|
|
1090
|
+
return false;
|
|
1091
|
+
}
|
|
1092
|
+
return true;
|
|
1093
|
+
} finally {
|
|
1094
|
+
if (stream) {
|
|
1095
|
+
stream.getTracks().forEach((track) => track.stop());
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
} catch (error) {
|
|
1099
|
+
console.error("Microphone permission check failed", error);
|
|
1100
|
+
if (error.name === "NotFoundError" || error.name === "DevicesNotFoundError") {
|
|
1101
|
+
console.log("未检测到麦克风设备,请连接麦克风后重试。");
|
|
1102
|
+
} else if (error.name === "NotAllowedError" || error.name === "PermissionDeniedError") {
|
|
1103
|
+
console.log("麦克风权限被拒绝,请在浏览器设置中允许访问。");
|
|
1104
|
+
} else if (error.name === "NotReadableError" || error.name === "TrackStartError") {
|
|
1105
|
+
console.log("麦克风被其他应用占用或无法访问。");
|
|
1106
|
+
} else {
|
|
1107
|
+
console.log("无法访问麦克风,请检查设备连接和浏览器权限。");
|
|
1108
|
+
}
|
|
1109
|
+
return false;
|
|
1110
|
+
}
|
|
1111
|
+
};
|
|
1112
|
+
|
|
1237
1113
|
function useVoiceRecognition(options) {
|
|
1238
1114
|
const voiceStatus = vue.ref("standby");
|
|
1239
1115
|
const isTranscribing = vue.ref(false);
|
|
@@ -1459,7 +1335,8 @@
|
|
|
1459
1335
|
callbacks.onFinish?.(parsed);
|
|
1460
1336
|
break;
|
|
1461
1337
|
case "error":
|
|
1462
|
-
|
|
1338
|
+
case "tool-output-error":
|
|
1339
|
+
callbacks.onError?.(parsed.errorText || parsed.error || "Unknown error", parsed);
|
|
1463
1340
|
break;
|
|
1464
1341
|
case "start":
|
|
1465
1342
|
case "text-start":
|
|
@@ -1718,7 +1595,16 @@
|
|
|
1718
1595
|
}
|
|
1719
1596
|
emitUpdate();
|
|
1720
1597
|
},
|
|
1721
|
-
onError(error) {
|
|
1598
|
+
onError(error, data) {
|
|
1599
|
+
const toolCallId = data?.toolCallId;
|
|
1600
|
+
if (toolCallId) {
|
|
1601
|
+
toolCalls.delete(toolCallId);
|
|
1602
|
+
const idx = findToolPartIndex(toolCallId);
|
|
1603
|
+
if (idx !== -1) {
|
|
1604
|
+
parts.splice(idx, 1);
|
|
1605
|
+
emitUpdate();
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1722
1608
|
console.error("[DataStreamParser] stream error:", error);
|
|
1723
1609
|
},
|
|
1724
1610
|
onStepFinish(_data) {
|
|
@@ -1788,20 +1674,28 @@
|
|
|
1788
1674
|
currentToolParts.value = [];
|
|
1789
1675
|
executingTools.value = /* @__PURE__ */ new Set();
|
|
1790
1676
|
};
|
|
1677
|
+
const extractExecutableCommands = (payload) => {
|
|
1678
|
+
if (!payload || typeof payload !== "object") return [];
|
|
1679
|
+
const commands = payload.commands;
|
|
1680
|
+
if (!Array.isArray(commands) || commands.length === 0) return [];
|
|
1681
|
+
return commands.filter((cmd) => cmd && typeof cmd === "object" && typeof cmd.name === "string" && cmd.name.trim()).map((cmd) => ({
|
|
1682
|
+
name: cmd.name,
|
|
1683
|
+
args: Array.isArray(cmd.args) ? cmd.args : []
|
|
1684
|
+
}));
|
|
1685
|
+
};
|
|
1791
1686
|
const executeHostCommands = async (toolCallId, result) => {
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
if (!Array.isArray(commands) || commands.length === 0) return;
|
|
1687
|
+
const commands = extractExecutableCommands(result);
|
|
1688
|
+
if (commands.length === 0) return false;
|
|
1795
1689
|
try {
|
|
1796
1690
|
executingTools.value = /* @__PURE__ */ new Set([...executingTools.value, toolCallId]);
|
|
1797
1691
|
for (const cmd of commands) {
|
|
1798
|
-
const args = Array.isArray(cmd.args) ? cmd.args : [];
|
|
1799
1692
|
try {
|
|
1800
|
-
await aiChatbotX.executeCommand(cmd.name, args);
|
|
1693
|
+
await aiChatbotX.executeCommand(cmd.name, cmd.args);
|
|
1801
1694
|
} catch (cmdErr) {
|
|
1802
1695
|
console.error(`[AgentInvoke] 执行命令 ${cmd.name} 失败:`, cmdErr);
|
|
1803
1696
|
}
|
|
1804
1697
|
}
|
|
1698
|
+
return true;
|
|
1805
1699
|
} finally {
|
|
1806
1700
|
const next = new Set(executingTools.value);
|
|
1807
1701
|
next.delete(toolCallId);
|
|
@@ -1835,8 +1729,9 @@
|
|
|
1835
1729
|
bubble.open();
|
|
1836
1730
|
let prevTextLength = 0;
|
|
1837
1731
|
const processedToolResults = /* @__PURE__ */ new Set();
|
|
1732
|
+
const processingToolResults = /* @__PURE__ */ new Set();
|
|
1838
1733
|
abortController = new AbortController();
|
|
1839
|
-
const commands = await aiChatbotX.
|
|
1734
|
+
const commands = await aiChatbotX.getCommads();
|
|
1840
1735
|
conversationHistory.value.length > 0 ? [...conversationHistory.value] : void 0;
|
|
1841
1736
|
try {
|
|
1842
1737
|
const response = await fetch(options.endpoint.value, {
|
|
@@ -1872,7 +1767,8 @@
|
|
|
1872
1767
|
};
|
|
1873
1768
|
currentToolParts.value = [...currentToolParts.value, toolPart];
|
|
1874
1769
|
if (tr.toolName === "executeCommand") {
|
|
1875
|
-
|
|
1770
|
+
console.log("executeCommand", tr.result);
|
|
1771
|
+
void executeHostCommands(toolPart.toolCallId, tr.result);
|
|
1876
1772
|
}
|
|
1877
1773
|
}
|
|
1878
1774
|
}
|
|
@@ -1889,13 +1785,23 @@
|
|
|
1889
1785
|
);
|
|
1890
1786
|
currentToolParts.value = toolParts;
|
|
1891
1787
|
for (const part of toolParts) {
|
|
1892
|
-
if (part.toolName === "executeCommand" && !processedToolResults.has(part.toolCallId)) {
|
|
1788
|
+
if (part.toolName === "executeCommand" && !processedToolResults.has(part.toolCallId) && !processingToolResults.has(part.toolCallId)) {
|
|
1893
1789
|
if (part.type === "tool-call" && part.state === "call" && part.args) {
|
|
1894
|
-
|
|
1895
|
-
executeHostCommands(part.toolCallId, part.args)
|
|
1790
|
+
processingToolResults.add(part.toolCallId);
|
|
1791
|
+
void executeHostCommands(part.toolCallId, part.args).then((executed) => {
|
|
1792
|
+
if (executed) {
|
|
1793
|
+
processedToolResults.add(part.toolCallId);
|
|
1794
|
+
}
|
|
1795
|
+
processingToolResults.delete(part.toolCallId);
|
|
1796
|
+
});
|
|
1896
1797
|
} else if (part.type === "tool-result" && part.result) {
|
|
1897
|
-
|
|
1898
|
-
executeHostCommands(part.toolCallId, part.result)
|
|
1798
|
+
processingToolResults.add(part.toolCallId);
|
|
1799
|
+
void executeHostCommands(part.toolCallId, part.result).then((executed) => {
|
|
1800
|
+
if (executed) {
|
|
1801
|
+
processedToolResults.add(part.toolCallId);
|
|
1802
|
+
}
|
|
1803
|
+
processingToolResults.delete(part.toolCallId);
|
|
1804
|
+
});
|
|
1899
1805
|
}
|
|
1900
1806
|
}
|
|
1901
1807
|
}
|
|
@@ -2016,11 +1922,7 @@
|
|
|
2016
1922
|
const aiChatbotX = injectStrict(AiChatbotXKey);
|
|
2017
1923
|
const getVoiceConfig = () => {
|
|
2018
1924
|
if (props.voiceConfig) return props.voiceConfig;
|
|
2019
|
-
|
|
2020
|
-
return aiChatbotX.voiceConfig();
|
|
2021
|
-
} catch {
|
|
2022
|
-
return null;
|
|
2023
|
-
}
|
|
1925
|
+
return null;
|
|
2024
1926
|
};
|
|
2025
1927
|
const endpoint = vue.computed(() => {
|
|
2026
1928
|
return props.invokeUrl || "http://localhost:3001/agent/zyy55sw40nrl801056m0o/stream-invoke";
|
|
@@ -2089,11 +1991,7 @@
|
|
|
2089
1991
|
const { voiceStatus, transcriptionText, wakeAnimating, isTranscribing } = voice;
|
|
2090
1992
|
const { isInvoking, currentTextContent, currentToolParts, executingTools, hasAnyContent, toolDisplayName } = agent;
|
|
2091
1993
|
aiChatbotX?.registerVoiceMethods({
|
|
2092
|
-
|
|
2093
|
-
stop: () => toggleVoiceMode(false),
|
|
2094
|
-
openDialog: async () => Promise.resolve(),
|
|
2095
|
-
closeDialog: async () => Promise.resolve(),
|
|
2096
|
-
toggleCollapse: async () => Promise.resolve()
|
|
1994
|
+
stopBroadcast: async () => interruptCurrentResponse()
|
|
2097
1995
|
});
|
|
2098
1996
|
vue.onBeforeUnmount(async () => {
|
|
2099
1997
|
bubble.destroy();
|
|
@@ -2232,13 +2130,26 @@
|
|
|
2232
2130
|
}
|
|
2233
2131
|
});
|
|
2234
2132
|
|
|
2235
|
-
const voiceAssistant = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-
|
|
2133
|
+
const voiceAssistant = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-fda883a9"]]);
|
|
2134
|
+
|
|
2135
|
+
var clientCommandKey = /* @__PURE__ */ ((clientCommandKey2) => {
|
|
2136
|
+
clientCommandKey2["SET_THEME"] = "SiMeAgent_setTheme";
|
|
2137
|
+
clientCommandKey2["APPEND_MESSAGE"] = "SiMeAgent_appendMessage";
|
|
2138
|
+
clientCommandKey2["WAKE"] = "SiMeAgent_wake";
|
|
2139
|
+
clientCommandKey2["TRANSITION"] = "SiMeAgent_transition";
|
|
2140
|
+
clientCommandKey2["TRANSITION_END"] = "SiMeAgent_transition_end";
|
|
2141
|
+
clientCommandKey2["START_NEW_CONVERSATION"] = "SiMeAgent_startNewConversation";
|
|
2142
|
+
clientCommandKey2["RECOGNITION"] = "SiMeAgent_recognition";
|
|
2143
|
+
return clientCommandKey2;
|
|
2144
|
+
})(clientCommandKey || {});
|
|
2236
2145
|
|
|
2237
|
-
exports.
|
|
2146
|
+
exports.AgentChatTransport = AgentChatTransport;
|
|
2147
|
+
exports.AiChat = aiChat;
|
|
2148
|
+
exports.AiChatbotProvider = _sfc_main$1;
|
|
2238
2149
|
exports.AiChatbotVoiceAssistant = voiceAssistant;
|
|
2239
|
-
exports.AiChatbotX = simeX;
|
|
2240
2150
|
exports.AiChatbotXKey = AiChatbotXKey;
|
|
2241
2151
|
exports.clientCommandKey = clientCommandKey;
|
|
2152
|
+
exports.createAgentChatTransport = createAgentChatTransport;
|
|
2242
2153
|
exports.injectStrict = injectStrict;
|
|
2243
2154
|
|
|
2244
2155
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|