@siact/sime-x-vue 0.0.10 → 0.0.12

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.
@@ -1,1005 +1,810 @@
1
1
  (function (global, factory) {
2
- typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('vue'), require('@siact/sime-bridge'), require('web-voice-kit')) :
3
- typeof define === 'function' && define.amd ? define(['exports', 'vue', '@siact/sime-bridge', 'web-voice-kit'], factory) :
4
- (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.SimeXVue = {}, global.Vue, global.simeBridge, global.webVoiceKit));
5
- })(this, (function (exports, vue, simeBridge, webVoiceKit) { 'use strict';
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
- const AiChatbotXKey = Symbol("sime-x");
8
- function injectStrict(key, defaultValue, treatDefaultAsFactory) {
9
- let result;
10
- if (defaultValue === void 0) {
11
- result = vue.inject(key);
12
- } else if (treatDefaultAsFactory === true) {
13
- result = vue.inject(key, defaultValue, true);
14
- } else {
15
- result = vue.inject(key, defaultValue, false);
16
- }
17
- if (!result) {
18
- throw new Error(`Could not resolve ${key.description}`);
19
- }
20
- return result;
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
- var clientCommandKey = /* @__PURE__ */ ((clientCommandKey2) => {
24
- clientCommandKey2["SET_THEME"] = "SiMeAgent_setTheme";
25
- clientCommandKey2["APPEND_MESSAGE"] = "SiMeAgent_appendMessage";
26
- clientCommandKey2["WAKE"] = "SiMeAgent_wake";
27
- clientCommandKey2["TRANSITION"] = "SiMeAgent_transition";
28
- clientCommandKey2["TRANSITION_END"] = "SiMeAgent_transition_end";
29
- clientCommandKey2["START_NEW_CONVERSATION"] = "SiMeAgent_startNewConversation";
30
- clientCommandKey2["RECOGNITION"] = "SiMeAgent_recognition";
31
- return clientCommandKey2;
32
- })(clientCommandKey || {});
33
-
34
- const _sfc_main$4 = /* @__PURE__ */ vue.defineComponent({
35
- __name: "sime-provider",
36
- props: {
37
- project: {},
38
- description: {},
39
- debug: { type: Boolean },
40
- chatbotUrl: {},
41
- appId: {},
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 stopBroadcastRef = vue.shallowRef(async () => {
53
- });
54
- const toggleCollapseRef = vue.shallowRef(async () => {
55
- });
56
- const openDialogRef = vue.shallowRef(async () => {
57
- });
58
- const closeDialogRef = vue.shallowRef(async () => {
59
- });
60
- vue.provide(AiChatbotXKey, {
61
- chatbotUrl: () => props.chatbotUrl,
62
- appId: () => props.appId,
63
- appToken: () => props.appToken,
64
- voiceConfig: () => props.voiceConfig || { appId: "", apiKey: "", websocketUrl: "" },
65
- startListening: () => startListeningRef.value(),
66
- stopListening: () => stopListeningRef.value(),
67
- stopBroadcast: () => stopBroadcastRef.value(),
68
- toggleCollapse: () => toggleCollapseRef.value(),
69
- openDialog: () => openDialogRef.value(),
70
- closeDialog: () => closeDialogRef.value(),
71
- registerVoiceMethods: (methods) => {
72
- startListeningRef.value = methods.start;
73
- stopListeningRef.value = methods.stop;
74
- if (methods.stopBroadcast) stopBroadcastRef.value = methods.stopBroadcast;
75
- toggleCollapseRef.value = methods.toggleCollapse;
76
- openDialogRef.value = methods.openDialog;
77
- closeDialogRef.value = methods.closeDialog;
78
- },
79
- clientCommand: () => hostBridge.value.clientCommands(),
80
- hostCommads: () => hostBridge.value.hostCommands(),
81
- registerCommand: (cmd) => {
82
- hostBridge.value.registerCommand(cmd);
83
- },
84
- unregisterCommand: (name) => {
85
- hostBridge.value.unregisterCommand(name);
86
- },
87
- async appendMessage(message) {
88
- await hostBridge.value.executeClientCommand(clientCommandKey.APPEND_MESSAGE, [message]);
89
- },
90
- async setTheme(theme) {
91
- await hostBridge.value.executeClientCommand(clientCommandKey.SET_THEME, [theme]);
92
- },
93
- async weak() {
94
- await hostBridge.value.executeClientCommand(clientCommandKey.WAKE);
95
- },
96
- async startNewConversation() {
97
- await hostBridge.value.executeClientCommand(clientCommandKey.START_NEW_CONVERSATION);
98
- },
99
- setIframeElement(iframe) {
100
- hostBridge.value.setIframe(iframe);
101
- },
102
- async recognition(message, commands) {
103
- return await hostBridge.value.executeClientCommand(clientCommandKey.RECOGNITION, [message, commands]);
104
- },
105
- async executeCommand(commandName, args = []) {
106
- 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
+ };
107
48
  }
108
49
  });
109
- return (_ctx, _cache) => {
110
- return vue.renderSlot(_ctx.$slots, "default");
111
- };
112
- }
113
- });
114
-
115
- const _hoisted_1$3 = { class: "content-container" };
116
- const _hoisted_2$3 = { class: "status-header" };
117
- const _hoisted_3$2 = { class: "status-text" };
118
- const _hoisted_4$2 = {
119
- key: 0,
120
- class: "transcription-content"
121
- };
122
- const _hoisted_5$2 = {
123
- key: 1,
124
- class: "placeholder-text"
125
- };
126
- const _sfc_main$3 = /* @__PURE__ */ vue.defineComponent({
127
- __name: "voice-status",
128
- props: {
129
- status: {},
130
- transcriptionText: {},
131
- isTranscribing: { type: Boolean }
132
- },
133
- setup(__props) {
134
- const props = __props;
135
- const currentMode = vue.computed(() => {
136
- if (props.isTranscribing) return "mode-transcribing";
137
- if (props.status === "wake") return "mode-wake";
138
- return "mode-standby";
139
- });
140
- const statusLabel = vue.computed(() => {
141
- if (props.isTranscribing) return "正在聆听您的问题...";
142
- if (props.status === "wake") return "您好,有什么可以帮助您的吗?";
143
- return "Standby";
144
- });
145
- return (_ctx, _cache) => {
146
- return vue.openBlock(), vue.createBlock(vue.Transition, { name: "voice-panel" }, {
147
- default: vue.withCtx(() => [
148
- __props.status === "wake" || __props.isTranscribing ? (vue.openBlock(), vue.createElementBlock("div", {
149
- key: 0,
150
- class: vue.normalizeClass(["voice-status-wrapper", currentMode.value])
151
- }, [
152
- _cache[0] || (_cache[0] = vue.createElementVNode("div", { class: "indicator-container" }, [
153
- vue.createElementVNode("div", { class: "ambient-glow" }),
154
- vue.createElementVNode("div", { class: "ripple-layer" }, [
155
- vue.createElementVNode("div", { class: "ripple delay-1" }),
156
- vue.createElementVNode("div", { class: "ripple delay-2" }),
157
- vue.createElementVNode("div", { class: "ripple delay-3" })
158
- ]),
159
- vue.createElementVNode("div", { class: "icon-core" }, [
160
- vue.createElementVNode("svg", {
161
- class: "mic-icon",
162
- viewBox: "0 0 24 24",
163
- fill: "none",
164
- stroke: "currentColor",
165
- "stroke-width": "2",
166
- "stroke-linecap": "round",
167
- "stroke-linejoin": "round"
168
- }, [
169
- vue.createElementVNode("rect", {
170
- x: "9",
171
- y: "2",
172
- width: "6",
173
- height: "12",
174
- rx: "3",
175
- class: "mic-capsule"
176
- }),
177
- vue.createElementVNode("path", {
178
- d: "M5 10C5 13.866 8.13401 17 12 17C15.866 17 19 13.866 19 10",
179
- class: "mic-stand"
180
- }),
181
- vue.createElementVNode("path", {
182
- d: "M12 17V21M8 21H16",
183
- class: "mic-base"
184
- })
185
- ])
186
- ])
187
- ], -1)),
188
- vue.createElementVNode("div", _hoisted_1$3, [
189
- vue.createElementVNode("div", _hoisted_2$3, [
190
- vue.createElementVNode("span", _hoisted_3$2, vue.toDisplayString(statusLabel.value), 1)
191
- ]),
192
- vue.createElementVNode("div", {
193
- class: vue.normalizeClass(["text-window", { "has-text": !!__props.transcriptionText }])
194
- }, [
195
- vue.createVNode(vue.Transition, {
196
- name: "fade-slide",
197
- mode: "out-in"
198
- }, {
199
- default: vue.withCtx(() => [
200
- __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)
201
- ]),
202
- _: 1
203
- })
204
- ], 2)
205
- ])
206
- ], 2)) : vue.createCommentVNode("", true)
207
- ]),
208
- _: 1
209
- });
210
- };
211
- }
212
- });
213
-
214
- const _export_sfc = (sfc, props) => {
215
- const target = sfc.__vccOpts || sfc;
216
- for (const [key, val] of props) {
217
- target[key] = val;
218
- }
219
- return target;
220
- };
221
-
222
- const VoiceStatus = /* @__PURE__ */ _export_sfc(_sfc_main$3, [["__scopeId", "data-v-c9fa6caf"]]);
223
-
224
- const _hoisted_1$2 = {
225
- key: 0,
226
- class: "execution-bubble"
227
- };
228
- const _hoisted_2$2 = { class: "exec-text" };
229
- const _sfc_main$2 = /* @__PURE__ */ vue.defineComponent({
230
- __name: "execution-status",
231
- props: {
232
- visible: { type: Boolean },
233
- text: {}
234
- },
235
- setup(__props) {
236
- return (_ctx, _cache) => {
237
- return vue.openBlock(), vue.createBlock(vue.Transition, { name: "exec-bubble" }, {
238
- default: vue.withCtx(() => [
239
- __props.visible ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_1$2, [
240
- vue.createElementVNode("span", _hoisted_2$2, vue.toDisplayString(__props.text || "执行中"), 1),
241
- _cache[0] || (_cache[0] = vue.createElementVNode("div", { class: "loading-dots" }, [
242
- vue.createElementVNode("span", { class: "dot" }),
243
- vue.createElementVNode("span", { class: "dot" }),
244
- vue.createElementVNode("span", { class: "dot" })
245
- ], -1))
246
- ])) : vue.createCommentVNode("", true)
247
- ]),
248
- _: 1
249
- });
250
- };
251
- }
252
- });
253
-
254
- const ExecutionStatus = /* @__PURE__ */ _export_sfc(_sfc_main$2, [["__scopeId", "data-v-8244ff0d"]]);
255
-
256
- const ensureMicrophonePermission = async () => {
257
- if (typeof navigator === "undefined" || typeof window === "undefined") {
258
- console.log("当前环境不支持麦克风访问");
259
- return false;
260
- }
261
- if (!navigator.mediaDevices?.getUserMedia || !navigator.mediaDevices?.enumerateDevices) {
262
- console.log("当前环境不支持麦克风访问");
263
- return false;
50
+ this.agentOptions = options;
264
51
  }
265
- try {
266
- const devices = await navigator.mediaDevices.enumerateDevices();
267
- const audioInputDevices = devices.filter((device) => device.kind === "audioinput");
268
- if (audioInputDevices.length === 0) {
269
- console.log("未检测到麦克风设备,请连接麦克风后重试。");
270
- return false;
271
- }
272
- if ("permissions" in navigator && navigator.permissions?.query) {
52
+ async sendMessages(options) {
53
+ if (this.agentOptions.getCommands) {
273
54
  try {
274
- const status = await navigator.permissions.query({ name: "microphone" });
275
- if (status.state === "denied") {
276
- console.log("麦克风权限被禁用,请在浏览器设置中开启。");
277
- return false;
55
+ const commands = await this.agentOptions.getCommands();
56
+ if (commands && commands.length > 0) {
57
+ options.body = {
58
+ ...options.body || {},
59
+ commands
60
+ };
278
61
  }
279
- } catch (e) {
280
- console.warn("Permission query not supported:", e);
62
+ } catch {
281
63
  }
282
64
  }
283
- let stream = null;
284
- try {
285
- stream = await navigator.mediaDevices.getUserMedia({
286
- audio: {
287
- echoCancellation: true,
288
- noiseSuppression: true,
289
- autoGainControl: true
290
- }
291
- });
292
- const audioTracks = stream.getAudioTracks();
293
- if (audioTracks.length === 0) {
294
- console.log("无法获取麦克风音频轨道。");
295
- return false;
296
- }
297
- const activeTrack = audioTracks[0];
298
- if (!activeTrack.enabled || activeTrack.readyState !== "live") {
299
- console.log("麦克风设备不可用,请检查设备连接。");
300
- return false;
301
- }
302
- return true;
303
- } finally {
304
- if (stream) {
305
- stream.getTracks().forEach((track) => track.stop());
306
- }
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];
307
72
  }
308
- } catch (error) {
309
- console.error("Microphone permission check failed", error);
310
- if (error.name === "NotFoundError" || error.name === "DevicesNotFoundError") {
311
- console.log("未检测到麦克风设备,请连接麦克风后重试。");
312
- } else if (error.name === "NotAllowedError" || error.name === "PermissionDeniedError") {
313
- console.log("麦克风权限被拒绝,请在浏览器设置中允许访问。");
314
- } else if (error.name === "NotReadableError" || error.name === "TrackStartError") {
315
- console.log("麦克风被其他应用占用或无法访问。");
316
- } else {
317
- console.log("无法访问麦克风,请检查设备连接和浏览器权限。");
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
+ });
318
87
  }
319
- return false;
320
88
  }
321
- };
89
+ return history;
90
+ }
322
91
 
323
- const _hoisted_1$1 = ["data-theme"];
324
- const _hoisted_2$1 = { class: "fab-avatar-wrapper" };
325
- const _hoisted_3$1 = ["src"];
326
- const _hoisted_4$1 = { class: "header-left" };
327
- const _hoisted_5$1 = { class: "logo-icon" };
328
- const _hoisted_6$1 = ["src"];
329
- const _hoisted_7$1 = { class: "title" };
330
- const _hoisted_8$1 = { class: "actions" };
331
- const _hoisted_9$1 = ["title"];
332
- const _hoisted_10$1 = {
92
+ const _hoisted_1$1 = {
333
93
  key: 0,
334
- class: "voice-indicator"
94
+ class: "ai-chat__welcome"
335
95
  };
336
- const _hoisted_11$1 = ["title"];
337
- const _hoisted_12$1 = ["title"];
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"];
338
110
  const _hoisted_13$1 = {
339
- width: "16",
340
- height: "16",
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",
341
133
  viewBox: "0 0 24 24",
342
134
  fill: "none"
343
135
  };
344
- const _hoisted_14 = ["d"];
345
- const _hoisted_15 = ["src"];
346
- const FAB_SAFE_GAP = 24;
347
- const _sfc_main$1 = /* @__PURE__ */ vue.defineComponent({
348
- __name: "sime-x",
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",
349
191
  props: {
350
- xLogo: {},
351
- xSize: {},
352
- xTitle: {},
353
- xTheme: {},
354
- xDialogSize: {},
355
- wakeWords: {},
356
- modelPath: {}
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: {}
357
203
  },
358
- emits: ["start-transcribing", "stop-transcribing", "wakeUp"],
359
- 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
+ };
360
221
  const props = __props;
361
222
  const emit = __emit;
362
- const aiChatbotX = injectStrict(AiChatbotXKey);
363
- const chatbotUrl = vue.ref("");
364
- const voiceStatus = vue.ref("standby");
365
- const wakeAnimating = vue.ref(false);
366
- const wakeResponses = ["在呢", "在的", "我在", "您好", "在呢,请说"];
367
- const transcriptionText = vue.ref("");
368
- const isTranscribing = vue.ref(false);
369
- const isProcessing = vue.ref(false);
370
- const visible = vue.ref(false);
371
- const isCollapsed = vue.ref(false);
372
- const fabRef = vue.ref(null);
373
- const positionReady = vue.ref(false);
374
- let detector = null;
375
- const isInitializing = vue.ref(false);
376
- const initError = vue.ref("");
377
- const getSystemTheme = () => {
378
- return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
379
- };
380
- const currentTheme = vue.ref(props.xTheme === "system" ? getSystemTheme() : props.xTheme || "light");
381
- const cycleTheme = async () => {
382
- currentTheme.value = currentTheme.value === "light" ? "dark" : "light";
383
- aiChatbotX.setTheme(currentTheme.value);
384
- };
385
- const startNewConversation = () => {
386
- aiChatbotX.startNewConversation();
387
- };
388
- const themeTooltip = vue.computed(() => currentTheme.value === "light" ? "切换到深色模式" : "切换到浅色模式");
389
- const voiceButtonTooltip = vue.computed(() => {
390
- if (isInitializing.value) return "初始化中...";
391
- if (initError.value) return `错误: ${initError.value}`;
392
- switch (voiceStatus.value) {
393
- case "standby":
394
- return "开启语音监听";
395
- case "listening":
396
- return "监听中,等待唤醒词...";
397
- case "wake":
398
- return "已唤醒!";
399
- default:
400
- 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);
401
238
  }
402
239
  });
403
- if (props.xTheme === "system") {
404
- const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
405
- const handleThemeChange = (e) => {
406
- currentTheme.value = e.matches ? "dark" : "light";
407
- };
408
- mediaQuery.addEventListener("change", handleThemeChange);
409
- vue.onBeforeUnmount(() => {
410
- mediaQuery.removeEventListener("change", handleThemeChange);
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
+ }
411
254
  });
412
- }
413
- const initVoiceDetector = () => {
414
- if (detector || isInitializing.value) return;
415
- isInitializing.value = true;
416
- initError.value = "";
417
- if (!props.modelPath) {
418
- initError.value = "未提供语音模型文件";
419
- isInitializing.value = false;
420
- return;
421
- }
422
- try {
423
- detector = new webVoiceKit.WakeWordDetectorStandalone({
424
- modelPath: props.modelPath,
425
- sampleRate: 16e3,
426
- usePartial: true,
427
- autoReset: {
428
- enabled: true,
429
- resetDelayMs: 5e3
430
- }
431
- });
432
- const wakeWords = props.wakeWords || ["你好", "您好"];
433
- detector.setWakeWords(wakeWords);
434
- detector.onWake(() => {
435
- console.log("[VoiceDetector] 检测到唤醒词");
436
- wakeAnimating.value = true;
437
- playWakeResponse();
438
- emit("wakeUp", true);
439
- aiChatbotX.weak();
440
- aiChatbotX.openDialog();
441
- setTimeout(() => {
442
- wakeAnimating.value = false;
443
- }, 1500);
444
- voiceStatus.value = "listening";
445
- });
446
- detector.onError((error) => {
447
- console.error("[VoiceDetector] 错误:", error);
448
- initError.value = error.message;
449
- voiceStatus.value = "standby";
450
- isTranscribing.value = false;
451
- if (error.message.includes("permission")) {
452
- transcriptionText.value = "需要麦克风权限";
453
- } else if (error.message.includes("model")) {
454
- transcriptionText.value = "模型加载失败";
455
- } else {
456
- transcriptionText.value = "初始化失败";
457
- }
458
- setTimeout(() => {
459
- transcriptionText.value = "";
460
- }, 3e3);
461
- });
462
- console.log("[VoiceDetector] 初始化成功");
463
- } catch (error) {
464
- console.error("[VoiceDetector] 初始化失败:", error);
465
- initError.value = error instanceof Error ? error.message : "初始化失败";
466
- voiceStatus.value = "standby";
467
- isTranscribing.value = false;
468
- } finally {
469
- isInitializing.value = false;
470
- }
471
255
  };
472
- const playWakeResponse = () => {
473
- try {
474
- if (typeof window === "undefined" || !window.speechSynthesis) {
475
- console.warn("[TTS] SpeechSynthesis API 不可用");
476
- return;
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();
477
267
  }
478
- const text = wakeResponses[Math.floor(Math.random() * wakeResponses.length)];
479
- const utterance = new SpeechSynthesisUtterance(text);
480
- utterance.lang = "zh-CN";
481
- utterance.rate = 1;
482
- utterance.pitch = 0.8;
483
- utterance.volume = 0.9;
484
- const voices = window.speechSynthesis.getVoices();
485
- const maleVoice = voices.find((v) => v.lang.startsWith("zh") && /male|男/i.test(v.name));
486
- const zhVoice = maleVoice || voices.find((v) => v.lang.startsWith("zh"));
487
- if (zhVoice) {
488
- utterance.voice = zhVoice;
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();
489
280
  }
490
- window.speechSynthesis.speak(utterance);
491
- } catch (error) {
492
- console.error("[TTS] 播报失败:", error);
493
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`;
494
288
  };
495
- const stopTranscribing = async () => {
496
- };
497
- const toggleVoiceMode = async (targetState) => {
498
- const permission = await ensureMicrophonePermission();
499
- if (!permission) return;
500
- if (isInitializing.value) return;
501
- if (!detector) {
502
- await initVoiceDetector();
503
- if (!detector) return;
289
+ const handleKeydown = (e) => {
290
+ if (e.key === "Enter" && !e.shiftKey) {
291
+ e.preventDefault();
292
+ handleSubmit();
504
293
  }
505
- const isCurrentlyListening = voiceStatus.value === "listening";
506
- const shouldStart = targetState !== void 0 ? targetState : !isCurrentlyListening;
507
- if (shouldStart === isCurrentlyListening) return;
508
- try {
509
- if (shouldStart) {
510
- console.log("[VoiceDetector] 强制/自动启动监听...");
511
- await detector.start();
512
- voiceStatus.value = "listening";
513
- transcriptionText.value = "";
514
- isTranscribing.value = false;
515
- } else {
516
- console.log("[VoiceDetector] 强制/自动停止监听...");
517
- await detector.stop();
518
- stopTranscribing();
519
- voiceStatus.value = "standby";
520
- transcriptionText.value = "";
521
- isTranscribing.value = false;
294
+ };
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";
522
303
  }
523
- } catch (error) {
524
- console.error("[VoiceDetector] 操作失败:", error);
525
- voiceStatus.value = "standby";
526
- transcriptionText.value = "操作失败";
527
- isTranscribing.value = false;
528
- setTimeout(() => {
529
- transcriptionText.value = "";
530
- }, 2e3);
531
- }
304
+ });
532
305
  };
533
- const position = vue.reactive({ x: 0, y: 0 });
534
- const containerWidth = vue.ref(props.xDialogSize?.width || 420);
535
- const containerHeight = vue.ref(props.xDialogSize?.height || 600);
536
- const isFirstOpen = vue.ref(true);
537
- const validatePosition = (x, y, width, height) => {
538
- const margin = 20;
539
- const viewportWidth = window.innerWidth;
540
- const viewportHeight = window.innerHeight;
541
- const maxX = viewportWidth - width - margin;
542
- const maxY = viewportHeight - height - margin;
543
- return {
544
- x: Math.max(margin, Math.min(x, maxX)),
545
- y: Math.max(margin, Math.min(y, maxY))
546
- };
306
+ const handleSuggestionClick = (suggestion) => {
307
+ chat.sendMessage({ text: suggestion });
547
308
  };
548
- const getOverlapArea = (rectA, rectB) => {
549
- const overlapX = Math.max(0, Math.min(rectA.right, rectB.right) - Math.max(rectA.left, rectB.left));
550
- const overlapY = Math.max(0, Math.min(rectA.bottom, rectB.bottom) - Math.max(rectA.top, rectB.top));
551
- return overlapX * overlapY;
309
+ const handleStop = () => {
310
+ chat.stop();
552
311
  };
553
- const keepDistanceFromFab = (x, y, width, height) => {
554
- if (!fabRef.value) return { x, y };
555
- const fabRect = fabRef.value.getBoundingClientRect();
556
- const safeFabRect = {
557
- left: fabRect.left - FAB_SAFE_GAP,
558
- right: fabRect.right + FAB_SAFE_GAP,
559
- top: fabRect.top - FAB_SAFE_GAP,
560
- bottom: fabRect.bottom + FAB_SAFE_GAP
561
- };
562
- const candidates = [
563
- { x, y },
564
- { x: safeFabRect.left - width - FAB_SAFE_GAP, y },
565
- { x: safeFabRect.right + FAB_SAFE_GAP, y },
566
- { x, y: safeFabRect.top - height - FAB_SAFE_GAP },
567
- { x, y: safeFabRect.bottom + FAB_SAFE_GAP }
568
- ];
569
- let best = validatePosition(candidates[0].x, candidates[0].y, width, height);
570
- let bestOverlap = getOverlapArea(
571
- { left: best.x, right: best.x + width, top: best.y, bottom: best.y + height },
572
- safeFabRect
573
- );
574
- for (let i = 1; i < candidates.length; i++) {
575
- const validated = validatePosition(candidates[i].x, candidates[i].y, width, height);
576
- const overlap = getOverlapArea(
577
- { left: validated.x, right: validated.x + width, top: validated.y, bottom: validated.y + height },
578
- safeFabRect
579
- );
580
- if (overlap < bestOverlap) {
581
- best = validated;
582
- bestOverlap = overlap;
583
- if (overlap === 0) break;
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;
584
321
  }
585
322
  }
586
- return best;
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));
326
+ vue.nextTick(() => {
327
+ chat.sendMessage({ text: userText });
328
+ });
587
329
  };
588
- const calculateInitialPosition = () => {
589
- if (!fabRef.value) return;
590
- const fabRect = fabRef.value.getBoundingClientRect();
591
- const dialogWidth = containerWidth.value;
592
- const dialogHeight = containerHeight.value;
593
- const viewportWidth = window.innerWidth;
594
- const viewportHeight = window.innerHeight;
595
- const margin = 20;
596
- const minMargin = 20;
597
- let x = 0;
598
- let y = 0;
599
- const leftX = fabRect.left - dialogWidth - margin;
600
- if (leftX >= minMargin) {
601
- x = leftX;
602
- y = fabRect.top;
603
- if (y + dialogHeight > viewportHeight - minMargin) {
604
- y = viewportHeight - dialogHeight - minMargin;
605
- }
606
- if (y < minMargin) {
607
- y = minMargin;
608
- }
609
- } else {
610
- const rightX = fabRect.right + margin;
611
- if (rightX + dialogWidth <= viewportWidth - minMargin) {
612
- x = rightX;
613
- y = fabRect.top;
614
- if (y + dialogHeight > viewportHeight - minMargin) {
615
- y = viewportHeight - dialogHeight - minMargin;
616
- }
617
- if (y < minMargin) {
618
- y = minMargin;
619
- }
620
- } else {
621
- x = (viewportWidth - dialogWidth) / 2;
622
- const aboveY = fabRect.top - dialogHeight - margin;
623
- if (aboveY >= minMargin) {
624
- y = aboveY;
625
- } else {
626
- const belowY = fabRect.bottom + margin;
627
- if (belowY + dialogHeight <= viewportHeight - minMargin) {
628
- y = belowY;
629
- } else {
630
- y = (viewportHeight - dialogHeight) / 2;
631
- }
632
- }
633
- }
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");
634
340
  }
635
- const validated = validatePosition(x, y, dialogWidth, dialogHeight);
636
- const separated = keepDistanceFromFab(validated.x, validated.y, dialogWidth, dialogHeight);
637
- position.x = separated.x;
638
- position.y = separated.y;
639
341
  };
640
- const toggleCollapse = async () => {
641
- isCollapsed.value = !isCollapsed.value;
642
- vue.nextTick(() => {
643
- const currentHeight = isCollapsed.value ? 60 : containerHeight.value;
644
- const validated = validatePosition(position.x, position.y, containerWidth.value, currentHeight);
645
- const separated = keepDistanceFromFab(validated.x, validated.y, containerWidth.value, currentHeight);
646
- position.x = separated.x;
647
- position.y = separated.y;
648
- });
342
+ const isToolPart = (part) => {
343
+ return typeof part.type === "string" && part.type.startsWith("tool-");
649
344
  };
650
- const drag = vue.reactive({
651
- isDragging: false,
652
- startX: 0,
653
- startY: 0,
654
- offsetX: 0,
655
- offsetY: 0
656
- });
657
- const startDrag = (e) => {
658
- drag.isDragging = true;
659
- drag.startX = e.clientX;
660
- drag.startY = e.clientY;
661
- drag.offsetX = position.x;
662
- drag.offsetY = position.y;
663
- document.addEventListener("mousemove", onDrag);
664
- document.addEventListener("mouseup", stopDrag);
345
+ const isToolLoading = (part) => {
346
+ return part.state === "partial-call" || part.state === "call" || part.state === "input-streaming" || part.state === "input-available";
665
347
  };
666
- const onDrag = (e) => {
667
- if (!drag.isDragging) return;
668
- const newX = drag.offsetX + (e.clientX - drag.startX);
669
- const newY = drag.offsetY + (e.clientY - drag.startY);
670
- const validated = validatePosition(newX, newY, containerWidth.value, isCollapsed.value ? 60 : containerHeight.value);
671
- position.x = validated.x;
672
- position.y = validated.y;
348
+ const isToolDone = (part) => {
349
+ return part.state === "result" || part.state === "output-available";
673
350
  };
674
- const stopDrag = () => {
675
- drag.isDragging = false;
676
- document.removeEventListener("mousemove", onDrag);
677
- document.removeEventListener("mouseup", stopDrag);
351
+ const isToolError = (part) => {
352
+ return part.state === "error" || part.state === "output-error";
678
353
  };
679
- const toggleDialog = async (state) => {
680
- if (state) {
681
- visible.value = true;
682
- positionReady.value = false;
683
- emit("wakeUp", true);
684
- await vue.nextTick();
685
- if (isFirstOpen.value) {
686
- calculateInitialPosition();
687
- isFirstOpen.value = false;
688
- } else {
689
- const validated = validatePosition(
690
- position.x,
691
- position.y,
692
- containerWidth.value,
693
- isCollapsed.value ? 60 : containerHeight.value
694
- );
695
- const separated = keepDistanceFromFab(
696
- validated.x,
697
- validated.y,
698
- containerWidth.value,
699
- isCollapsed.value ? 60 : containerHeight.value
700
- );
701
- position.x = separated.x;
702
- position.y = separated.y;
703
- }
704
- await vue.nextTick();
705
- positionReady.value = true;
706
- } else {
707
- positionReady.value = false;
708
- visible.value = false;
709
- isCollapsed.value = false;
710
- emit("wakeUp", false);
711
- }
354
+ const getToolName = (part) => {
355
+ return part.toolName || part.type?.replace("tool-", "") || "unknown";
712
356
  };
713
- const handleIframeLoad = async (event) => {
714
- aiChatbotX.setIframeElement(event.target);
715
- aiChatbotX.setTheme(currentTheme.value);
357
+ const getToolDisplayName = (name) => {
358
+ return props.toolNames?.[name] || toolDisplayNames[name] || name;
716
359
  };
717
- vue.watch(
718
- () => [aiChatbotX.chatbotUrl()],
719
- ([url]) => {
720
- console.log("[AiChatbotX] 初始化", url);
721
- if (url) {
722
- chatbotUrl.value = `${url}/app/${aiChatbotX.appId()}?token=${aiChatbotX.appToken()}`;
723
- }
724
- },
725
- { immediate: true }
726
- );
727
- vue.onBeforeUnmount(async () => {
728
- if (detector) {
729
- try {
730
- if (detector.isActive()) {
731
- await detector.stop();
732
- }
733
- detector = null;
734
- } catch (error) {
735
- console.error("[VoiceDetector] 清理失败:", error);
736
- }
737
- }
738
- });
739
- aiChatbotX?.registerVoiceMethods({
740
- start: () => toggleVoiceMode(true),
741
- stop: () => toggleVoiceMode(false),
742
- openDialog: () => toggleDialog(true),
743
- closeDialog: () => toggleDialog(false),
744
- toggleCollapse: () => toggleCollapse()
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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").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();
745
374
  });
746
- return (_ctx, _cache) => {
747
- return vue.openBlock(), vue.createElementBlock("div", {
748
- class: "sime-x",
749
- "data-theme": currentTheme.value
750
- }, [
751
- vue.createVNode(vue.Transition, { name: "fade" }, {
752
- default: vue.withCtx(() => [
753
- vue.createElementVNode("div", {
754
- ref_key: "fabRef",
755
- ref: fabRef,
756
- class: "assistant-fab",
757
- onClick: _cache[0] || (_cache[0] = ($event) => toggleDialog(!visible.value))
758
- }, [
759
- !isProcessing.value ? (vue.openBlock(), vue.createBlock(VoiceStatus, {
760
- key: 0,
761
- class: "voice-status",
762
- status: voiceStatus.value,
763
- "transcription-text": transcriptionText.value,
764
- "is-transcribing": isTranscribing.value,
765
- style: { "width": "480px" }
766
- }, null, 8, ["status", "transcription-text", "is-transcribing"])) : (vue.openBlock(), vue.createBlock(ExecutionStatus, {
767
- key: 1,
768
- class: "voice-status",
769
- visible: isProcessing.value,
770
- text: transcriptionText.value
771
- }, null, 8, ["visible", "text"])),
772
- vue.createElementVNode("div", _hoisted_2$1, [
773
- vue.createElementVNode("img", {
774
- src: __props.xLogo ? __props.xLogo : "/sime.png",
775
- alt: "assistant",
776
- style: vue.normalizeStyle({
777
- width: __props.xSize?.width + "px"
778
- })
779
- }, null, 12, _hoisted_3$1),
780
- vue.createVNode(vue.Transition, { name: "indicator-fade" }, {
781
- default: vue.withCtx(() => [
782
- voiceStatus.value === "listening" ? (vue.openBlock(), vue.createElementBlock("div", {
783
- key: 0,
784
- class: vue.normalizeClass(["listening-badge", { "wake-active": wakeAnimating.value }])
785
- }, [..._cache[3] || (_cache[3] = [
786
- vue.createElementVNode("div", { class: "listening-waves" }, [
787
- vue.createElementVNode("div", { class: "wave wave-1" }),
788
- vue.createElementVNode("div", { class: "wave wave-2" }),
789
- vue.createElementVNode("div", { class: "wave wave-3" })
790
- ], -1),
791
- vue.createElementVNode("div", { class: "listening-icon" }, [
792
- vue.createElementVNode("svg", {
793
- width: "24",
794
- height: "24",
795
- viewBox: "0 0 24 24",
796
- fill: "none"
797
- }, [
798
- vue.createElementVNode("path", {
799
- 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",
800
- fill: "currentColor"
801
- }),
802
- vue.createElementVNode("path", {
803
- d: "M17 11c0 2.76-2.24 5-5 5s-5-2.24-5-5",
804
- stroke: "currentColor",
805
- "stroke-width": "2",
806
- "stroke-linecap": "round"
807
- })
808
- ])
809
- ], -1)
810
- ])], 2)) : vue.createCommentVNode("", true)
811
- ]),
812
- _: 1
813
- })
814
- ]),
815
- vue.createElementVNode("div", {
816
- class: vue.normalizeClass(["fab-pulse", { active: voiceStatus.value === "listening" }])
817
- }, null, 2)
818
- ], 512)
819
- ]),
820
- _: 1
821
- }),
822
- vue.createVNode(vue.Transition, { name: "dialog-fade" }, {
823
- default: vue.withCtx(() => [
824
- vue.createElementVNode("div", {
825
- ref: "dialogRef",
826
- class: vue.normalizeClass(["x-dialog-container", {
827
- collapsed: isCollapsed.value,
828
- "is-hidden": !visible.value,
829
- "position-ready": positionReady.value
830
- }]),
831
- style: vue.normalizeStyle({
832
- width: containerWidth.value + "px",
833
- height: isCollapsed.value ? "auto" : containerHeight.value + "px",
834
- border: currentTheme.value === "light" && !isCollapsed.value ? "1px solid var(--border-color)" : "none",
835
- "--dialog-x": position.x + "px",
836
- "--dialog-y": position.y + "px"
837
- }),
838
- onMousedown: startDrag
839
- }, [
840
- vue.createElementVNode("div", {
841
- class: "x-dialog-header",
842
- onMousedown: vue.withModifiers(startDrag, ["stop"])
843
- }, [
844
- vue.createElementVNode("div", _hoisted_4$1, [
845
- vue.createElementVNode("div", _hoisted_5$1, [
846
- vue.createElementVNode("img", {
847
- src: __props.xLogo ? __props.xLogo : "/sime.png",
848
- alt: "assistant",
849
- class: "logo"
850
- }, null, 8, _hoisted_6$1)
851
- ]),
852
- vue.createElementVNode("span", _hoisted_7$1, vue.toDisplayString(__props.xTitle), 1)
853
- ]),
854
- vue.createElementVNode("div", _hoisted_8$1, [
855
- vue.createElementVNode("button", {
856
- class: "action-btn theme-btn",
857
- title: "开启新对话",
858
- onClick: startNewConversation
859
- }, [..._cache[4] || (_cache[4] = [
860
- vue.createElementVNode("svg", {
861
- width: "16",
862
- height: "16",
863
- viewBox: "0 0 24 24",
864
- fill: "none"
865
- }, [
866
- vue.createElementVNode("path", {
867
- d: "M12 5v14M5 12h14",
868
- stroke: "currentColor",
869
- "stroke-width": "2",
870
- "stroke-linecap": "round"
871
- })
872
- ], -1)
873
- ])]),
874
- vue.createElementVNode("button", {
875
- class: vue.normalizeClass(["action-btn theme-btn", {
876
- active: voiceStatus.value !== "standby",
877
- listening: voiceStatus.value === "listening",
878
- woke: voiceStatus.value === "wake"
879
- }]),
880
- onClick: _cache[1] || (_cache[1] = vue.withModifiers(() => toggleVoiceMode(), ["stop"])),
881
- title: voiceButtonTooltip.value
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)
391
+ ]),
392
+ vue.createElementVNode("div", _hoisted_5$1, [
393
+ vue.createElementVNode("form", {
394
+ class: "ai-chat__form",
395
+ onSubmit: vue.withModifiers(handleSubmit, ["prevent"])
396
+ }, [
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]
409
+ ]),
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"
882
424
  }, [
883
- _cache[5] || (_cache[5] = vue.createElementVNode("svg", {
884
- width: "16",
885
- height: "16",
886
- viewBox: "0 0 24 24",
887
- fill: "none"
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}`
888
462
  }, [
889
- vue.createElementVNode("path", {
890
- d: "M12 15C13.6569 15 15 13.6569 15 12V7C15 5.34315 13.6569 4 12 4C10.3431 4 9 5.34315 9 7V12C9 13.6569 10.3431 15 12 15Z",
891
- stroke: "currentColor",
892
- "stroke-width": "2",
893
- "stroke-linecap": "round",
894
- "stroke-linejoin": "round"
895
- }),
896
- vue.createElementVNode("path", {
897
- d: "M18 11C18 14.3137 15.3137 17 12 17C8.68629 17 6 14.3137 6 11",
898
- stroke: "currentColor",
899
- "stroke-width": "2",
900
- "stroke-linecap": "round",
901
- "stroke-linejoin": "round"
902
- }),
903
- vue.createElementVNode("path", {
904
- d: "M12 19V17",
905
- stroke: "currentColor",
906
- "stroke-width": "2",
907
- "stroke-linecap": "round",
908
- "stroke-linejoin": "round"
909
- }),
910
- vue.createElementVNode("path", {
911
- d: "M9 21H15",
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",
912
552
  stroke: "currentColor",
913
553
  "stroke-width": "2",
914
554
  "stroke-linecap": "round",
915
555
  "stroke-linejoin": "round"
916
- })
917
- ], -1)),
918
- voiceStatus.value !== "standby" ? (vue.openBlock(), vue.createElementBlock("span", _hoisted_10$1)) : vue.createCommentVNode("", true)
919
- ], 10, _hoisted_9$1),
920
- vue.createElementVNode("button", {
921
- class: "action-btn theme-btn",
922
- onClick: vue.withModifiers(cycleTheme, ["stop"]),
923
- title: themeTooltip.value
924
- }, [..._cache[6] || (_cache[6] = [
925
- vue.createElementVNode("svg", {
926
- width: "16",
927
- height: "16",
928
- viewBox: "0 0 24 24",
929
- 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)
930
566
  }, [
931
- vue.createElementVNode("circle", {
932
- cx: "12",
933
- cy: "12",
934
- r: "7",
935
- stroke: "currentColor",
936
- "stroke-width": "2"
937
- }),
938
- vue.createElementVNode("path", {
939
- d: "M12 5A7 7 0 1 1 12 19",
940
- fill: "currentColor"
941
- })
942
- ], -1)
943
- ])], 8, _hoisted_11$1),
944
- vue.createElementVNode("button", {
945
- class: "action-btn collapse-btn",
946
- onClick: vue.withModifiers(toggleCollapse, ["stop"]),
947
- title: isCollapsed.value ? "展开" : "折叠"
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"
948
645
  }, [
949
- (vue.openBlock(), vue.createElementBlock("svg", _hoisted_13$1, [
950
- vue.createElementVNode("path", {
951
- d: isCollapsed.value ? "M18 15L12 9L6 15" : "M6 9L12 15L18 9",
952
- stroke: "currentColor",
953
- "stroke-width": "2",
954
- "stroke-linecap": "round",
955
- "stroke-linejoin": "round"
956
- }, null, 8, _hoisted_14)
957
- ]))
958
- ], 8, _hoisted_12$1),
959
- vue.createElementVNode("button", {
960
- class: "action-btn minimize-btn",
961
- onClick: _cache[2] || (_cache[2] = vue.withModifiers(($event) => toggleDialog(false), ["stop"])),
962
- title: "最小化"
963
- }, [..._cache[7] || (_cache[7] = [
964
- vue.createElementVNode("svg", {
965
- width: "16",
966
- height: "16",
967
- viewBox: "0 0 24 24",
968
- fill: "none"
969
- }, [
970
- vue.createElementVNode("path", {
971
- d: "M5 12H19",
972
- stroke: "currentColor",
973
- "stroke-width": "2",
974
- "stroke-linecap": "round"
975
- })
976
- ], -1)
977
- ])])
978
- ])
979
- ], 32),
980
- vue.createElementVNode("div", {
981
- class: vue.normalizeClass(["x-dialog-content", { "is-hidden": isCollapsed.value }]),
982
- style: vue.normalizeStyle({ opacity: isCollapsed.value ? 0 : 1 })
983
- }, [
984
- vue.createElementVNode("iframe", {
985
- ref: "iframeRef",
986
- src: chatbotUrl.value,
987
- class: "x-iframe",
988
- allow: "microphone *; storage-access *; camera *",
989
- frameborder: "0",
990
- onLoad: handleIframeLoad
991
- }, null, 40, _hoisted_15)
992
- ], 6)
993
- ], 38)
994
- ]),
995
- _: 1
996
- })
997
- ], 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);
998
684
  };
999
685
  }
1000
686
  });
1001
687
 
1002
- const simeX = /* @__PURE__ */ _export_sfc(_sfc_main$1, [["__scopeId", "data-v-91f104d1"]]);
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
+ });
1003
808
 
1004
809
  function useTTS(getVoiceConfig) {
1005
810
  const isSpeaking = vue.ref(false);
@@ -1238,6 +1043,73 @@
1238
1043
  };
1239
1044
  }
1240
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
+
1241
1113
  function useVoiceRecognition(options) {
1242
1114
  const voiceStatus = vue.ref("standby");
1243
1115
  const isTranscribing = vue.ref(false);
@@ -1463,7 +1335,8 @@
1463
1335
  callbacks.onFinish?.(parsed);
1464
1336
  break;
1465
1337
  case "error":
1466
- callbacks.onError?.(parsed.errorText || parsed.error || "Unknown error");
1338
+ case "tool-output-error":
1339
+ callbacks.onError?.(parsed.errorText || parsed.error || "Unknown error", parsed);
1467
1340
  break;
1468
1341
  case "start":
1469
1342
  case "text-start":
@@ -1722,7 +1595,16 @@
1722
1595
  }
1723
1596
  emitUpdate();
1724
1597
  },
1725
- 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
+ }
1726
1608
  console.error("[DataStreamParser] stream error:", error);
1727
1609
  },
1728
1610
  onStepFinish(_data) {
@@ -1792,20 +1674,63 @@
1792
1674
  currentToolParts.value = [];
1793
1675
  executingTools.value = /* @__PURE__ */ new Set();
1794
1676
  };
1795
- const executeHostCommands = async (toolCallId, result) => {
1796
- if (!result || typeof result !== "object") return;
1797
- const commands = result.commands;
1798
- if (!Array.isArray(commands) || commands.length === 0) return;
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
+ };
1686
+ const buildCommandDefinitionMap = (commands) => {
1687
+ return new Map(commands.map((command) => [command.name, command]));
1688
+ };
1689
+ const toExecutableCommand = (toolName, payload, commandDefinitions) => {
1690
+ const commandDefinition = commandDefinitions.get(toolName);
1691
+ if (!commandDefinition) {
1692
+ return null;
1693
+ }
1694
+ const parameters = commandDefinition.parameters || [];
1695
+ if (Array.isArray(payload)) {
1696
+ return {
1697
+ name: toolName,
1698
+ args: payload
1699
+ };
1700
+ }
1701
+ if (!payload || typeof payload !== "object") {
1702
+ return {
1703
+ name: toolName,
1704
+ args: []
1705
+ };
1706
+ }
1707
+ const payloadRecord = payload;
1708
+ return {
1709
+ name: toolName,
1710
+ args: parameters.map((parameter) => payloadRecord[parameter.name])
1711
+ };
1712
+ };
1713
+ const resolveExecutableCommands = (toolName, payload, commandDefinitions) => {
1714
+ const extractedCommands = extractExecutableCommands(payload);
1715
+ if (extractedCommands.length > 0) {
1716
+ return extractedCommands;
1717
+ }
1718
+ const directCommand = toExecutableCommand(toolName, payload, commandDefinitions);
1719
+ return directCommand ? [directCommand] : [];
1720
+ };
1721
+ const executeHostCommands = async (toolCallId, toolName, payload, commandDefinitions) => {
1722
+ const commands = resolveExecutableCommands(toolName, payload, commandDefinitions);
1723
+ if (commands.length === 0) return false;
1799
1724
  try {
1800
1725
  executingTools.value = /* @__PURE__ */ new Set([...executingTools.value, toolCallId]);
1801
1726
  for (const cmd of commands) {
1802
- const args = Array.isArray(cmd.args) ? cmd.args : [];
1803
1727
  try {
1804
- await aiChatbotX.executeCommand(cmd.name, args);
1728
+ await aiChatbotX.executeCommand(cmd.name, cmd.args);
1805
1729
  } catch (cmdErr) {
1806
1730
  console.error(`[AgentInvoke] 执行命令 ${cmd.name} 失败:`, cmdErr);
1807
1731
  }
1808
1732
  }
1733
+ return true;
1809
1734
  } finally {
1810
1735
  const next = new Set(executingTools.value);
1811
1736
  next.delete(toolCallId);
@@ -1839,8 +1764,10 @@
1839
1764
  bubble.open();
1840
1765
  let prevTextLength = 0;
1841
1766
  const processedToolResults = /* @__PURE__ */ new Set();
1767
+ const processingToolResults = /* @__PURE__ */ new Set();
1842
1768
  abortController = new AbortController();
1843
- const commands = await aiChatbotX.hostCommads();
1769
+ const commands = await aiChatbotX.getCommads();
1770
+ const commandDefinitions = buildCommandDefinitionMap(commands);
1844
1771
  conversationHistory.value.length > 0 ? [...conversationHistory.value] : void 0;
1845
1772
  try {
1846
1773
  const response = await fetch(options.endpoint.value, {
@@ -1875,8 +1802,8 @@
1875
1802
  state: "result"
1876
1803
  };
1877
1804
  currentToolParts.value = [...currentToolParts.value, toolPart];
1878
- if (tr.toolName === "executeCommand") {
1879
- executeHostCommands(toolPart.toolCallId, tr.result);
1805
+ if (commandDefinitions.has(tr.toolName)) {
1806
+ void executeHostCommands(toolPart.toolCallId, tr.toolName, tr.result, commandDefinitions);
1880
1807
  }
1881
1808
  }
1882
1809
  }
@@ -1893,13 +1820,27 @@
1893
1820
  );
1894
1821
  currentToolParts.value = toolParts;
1895
1822
  for (const part of toolParts) {
1896
- if (part.toolName === "executeCommand" && !processedToolResults.has(part.toolCallId)) {
1823
+ if (commandDefinitions.has(part.toolName) && !processedToolResults.has(part.toolCallId) && !processingToolResults.has(part.toolCallId)) {
1897
1824
  if (part.type === "tool-call" && part.state === "call" && part.args) {
1898
- processedToolResults.add(part.toolCallId);
1899
- executeHostCommands(part.toolCallId, part.args);
1825
+ processingToolResults.add(part.toolCallId);
1826
+ void executeHostCommands(part.toolCallId, part.toolName, part.args, commandDefinitions).then(
1827
+ (executed) => {
1828
+ if (executed) {
1829
+ processedToolResults.add(part.toolCallId);
1830
+ }
1831
+ processingToolResults.delete(part.toolCallId);
1832
+ }
1833
+ );
1900
1834
  } else if (part.type === "tool-result" && part.result) {
1901
- processedToolResults.add(part.toolCallId);
1902
- executeHostCommands(part.toolCallId, part.result);
1835
+ processingToolResults.add(part.toolCallId);
1836
+ void executeHostCommands(part.toolCallId, part.toolName, part.result, commandDefinitions).then(
1837
+ (executed) => {
1838
+ if (executed) {
1839
+ processedToolResults.add(part.toolCallId);
1840
+ }
1841
+ processingToolResults.delete(part.toolCallId);
1842
+ }
1843
+ );
1903
1844
  }
1904
1845
  }
1905
1846
  }
@@ -2020,11 +1961,7 @@
2020
1961
  const aiChatbotX = injectStrict(AiChatbotXKey);
2021
1962
  const getVoiceConfig = () => {
2022
1963
  if (props.voiceConfig) return props.voiceConfig;
2023
- try {
2024
- return aiChatbotX.voiceConfig();
2025
- } catch {
2026
- return null;
2027
- }
1964
+ return null;
2028
1965
  };
2029
1966
  const endpoint = vue.computed(() => {
2030
1967
  return props.invokeUrl || "http://localhost:3001/agent/zyy55sw40nrl801056m0o/stream-invoke";
@@ -2093,12 +2030,7 @@
2093
2030
  const { voiceStatus, transcriptionText, wakeAnimating, isTranscribing } = voice;
2094
2031
  const { isInvoking, currentTextContent, currentToolParts, executingTools, hasAnyContent, toolDisplayName } = agent;
2095
2032
  aiChatbotX?.registerVoiceMethods({
2096
- start: () => toggleVoiceMode(true),
2097
- stop: () => toggleVoiceMode(false),
2098
- stopBroadcast: async () => interruptCurrentResponse(),
2099
- openDialog: async () => Promise.resolve(),
2100
- closeDialog: async () => Promise.resolve(),
2101
- toggleCollapse: async () => Promise.resolve()
2033
+ stopBroadcast: async () => interruptCurrentResponse()
2102
2034
  });
2103
2035
  vue.onBeforeUnmount(async () => {
2104
2036
  bubble.destroy();
@@ -2237,13 +2169,26 @@
2237
2169
  }
2238
2170
  });
2239
2171
 
2240
- const voiceAssistant = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-77c6ae95"]]);
2172
+ const voiceAssistant = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-fda883a9"]]);
2173
+
2174
+ var clientCommandKey = /* @__PURE__ */ ((clientCommandKey2) => {
2175
+ clientCommandKey2["SET_THEME"] = "SiMeAgent_setTheme";
2176
+ clientCommandKey2["APPEND_MESSAGE"] = "SiMeAgent_appendMessage";
2177
+ clientCommandKey2["WAKE"] = "SiMeAgent_wake";
2178
+ clientCommandKey2["TRANSITION"] = "SiMeAgent_transition";
2179
+ clientCommandKey2["TRANSITION_END"] = "SiMeAgent_transition_end";
2180
+ clientCommandKey2["START_NEW_CONVERSATION"] = "SiMeAgent_startNewConversation";
2181
+ clientCommandKey2["RECOGNITION"] = "SiMeAgent_recognition";
2182
+ return clientCommandKey2;
2183
+ })(clientCommandKey || {});
2241
2184
 
2242
- exports.AiChatbotProvider = _sfc_main$4;
2185
+ exports.AgentChatTransport = AgentChatTransport;
2186
+ exports.AiChat = aiChat;
2187
+ exports.AiChatbotProvider = _sfc_main$1;
2243
2188
  exports.AiChatbotVoiceAssistant = voiceAssistant;
2244
- exports.AiChatbotX = simeX;
2245
2189
  exports.AiChatbotXKey = AiChatbotXKey;
2246
2190
  exports.clientCommandKey = clientCommandKey;
2191
+ exports.createAgentChatTransport = createAgentChatTransport;
2247
2192
  exports.injectStrict = injectStrict;
2248
2193
 
2249
2194
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });