ibi-ai-talk 1.0.1 → 1.0.3

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/index.umd.js CHANGED
@@ -61,7 +61,7 @@ if (typeof window !== 'undefined') {
61
61
  // Indicate to webpack that this file can be concatenated
62
62
  /* harmony default export */ const setPublicPath = (null);
63
63
 
64
- ;// CONCATENATED MODULE: ./node_modules/@vue/vue-loader-v15/lib/loaders/templateLoader.js??ruleSet[1].rules[2]!./node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./src/index.vue?vue&type=template&id=71334dec
64
+ ;// CONCATENATED MODULE: ./node_modules/@vue/vue-loader-v15/lib/loaders/templateLoader.js??ruleSet[1].rules[2]!./node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./src/index.vue?vue&type=template&id=70fe37c2
65
65
  var render = function render(){var _vm=this,_c=_vm._self._c;return _c("div")
66
66
  }
67
67
  var staticRenderFns = []
@@ -237,1074 +237,1074 @@ function initOpusEncoder() {
237
237
  return false;
238
238
  }
239
239
  }
240
- ;// CONCATENATED MODULE: ./src/utils/tools.js
241
- // 全局变量
242
- let mcpTools = [];
243
- let mcpEditingIndex = null;
244
- let mcpProperties = [];
245
- let websocket = null; // 将从外部设置
240
+ ;// CONCATENATED MODULE: ./src/utils/blocking-queue.js
241
+ class BlockingQueue {
242
+ #items = [];
243
+ #waiters = []; // {resolve, reject, min, timer, onTimeout}
246
244
 
247
- /**
248
- * 设置 WebSocket 实例
249
- * @param {WebSocket} ws - WebSocket 连接实例
250
- */
251
- function setWebSocket(ws) {
252
- websocket = ws;
253
- }
245
+ /* 空队列一次性闸门 */
246
+ #emptyPromise = null;
247
+ #emptyResolve = null;
254
248
 
255
- /**
256
- * 初始化 MCP 工具
257
- */
258
- async function initMcpTools() {
259
- // 加载默认工具数据
260
- const defaultMcpTools = await fetch("/default-mcp-tools.json").then((res) =>
261
- res.json()
262
- );
249
+ /* 生产者:把数据塞进去 */
250
+ enqueue(item, ...restItems) {
251
+ if (restItems.length === 0) {
252
+ this.#items.push(item);
253
+ }
254
+ // 如果有额外参数,批量处理所有项
255
+ else {
256
+ const items = [item, ...restItems].filter(i => i);
257
+ if (items.length === 0) return;
258
+ this.#items.push(...items);
259
+ }
260
+ // 若有空队列闸门,一次性放行所有等待者
261
+ if (this.#emptyResolve) {
262
+ this.#emptyResolve();
263
+ this.#emptyResolve = null;
264
+ this.#emptyPromise = null;
265
+ }
263
266
 
264
- const savedTools = localStorage.getItem("mcpTools");
265
- if (savedTools) {
266
- try {
267
- mcpTools = JSON.parse(savedTools);
268
- } catch (e) {
269
- console.log("加载MCP工具失败,使用默认工具", "warning");
270
- mcpTools = [...defaultMcpTools];
267
+ // 唤醒所有正在等的 waiter
268
+ this.#wakeWaiters();
271
269
  }
272
- } else {
273
- mcpTools = [...defaultMcpTools];
274
- }
275
270
 
276
- // renderMcpTools();
277
- // setupMcpEventListeners();
271
+ /* 消费者:min 条或 timeout ms 先到谁 */
272
+ async dequeue(min = 1, timeout = Infinity, onTimeout = null) {
273
+ // 1. 若空,等第一次数据到达(所有调用共享同一个 promise)
274
+ if (this.#items.length === 0) {
275
+ await this.#waitForFirstItem();
276
+ }
277
+
278
+ // 立即满足
279
+ if (this.#items.length >= min) {
280
+ return this.#flush();
281
+ }
282
+
283
+ // 需要等待
284
+ return new Promise((resolve, reject) => {
285
+ let timer = null;
286
+ const waiter = { resolve, reject, min, onTimeout, timer };
287
+
288
+ // 超时逻辑
289
+ if (Number.isFinite(timeout)) {
290
+ waiter.timer = setTimeout(() => {
291
+ this.#removeWaiter(waiter);
292
+ if (onTimeout) onTimeout(this.#items.length);
293
+ resolve(this.#flush());
294
+ }, timeout);
295
+ }
296
+
297
+ this.#waiters.push(waiter);
298
+ });
299
+ }
300
+
301
+ /* 空队列闸门生成器 */
302
+ #waitForFirstItem() {
303
+ if (!this.#emptyPromise) {
304
+ this.#emptyPromise = new Promise(r => (this.#emptyResolve = r));
305
+ }
306
+ return this.#emptyPromise;
307
+ }
308
+
309
+ /* 内部:每次数据变动后,检查哪些 waiter 已满足 */
310
+ #wakeWaiters() {
311
+ for (let i = this.#waiters.length - 1; i >= 0; i--) {
312
+ const w = this.#waiters[i];
313
+ if (this.#items.length >= w.min) {
314
+ this.#removeWaiter(w);
315
+ w.resolve(this.#flush());
316
+ }
317
+ }
318
+ }
319
+
320
+ #removeWaiter(waiter) {
321
+ const idx = this.#waiters.indexOf(waiter);
322
+ if (idx !== -1) {
323
+ this.#waiters.splice(idx, 1);
324
+ if (waiter.timer) clearTimeout(waiter.timer);
325
+ }
326
+ }
327
+
328
+ #flush() {
329
+ const snapshot = [...this.#items];
330
+ this.#items.length = 0;
331
+ return snapshot;
332
+ }
333
+
334
+ /* 当前缓存长度(不含等待者) */
335
+ get length() {
336
+ return this.#items.length;
337
+ }
278
338
  }
339
+ ;// CONCATENATED MODULE: ./src/utils/stream-context.js
279
340
 
280
- /**
281
- * 渲染工具列表
282
- */
283
- function renderMcpTools() {
284
- const container = document.getElementById("mcpToolsContainer");
285
- const countSpan = document.getElementById("mcpToolsCount");
286
341
 
287
- countSpan.textContent = `${mcpTools.length} 个工具`;
342
+ // 音频流播放上下文类
343
+ class StreamingContext {
344
+ constructor(
345
+ opusDecoder,
346
+ audioContext,
347
+ sampleRate,
348
+ channels,
349
+ minAudioDuration
350
+ ) {
351
+ this.opusDecoder = opusDecoder;
352
+ this.audioContext = audioContext;
288
353
 
289
- if (mcpTools.length === 0) {
290
- container.innerHTML =
291
- '<div style="text-align: center; padding: 30px; color: #999;">暂无工具,点击下方按钮添加新工具</div>';
292
- return;
354
+ // 音频参数
355
+ this.sampleRate = sampleRate;
356
+ this.channels = channels;
357
+ this.minAudioDuration = minAudioDuration;
358
+
359
+ // 初始化队列和状态
360
+ this.queue = []; // 已解码的PCM队列。正在播放
361
+ this.activeQueue = new BlockingQueue(); // 已解码的PCM队列。准备播放
362
+ this.pendingAudioBufferQueue = []; // 待处理的缓存队列
363
+ this.audioBufferQueue = new BlockingQueue(); // 缓存队列
364
+ this.playing = false; // 是否正在播放
365
+ this.endOfStream = false; // 是否收到结束信号
366
+ this.source = null; // 当前音频源
367
+ this.totalSamples = 0; // 累积的总样本数
368
+ this.lastPlayTime = 0; // 上次播放的时间戳
293
369
  }
294
370
 
295
- container.innerHTML = mcpTools
296
- .map((tool, index) => {
297
- const paramCount = tool.inputSchema.properties
298
- ? Object.keys(tool.inputSchema.properties).length
299
- : 0;
300
- const requiredCount = tool.inputSchema.required
301
- ? tool.inputSchema.required.length
302
- : 0;
303
- const hasMockResponse =
304
- tool.mockResponse && Object.keys(tool.mockResponse).length > 0;
371
+ // 缓存音频数组
372
+ pushAudioBuffer(item) {
373
+ this.audioBufferQueue.enqueue(...item);
374
+ }
305
375
 
306
- return `
307
- <div class="mcp-tool-card">
308
- <div class="mcp-tool-header">
309
- <div class="mcp-tool-name">${tool.name}</div>
310
- <div class="mcp-tool-actions">
311
- <button onclick="window.mcpModule.editMcpTool(${index})"
312
- style="padding: 4px 10px; border: none; border-radius: 4px; background-color: #2196f3; color: white; cursor: pointer; font-size: 12px;">
313
- ✏️ 编辑
314
- </button>
315
- <button onclick="window.mcpModule.deleteMcpTool(${index})"
316
- style="padding: 4px 10px; border: none; border-radius: 4px; background-color: #f44336; color: white; cursor: pointer; font-size: 12px;">
317
- 🗑️ 删除
318
- </button>
319
- </div>
320
- </div>
321
- <div class="mcp-tool-description">${tool.description}</div>
322
- <div class="mcp-tool-info">
323
- <div class="mcp-tool-info-row">
324
- <span class="mcp-tool-info-label">参数数量:</span>
325
- <span class="mcp-tool-info-value">${paramCount} 个 ${
326
- requiredCount > 0 ? `(${requiredCount} 个必填)` : ""
327
- }</span>
328
- </div>
329
- <div class="mcp-tool-info-row">
330
- <span class="mcp-tool-info-label">模拟返回:</span>
331
- <span class="mcp-tool-info-value">${
332
- hasMockResponse
333
- ? "✅ 已配置: " + JSON.stringify(tool.mockResponse)
334
- : "⚪ 使用默认"
335
- }</span>
336
- </div>
337
- </div>
338
- </div>
339
- `;
340
- })
341
- .join("");
342
- }
376
+ // 获取需要处理缓存队列,单线程:在audioBufferQueue一直更新的状态下不会出现安全问题
377
+ async getPendingAudioBufferQueue() {
378
+ // 原子交换 + 清空
379
+ [this.pendingAudioBufferQueue, this.audioBufferQueue] = [
380
+ await this.audioBufferQueue.dequeue(),
381
+ new BlockingQueue(),
382
+ ];
383
+ }
343
384
 
344
- /**
345
- * 渲染参数列表
346
- */
347
- function renderMcpProperties() {
348
- const container = document.getElementById("mcpPropertiesContainer");
385
+ // 获取正在播放已解码的PCM队列,单线程:在activeQueue一直更新的状态下不会出现安全问题
386
+ async getQueue(minSamples) {
387
+ let TepArray = [];
388
+ const num =
389
+ minSamples - this.queue.length > 0 ? minSamples - this.queue.length : 1;
390
+ // 原子交换 + 清空
391
+ [TepArray, this.activeQueue] = [
392
+ await this.activeQueue.dequeue(num),
393
+ new BlockingQueue(),
394
+ ];
395
+ this.queue.push(...TepArray);
396
+ }
349
397
 
350
- if (mcpProperties.length === 0) {
351
- container.innerHTML =
352
- '<div style="text-align: center; padding: 20px; color: #999; font-size: 14px;">暂无参数,点击下方按钮添加参数</div>';
353
- return;
398
+ // 将Int16音频数据转换为Float32音频数据
399
+ convertInt16ToFloat32(int16Data) {
400
+ const float32Data = new Float32Array(int16Data.length);
401
+ for (let i = 0; i < int16Data.length; i++) {
402
+ // 将[-32768,32767]范围转换为[-1,1],统一使用32768.0避免不对称失真
403
+ float32Data[i] = int16Data[i] / 32768.0;
404
+ }
405
+ return float32Data;
354
406
  }
355
407
 
356
- container.innerHTML = mcpProperties
357
- .map(
358
- (prop, index) => `
359
- <div class="mcp-property-item">
360
- <div class="mcp-property-header">
361
- <span class="mcp-property-name">${prop.name}</span>
362
- <button type="button" onclick="window.mcpModule.deleteMcpProperty(${index})"
363
- style="padding: 3px 8px; border: none; border-radius: 3px; background-color: #f44336; color: white; cursor: pointer; font-size: 11px;">
364
- 删除
365
- </button>
366
- </div>
367
- <div class="mcp-property-row">
368
- <div>
369
- <label class="mcp-small-label">参数名称 *</label>
370
- <input type="text" class="mcp-small-input" value="${
371
- prop.name
372
- }"
373
- onchange="window.mcpModule.updateMcpProperty(${index}, 'name', this.value)" required>
374
- </div>
375
- <div>
376
- <label class="mcp-small-label">数据类型 *</label>
377
- <select class="mcp-small-input" onchange="window.mcpModule.updateMcpProperty(${index}, 'type', this.value)">
378
- <option value="string" ${
379
- prop.type === "string" ? "selected" : ""
380
- }>字符串</option>
381
- <option value="integer" ${
382
- prop.type === "integer" ? "selected" : ""
383
- }>整数</option>
384
- <option value="number" ${
385
- prop.type === "number" ? "selected" : ""
386
- }>数字</option>
387
- <option value="boolean" ${
388
- prop.type === "boolean" ? "selected" : ""
389
- }>布尔值</option>
390
- <option value="array" ${
391
- prop.type === "array" ? "selected" : ""
392
- }>数组</option>
393
- <option value="object" ${
394
- prop.type === "object" ? "selected" : ""
395
- }>对象</option>
396
- </select>
397
- </div>
398
- </div>
399
- ${
400
- prop.type === "integer" || prop.type === "number"
401
- ? `
402
- <div class="mcp-property-row">
403
- <div>
404
- <label class="mcp-small-label">最小值</label>
405
- <input type="number" class="mcp-small-input" value="${
406
- prop.minimum !== undefined ? prop.minimum : ""
407
- }"
408
- placeholder="可选" onchange="window.mcpModule.updateMcpProperty(${index}, 'minimum', this.value ? parseFloat(this.value) : undefined)">
409
- </div>
410
- <div>
411
- <label class="mcp-small-label">最大值</label>
412
- <input type="number" class="mcp-small-input" value="${
413
- prop.maximum !== undefined ? prop.maximum : ""
414
- }"
415
- placeholder="可选" onchange="window.mcpModule.updateMcpProperty(${index}, 'maximum', this.value ? parseFloat(this.value) : undefined)">
416
- </div>
417
- </div>
418
- `
419
- : ""
420
- }
421
- <div class="mcp-property-row-full">
422
- <label class="mcp-small-label">参数描述</label>
423
- <input type="text" class="mcp-small-input" value="${
424
- prop.description || ""
425
- }"
426
- placeholder="可选" onchange="window.mcpModule.updateMcpProperty(${index}, 'description', this.value)">
427
- </div>
428
- <label class="mcp-checkbox-label">
429
- <input type="checkbox" ${prop.required ? "checked" : ""}
430
- onchange="window.mcpModule.updateMcpProperty(${index}, 'required', this.checked)">
431
- 必填参数
432
- </label>
433
- </div>
434
- `
435
- )
436
- .join("");
437
- }
408
+ // 将Opus数据解码为PCM
409
+ async decodeOpusFrames() {
410
+ if (!this.opusDecoder) {
411
+ console.log("Opus解码器未初始化,无法解码", "error");
412
+ return;
413
+ } else {
414
+ console.log("Opus解码器启动", "info");
415
+ }
438
416
 
439
- /**
440
- * 添加参数
441
- */
442
- function addMcpProperty() {
443
- mcpProperties.push({
444
- name: `param_${mcpProperties.length + 1}`,
445
- type: "string",
446
- required: false,
447
- description: "",
448
- });
449
- renderMcpProperties();
450
- }
417
+ while (true) {
418
+ let decodedSamples = [];
419
+ for (const frame of this.pendingAudioBufferQueue) {
420
+ try {
421
+ // 使用Opus解码器解码
422
+ const frameData = this.opusDecoder.decode(frame);
423
+ if (frameData && frameData.length > 0) {
424
+ // 转换为Float32
425
+ const floatData = this.convertInt16ToFloat32(frameData);
426
+ // 使用循环替代展开运算符
427
+ for (let i = 0; i < floatData.length; i++) {
428
+ decodedSamples.push(floatData[i]);
429
+ }
430
+ }
431
+ } catch (error) {
432
+ console.log("Opus解码失败: " + error.message, "error");
433
+ }
434
+ }
451
435
 
452
- /**
453
- * 更新参数
454
- */
455
- function updateMcpProperty(index, field, value) {
456
- if (field === "name") {
457
- const isDuplicate = mcpProperties.some(
458
- (p, i) => i !== index && p.name === value
459
- );
460
- if (isDuplicate) {
461
- alert("参数名称已存在,请使用不同的名称");
462
- renderMcpProperties();
463
- return;
436
+ if (decodedSamples.length > 0) {
437
+ // 使用循环替代展开运算符
438
+ for (let i = 0; i < decodedSamples.length; i++) {
439
+ this.activeQueue.enqueue(decodedSamples[i]);
440
+ }
441
+ this.totalSamples += decodedSamples.length;
442
+ } else {
443
+ console.log("没有成功解码的样本", "warning");
444
+ }
445
+ await this.getPendingAudioBufferQueue();
464
446
  }
465
447
  }
466
448
 
467
- mcpProperties[index][field] = value;
449
+ // 开始播放音频
450
+ async startPlaying() {
451
+ let scheduledEndTime = this.audioContext.currentTime; // 跟踪已调度音频的结束时间
468
452
 
469
- if (field === "type" && value !== "integer" && value !== "number") {
470
- delete mcpProperties[index].minimum;
471
- delete mcpProperties[index].maximum;
472
- renderMcpProperties();
473
- }
474
- }
453
+ while (true) {
454
+ // 初始缓冲:等待足够的样本再开始播放
455
+ const minSamples = this.sampleRate * this.minAudioDuration * 2;
456
+ if (!this.playing && this.queue.length < minSamples) {
457
+ await this.getQueue(minSamples);
458
+ }
459
+ this.playing = true;
475
460
 
476
- /**
477
- * 删除参数
478
- */
479
- function deleteMcpProperty(index) {
480
- mcpProperties.splice(index, 1);
481
- renderMcpProperties();
482
- }
461
+ // 持续播放队列中的音频,每次播放一个小块
462
+ while (this.playing && this.queue.length > 0) {
463
+ // 每次播放120ms的音频(2个Opus包)
464
+ const playDuration = 0.12;
465
+ const targetSamples = Math.floor(this.sampleRate * playDuration);
466
+ const actualSamples = Math.min(this.queue.length, targetSamples);
483
467
 
484
- /**
485
- * 设置事件监听
486
- */
487
- function setupMcpEventListeners() {
488
- const toggleBtn = document.getElementById("toggleMcpTools");
489
- const panel = document.getElementById("mcpToolsPanel");
490
- const addBtn = document.getElementById("addMcpToolBtn");
491
- const modal = document.getElementById("mcpToolModal");
492
- const closeBtn = document.getElementById("closeMcpModalBtn");
493
- const cancelBtn = document.getElementById("cancelMcpBtn");
494
- const form = document.getElementById("mcpToolForm");
495
- const addPropertyBtn = document.getElementById("addMcpPropertyBtn");
468
+ if (actualSamples === 0) break;
496
469
 
497
- toggleBtn.addEventListener("click", () => {
498
- const isExpanded = panel.classList.contains("expanded");
499
- panel.classList.toggle("expanded");
500
- toggleBtn.textContent = isExpanded ? "展开" : "收起";
501
- });
470
+ const currentSamples = this.queue.splice(0, actualSamples);
471
+ const audioBuffer = this.audioContext.createBuffer(
472
+ this.channels,
473
+ currentSamples.length,
474
+ this.sampleRate
475
+ );
476
+ audioBuffer.copyToChannel(new Float32Array(currentSamples), 0);
502
477
 
503
- addBtn.addEventListener("click", () => openMcpModal());
504
- closeBtn.addEventListener("click", closeMcpModal);
505
- cancelBtn.addEventListener("click", closeMcpModal);
506
- addPropertyBtn.addEventListener("click", addMcpProperty);
478
+ // 创建音频源
479
+ this.source = this.audioContext.createBufferSource();
480
+ this.source.buffer = audioBuffer;
507
481
 
508
- modal.addEventListener("click", (e) => {
509
- if (e.target === modal) closeMcpModal();
510
- });
482
+ // 精确调度播放时间
483
+ const currentTime = this.audioContext.currentTime;
484
+ const startTime = Math.max(scheduledEndTime, currentTime);
511
485
 
512
- form.addEventListener("submit", handleMcpSubmit);
513
- }
486
+ // 直接连接到输出
487
+ this.source.connect(this.audioContext.destination);
514
488
 
515
- /**
516
- * 打开模态框
517
- */
518
- function openMcpModal(index = null) {
519
- const isConnected = websocket && websocket.readyState === WebSocket.OPEN;
520
- if (isConnected) {
521
- alert("WebSocket 已连接,无法编辑工具");
522
- return;
523
- }
489
+ this.source.start(startTime);
524
490
 
525
- mcpEditingIndex = index;
526
- const errorContainer = document.getElementById("mcpErrorContainer");
527
- errorContainer.innerHTML = "";
491
+ // 更新下一个音频块的调度时间
492
+ const duration = audioBuffer.duration;
493
+ scheduledEndTime = startTime + duration;
494
+ this.lastPlayTime = startTime;
528
495
 
529
- if (index !== null) {
530
- document.getElementById("mcpModalTitle").textContent = "编辑工具";
531
- const tool = mcpTools[index];
532
- document.getElementById("mcpToolName").value = tool.name;
533
- document.getElementById("mcpToolDescription").value = tool.description;
534
- document.getElementById("mcpMockResponse").value = tool.mockResponse
535
- ? JSON.stringify(tool.mockResponse, null, 2)
536
- : "";
496
+ // 如果队列中数据不足,等待新数据
497
+ if (this.queue.length < targetSamples) {
498
+ break;
499
+ }
500
+ }
537
501
 
538
- mcpProperties = [];
539
- const schema = tool.inputSchema;
540
- if (schema.properties) {
541
- Object.keys(schema.properties).forEach((key) => {
542
- const prop = schema.properties[key];
543
- mcpProperties.push({
544
- name: key,
545
- type: prop.type || "string",
546
- minimum: prop.minimum,
547
- maximum: prop.maximum,
548
- description: prop.description || "",
549
- required: schema.required && schema.required.includes(key),
550
- });
551
- });
502
+ // 等待新数据
503
+ await this.getQueue(minSamples);
552
504
  }
553
- } else {
554
- document.getElementById("mcpModalTitle").textContent = "添加工具";
555
- document.getElementById("mcpToolForm").reset();
556
- mcpProperties = [];
557
505
  }
558
-
559
- renderMcpProperties();
560
- document.getElementById("mcpToolModal").style.display = "block";
561
506
  }
562
507
 
563
- /**
564
- * 关闭模态框
565
- */
566
- function closeMcpModal() {
567
- document.getElementById("mcpToolModal").style.display = "none";
568
- mcpEditingIndex = null;
569
- document.getElementById("mcpToolForm").reset();
570
- mcpProperties = [];
571
- document.getElementById("mcpErrorContainer").innerHTML = "";
508
+ // 创建streamingContext实例的工厂函数
509
+ function createStreamingContext(
510
+ opusDecoder,
511
+ audioContext,
512
+ sampleRate,
513
+ channels,
514
+ minAudioDuration
515
+ ) {
516
+ return new StreamingContext(
517
+ opusDecoder,
518
+ audioContext,
519
+ sampleRate,
520
+ channels,
521
+ minAudioDuration
522
+ );
572
523
  }
573
524
 
574
- /**
575
- * 处理表单提交
576
- */
577
- function handleMcpSubmit(e) {
578
- e.preventDefault();
579
- const errorContainer = document.getElementById("mcpErrorContainer");
580
- errorContainer.innerHTML = "";
525
+ ;// CONCATENATED MODULE: ./src/utils/player.js
526
+ // 音频播放模块
581
527
 
582
- const name = document.getElementById("mcpToolName").value.trim();
583
- const description = document
584
- .getElementById("mcpToolDescription")
585
- .value.trim();
586
- const mockResponseText = document
587
- .getElementById("mcpMockResponse")
588
- .value.trim();
589
528
 
590
- // 检查名称重复
591
- const isDuplicate = mcpTools.some(
592
- (tool, index) => tool.name === name && index !== mcpEditingIndex
593
- );
594
529
 
595
- if (isDuplicate) {
596
- showMcpError("工具名称已存在,请使用不同的名称");
597
- return;
530
+ // 音频播放器类
531
+ class AudioPlayer {
532
+ constructor() {
533
+ // 音频参数
534
+ this.SAMPLE_RATE = 16000;
535
+ this.CHANNELS = 1;
536
+ this.FRAME_SIZE = 960;
537
+ this.MIN_AUDIO_DURATION = 0.12;
538
+
539
+ // 状态
540
+ this.audioContext = null;
541
+ this.opusDecoder = null;
542
+ this.streamingContext = null;
543
+ this.queue = new BlockingQueue();
544
+ this.isPlaying = false;
598
545
  }
599
546
 
600
- // 解析模拟返回结果
601
- let mockResponse = null;
602
- if (mockResponseText) {
603
- try {
604
- mockResponse = JSON.parse(mockResponseText);
605
- } catch (e) {
606
- showMcpError("模拟返回结果不是有效的 JSON 格式: " + e.message);
607
- return;
547
+ // 获取或创建AudioContext
548
+ getAudioContext() {
549
+ if (!this.audioContext) {
550
+ this.audioContext = new (window.AudioContext ||
551
+ window.webkitAudioContext)({
552
+ sampleRate: this.SAMPLE_RATE,
553
+ latencyHint: "interactive",
554
+ });
555
+ console.log(
556
+ "创建音频上下文,采样率: " + this.SAMPLE_RATE + "Hz",
557
+ "debug"
558
+ );
608
559
  }
560
+ return this.audioContext;
609
561
  }
610
562
 
611
- // 构建 inputSchema
612
- const inputSchema = {
613
- type: "object",
614
- properties: {},
615
- required: [],
616
- };
563
+ // 初始化Opus解码器
564
+ async initOpusDecoder() {
565
+ if (this.opusDecoder) return this.opusDecoder;
617
566
 
618
- mcpProperties.forEach((prop) => {
619
- const propSchema = { type: prop.type };
567
+ try {
568
+ if (typeof window.ModuleInstance === "undefined") {
569
+ if (typeof Module !== "undefined") {
570
+ window.ModuleInstance = Module;
571
+ console.log("使用全局Module作为ModuleInstance", "info");
572
+ } else {
573
+ throw new Error("Opus库未加载,ModuleInstance和Module对象都不存在");
574
+ }
575
+ }
620
576
 
621
- if (prop.description) {
622
- propSchema.description = prop.description;
623
- }
577
+ const mod = window.ModuleInstance;
624
578
 
625
- if (prop.type === "integer" || prop.type === "number") {
626
- if (prop.minimum !== undefined && prop.minimum !== "") {
627
- propSchema.minimum = prop.minimum;
628
- }
629
- if (prop.maximum !== undefined && prop.maximum !== "") {
630
- propSchema.maximum = prop.maximum;
631
- }
632
- }
579
+ this.opusDecoder = {
580
+ channels: this.CHANNELS,
581
+ rate: this.SAMPLE_RATE,
582
+ frameSize: this.FRAME_SIZE,
583
+ module: mod,
584
+ decoderPtr: null,
633
585
 
634
- inputSchema.properties[prop.name] = propSchema;
586
+ init: function () {
587
+ if (this.decoderPtr) return true;
635
588
 
636
- if (prop.required) {
637
- inputSchema.required.push(prop.name);
638
- }
639
- });
589
+ const decoderSize = mod._opus_decoder_get_size(this.channels);
590
+ console.log(`Opus解码器大小: ${decoderSize}字节`, "debug");
640
591
 
641
- if (inputSchema.required.length === 0) {
642
- delete inputSchema.required;
643
- }
592
+ this.decoderPtr = mod._malloc(decoderSize);
593
+ if (!this.decoderPtr) {
594
+ throw new Error("无法分配解码器内存");
595
+ }
644
596
 
645
- const tool = { name, description, inputSchema, mockResponse };
597
+ const err = mod._opus_decoder_init(
598
+ this.decoderPtr,
599
+ this.rate,
600
+ this.channels
601
+ );
646
602
 
647
- if (mcpEditingIndex !== null) {
648
- mcpTools[mcpEditingIndex] = tool;
649
- console.log(`已更新工具: ${name}`, "success");
650
- } else {
651
- mcpTools.push(tool);
652
- console.log(`已添加工具: ${name}`, "success");
653
- }
603
+ if (err < 0) {
604
+ this.destroy();
605
+ throw new Error(`Opus解码器初始化失败: ${err}`);
606
+ }
654
607
 
655
- saveMcpTools();
656
- renderMcpTools();
657
- closeMcpModal();
658
- }
608
+ console.log("Opus解码器初始化成功", "success");
609
+ return true;
610
+ },
659
611
 
660
- /**
661
- * 显示错误
662
- */
663
- function showMcpError(message) {
664
- const errorContainer = document.getElementById("mcpErrorContainer");
665
- errorContainer.innerHTML = `<div class="mcp-error">${message}</div>`;
666
- }
612
+ decode: function (opusData) {
613
+ if (!this.decoderPtr) {
614
+ if (!this.init()) {
615
+ throw new Error("解码器未初始化且无法初始化");
616
+ }
617
+ }
667
618
 
668
- /**
669
- * 编辑工具
670
- */
671
- function editMcpTool(index) {
672
- openMcpModal(index);
673
- }
619
+ try {
620
+ const mod = this.module;
674
621
 
675
- /**
676
- * 删除工具
677
- */
678
- function deleteMcpTool(index) {
679
- const isConnected = websocket && websocket.readyState === WebSocket.OPEN;
680
- if (isConnected) {
681
- alert("WebSocket 已连接,无法编辑工具");
682
- return;
683
- }
684
- if (confirm(`确定要删除工具 "${mcpTools[index].name}" 吗?`)) {
685
- const toolName = mcpTools[index].name;
686
- mcpTools.splice(index, 1);
687
- saveMcpTools();
688
- renderMcpTools();
689
- console.log(`已删除工具: ${toolName}`, "info");
690
- }
691
- }
622
+ const opusPtr = mod._malloc(opusData.length);
623
+ mod.HEAPU8.set(opusData, opusPtr);
692
624
 
693
- /**
694
- * 保存工具
695
- */
696
- function saveMcpTools() {
697
- localStorage.setItem("mcpTools", JSON.stringify(mcpTools));
698
- }
625
+ const pcmPtr = mod._malloc(this.frameSize * 2);
699
626
 
700
- /**
701
- * 获取工具列表
702
- */
703
- function getMcpTools() {
704
- return mcpTools.map((tool) => ({
705
- name: tool.name,
706
- description: tool.description,
707
- inputSchema: tool.inputSchema,
708
- }));
709
- }
627
+ const decodedSamples = mod._opus_decode(
628
+ this.decoderPtr,
629
+ opusPtr,
630
+ opusData.length,
631
+ pcmPtr,
632
+ this.frameSize,
633
+ 0
634
+ );
710
635
 
711
- /**
712
- * 执行工具调用
713
- */
714
- function executeMcpTool(toolName, toolArgs) {
715
- const tool = mcpTools.find((t) => t.name === toolName);
716
- if (!tool) {
717
- console.log(`未找到工具: ${toolName}`, "error");
718
- return {
719
- success: false,
720
- error: `未知工具: ${toolName}`,
721
- };
722
- }
636
+ if (decodedSamples < 0) {
637
+ mod._free(opusPtr);
638
+ mod._free(pcmPtr);
639
+ throw new Error(`Opus解码失败: ${decodedSamples}`);
640
+ }
723
641
 
724
- if (tool.name == "self.drink_car_list") {
725
- console.log("准备触发 gotoOrderEvent 事件"); // 增加此日志
726
- const event = new CustomEvent("gotoOrderEvent");
727
- window.dispatchEvent(event);
728
- return {
729
- success: true,
730
- message: `工具 ${toolName} 执行成功`,
731
- data: sessionStorage.getItem('cartList') || [],
732
- };
733
- } else if (tool.name == "self.drink_car_reset") {
734
- console.log("准备触发 resetOrderEvent 事件"); // 增加此日志
735
- const event = new CustomEvent("resetOrderEvent", {
736
- detail: toolArgs,
737
- });
738
- window.dispatchEvent(event);
739
- return {
740
- success: true,
741
- message: `工具 ${toolName} 执行成功`,
742
- data: sessionStorage.getItem('cartList') || [],
743
- }
744
- } else if (tool.name == "self.drink_order") {
745
- console.log("准备触发 orderEvent 事件"); // 增加此日志
746
- const event = new CustomEvent("orderEvent");
747
- window.dispatchEvent(event);
748
- }
749
- }
642
+ const decodedData = new Int16Array(decodedSamples);
643
+ for (let i = 0; i < decodedSamples; i++) {
644
+ decodedData[i] = mod.HEAP16[(pcmPtr >> 1) + i];
645
+ }
750
646
 
751
- // 暴露全局方法供 HTML 内联事件调用
752
- window.mcpModule = {
753
- updateMcpProperty,
754
- deleteMcpProperty,
755
- editMcpTool,
756
- deleteMcpTool,
757
- };
647
+ mod._free(opusPtr);
648
+ mod._free(pcmPtr);
758
649
 
759
- ;// CONCATENATED MODULE: ./src/utils/blocking-queue.js
760
- class BlockingQueue {
761
- #items = [];
762
- #waiters = []; // {resolve, reject, min, timer, onTimeout}
650
+ return decodedData;
651
+ } catch (error) {
652
+ console.log(`Opus解码错误: ${error.message}`, "error");
653
+ return new Int16Array(0);
654
+ }
655
+ },
763
656
 
764
- /* 空队列一次性闸门 */
765
- #emptyPromise = null;
766
- #emptyResolve = null;
657
+ destroy: function () {
658
+ if (this.decoderPtr) {
659
+ this.module._free(this.decoderPtr);
660
+ this.decoderPtr = null;
661
+ }
662
+ },
663
+ };
767
664
 
768
- /* 生产者:把数据塞进去 */
769
- enqueue(item, ...restItems) {
770
- if (restItems.length === 0) {
771
- this.#items.push(item);
772
- }
773
- // 如果有额外参数,批量处理所有项
774
- else {
775
- const items = [item, ...restItems].filter(i => i);
776
- if (items.length === 0) return;
777
- this.#items.push(...items);
778
- }
779
- // 若有空队列闸门,一次性放行所有等待者
780
- if (this.#emptyResolve) {
781
- this.#emptyResolve();
782
- this.#emptyResolve = null;
783
- this.#emptyPromise = null;
784
- }
665
+ if (!this.opusDecoder.init()) {
666
+ throw new Error("Opus解码器初始化失败");
667
+ }
785
668
 
786
- // 唤醒所有正在等的 waiter
787
- this.#wakeWaiters();
669
+ return this.opusDecoder;
670
+ } catch (error) {
671
+ console.log(`Opus解码器初始化失败: ${error.message}`, "error");
672
+ this.opusDecoder = null;
673
+ throw error;
788
674
  }
675
+ }
789
676
 
790
- /* 消费者:min 条或 timeout ms 先到谁 */
791
- async dequeue(min = 1, timeout = Infinity, onTimeout = null) {
792
- // 1. 若空,等第一次数据到达(所有调用共享同一个 promise)
793
- if (this.#items.length === 0) {
794
- await this.#waitForFirstItem();
795
- }
796
-
797
- // 立即满足
798
- if (this.#items.length >= min) {
799
- return this.#flush();
800
- }
801
-
802
- // 需要等待
803
- return new Promise((resolve, reject) => {
804
- let timer = null;
805
- const waiter = { resolve, reject, min, onTimeout, timer };
677
+ // 启动音频缓冲
678
+ async startAudioBuffering() {
679
+ console.log("开始音频缓冲...", "info");
806
680
 
807
- // 超时逻辑
808
- if (Number.isFinite(timeout)) {
809
- waiter.timer = setTimeout(() => {
810
- this.#removeWaiter(waiter);
811
- if (onTimeout) onTimeout(this.#items.length);
812
- resolve(this.#flush());
813
- }, timeout);
814
- }
681
+ this.initOpusDecoder().catch((error) => {
682
+ console.log(`预初始化Opus解码器失败: ${error.message}`, "warning");
683
+ });
815
684
 
816
- this.#waiters.push(waiter);
817
- });
818
- }
685
+ const timeout = 400;
686
+ while (true) {
687
+ const packets = await this.queue.dequeue(6, timeout, (count) => {
688
+ console.log(`缓冲超时,当前缓冲包数: ${count},开始播放`, "info");
689
+ });
690
+ if (packets.length) {
691
+ console.log(`已缓冲 ${packets.length} 个音频包,开始播放`, "info");
692
+ this.streamingContext.pushAudioBuffer(packets);
693
+ }
819
694
 
820
- /* 空队列闸门生成器 */
821
- #waitForFirstItem() {
822
- if (!this.#emptyPromise) {
823
- this.#emptyPromise = new Promise(r => (this.#emptyResolve = r));
695
+ while (true) {
696
+ const data = await this.queue.dequeue(99, 30);
697
+ if (data.length) {
698
+ this.streamingContext.pushAudioBuffer(data);
699
+ } else {
700
+ break;
824
701
  }
825
- return this.#emptyPromise;
702
+ }
826
703
  }
704
+ }
827
705
 
828
- /* 内部:每次数据变动后,检查哪些 waiter 已满足 */
829
- #wakeWaiters() {
830
- for (let i = this.#waiters.length - 1; i >= 0; i--) {
831
- const w = this.#waiters[i];
832
- if (this.#items.length >= w.min) {
833
- this.#removeWaiter(w);
834
- w.resolve(this.#flush());
835
- }
836
- }
837
- }
706
+ // 播放已缓冲的音频
707
+ async playBufferedAudio() {
708
+ try {
709
+ this.audioContext = this.getAudioContext();
838
710
 
839
- #removeWaiter(waiter) {
840
- const idx = this.#waiters.indexOf(waiter);
841
- if (idx !== -1) {
842
- this.#waiters.splice(idx, 1);
843
- if (waiter.timer) clearTimeout(waiter.timer);
711
+ if (!this.opusDecoder) {
712
+ console.log("初始化Opus解码器...", "info");
713
+ try {
714
+ this.opusDecoder = await this.initOpusDecoder();
715
+ if (!this.opusDecoder) {
716
+ throw new Error("解码器初始化失败");
717
+ }
718
+ console.log("Opus解码器初始化成功", "success");
719
+ } catch (error) {
720
+ console.log("Opus解码器初始化失败: " + error.message, "error");
721
+ this.isPlaying = false;
722
+ return;
844
723
  }
845
- }
724
+ }
846
725
 
847
- #flush() {
848
- const snapshot = [...this.#items];
849
- this.#items.length = 0;
850
- return snapshot;
726
+ if (!this.streamingContext) {
727
+ this.streamingContext = createStreamingContext(
728
+ this.opusDecoder,
729
+ this.audioContext,
730
+ this.SAMPLE_RATE,
731
+ this.CHANNELS,
732
+ this.MIN_AUDIO_DURATION
733
+ );
734
+ }
735
+
736
+ this.streamingContext.decodeOpusFrames();
737
+ this.streamingContext.startPlaying();
738
+ } catch (error) {
739
+ console.log(`播放已缓冲的音频出错: ${error.message}`, "error");
740
+ this.isPlaying = false;
741
+ this.streamingContext = null;
851
742
  }
743
+ }
852
744
 
853
- /* 当前缓存长度(不含等待者) */
854
- get length() {
855
- return this.#items.length;
745
+ // 添加音频数据到队列
746
+ enqueueAudioData(opusData) {
747
+ if (opusData.length > 0) {
748
+ this.queue.enqueue(opusData);
749
+ } else {
750
+ console.log("收到空音频数据帧,可能是结束标志", "warning");
751
+ if (this.isPlaying && this.streamingContext) {
752
+ this.streamingContext.endOfStream = true;
753
+ }
856
754
  }
857
- }
858
- ;// CONCATENATED MODULE: ./src/utils/stream-context.js
755
+ }
859
756
 
757
+ // 预加载解码器
758
+ async preload() {
759
+ console.log("预加载Opus解码器...", "info");
760
+ try {
761
+ await this.initOpusDecoder();
762
+ console.log("Opus解码器预加载成功", "success");
763
+ } catch (error) {
764
+ console.log(
765
+ `Opus解码器预加载失败: ${error.message},将在需要时重试`,
766
+ "warning"
767
+ );
768
+ }
769
+ }
860
770
 
861
- // 音频流播放上下文类
862
- class StreamingContext {
863
- constructor(
864
- opusDecoder,
865
- audioContext,
866
- sampleRate,
867
- channels,
868
- minAudioDuration
869
- ) {
870
- this.opusDecoder = opusDecoder;
871
- this.audioContext = audioContext;
771
+ // 启动播放系统
772
+ async start() {
773
+ await this.preload();
774
+ this.playBufferedAudio();
775
+ this.startAudioBuffering();
776
+ }
777
+ }
872
778
 
873
- // 音频参数
874
- this.sampleRate = sampleRate;
875
- this.channels = channels;
876
- this.minAudioDuration = minAudioDuration;
779
+ // 创建单例
780
+ let audioPlayerInstance = null;
877
781
 
878
- // 初始化队列和状态
879
- this.queue = []; // 已解码的PCM队列。正在播放
880
- this.activeQueue = new BlockingQueue(); // 已解码的PCM队列。准备播放
881
- this.pendingAudioBufferQueue = []; // 待处理的缓存队列
882
- this.audioBufferQueue = new BlockingQueue(); // 缓存队列
883
- this.playing = false; // 是否正在播放
884
- this.endOfStream = false; // 是否收到结束信号
885
- this.source = null; // 当前音频源
886
- this.totalSamples = 0; // 累积的总样本数
887
- this.lastPlayTime = 0; // 上次播放的时间戳
782
+ function getAudioPlayer() {
783
+ if (!audioPlayerInstance) {
784
+ audioPlayerInstance = new AudioPlayer();
888
785
  }
786
+ return audioPlayerInstance;
787
+ }
889
788
 
890
- // 缓存音频数组
891
- pushAudioBuffer(item) {
892
- this.audioBufferQueue.enqueue(...item);
893
- }
894
-
895
- // 获取需要处理缓存队列,单线程:在audioBufferQueue一直更新的状态下不会出现安全问题
896
- async getPendingAudioBufferQueue() {
897
- // 原子交换 + 清空
898
- [this.pendingAudioBufferQueue, this.audioBufferQueue] = [
899
- await this.audioBufferQueue.dequeue(),
900
- new BlockingQueue(),
901
- ];
902
- }
903
-
904
- // 获取正在播放已解码的PCM队列,单线程:在activeQueue一直更新的状态下不会出现安全问题
905
- async getQueue(minSamples) {
906
- let TepArray = [];
907
- const num =
908
- minSamples - this.queue.length > 0 ? minSamples - this.queue.length : 1;
909
- // 原子交换 + 清空
910
- [TepArray, this.activeQueue] = [
911
- await this.activeQueue.dequeue(num),
912
- new BlockingQueue(),
913
- ];
914
- this.queue.push(...TepArray);
915
- }
916
-
917
- // 将Int16音频数据转换为Float32音频数据
918
- convertInt16ToFloat32(int16Data) {
919
- const float32Data = new Float32Array(int16Data.length);
920
- for (let i = 0; i < int16Data.length; i++) {
921
- // 将[-32768,32767]范围转换为[-1,1],统一使用32768.0避免不对称失真
922
- float32Data[i] = int16Data[i] / 32768.0;
923
- }
924
- return float32Data;
925
- }
789
+ ;// CONCATENATED MODULE: ./src/utils/tools.js
790
+ // 全局变量
791
+ let mcpTools = [];
792
+ let mcpEditingIndex = null;
793
+ let mcpProperties = [];
794
+ let websocket = null; // 将从外部设置
926
795
 
927
- // 将Opus数据解码为PCM
928
- async decodeOpusFrames() {
929
- if (!this.opusDecoder) {
930
- console.log("Opus解码器未初始化,无法解码", "error");
931
- return;
932
- } else {
933
- console.log("Opus解码器启动", "info");
934
- }
796
+ /**
797
+ * 设置 WebSocket 实例
798
+ * @param {WebSocket} ws - WebSocket 连接实例
799
+ */
800
+ function setWebSocket(ws) {
801
+ websocket = ws;
802
+ }
935
803
 
936
- while (true) {
937
- let decodedSamples = [];
938
- for (const frame of this.pendingAudioBufferQueue) {
939
- try {
940
- // 使用Opus解码器解码
941
- const frameData = this.opusDecoder.decode(frame);
942
- if (frameData && frameData.length > 0) {
943
- // 转换为Float32
944
- const floatData = this.convertInt16ToFloat32(frameData);
945
- // 使用循环替代展开运算符
946
- for (let i = 0; i < floatData.length; i++) {
947
- decodedSamples.push(floatData[i]);
948
- }
949
- }
950
- } catch (error) {
951
- console.log("Opus解码失败: " + error.message, "error");
952
- }
953
- }
804
+ /**
805
+ * 初始化 MCP 工具
806
+ */
807
+ async function initMcpTools() {
808
+ // 加载默认工具数据
809
+ const defaultMcpTools = await fetch("/default-mcp-tools.json").then((res) =>
810
+ res.json()
811
+ );
954
812
 
955
- if (decodedSamples.length > 0) {
956
- // 使用循环替代展开运算符
957
- for (let i = 0; i < decodedSamples.length; i++) {
958
- this.activeQueue.enqueue(decodedSamples[i]);
959
- }
960
- this.totalSamples += decodedSamples.length;
961
- } else {
962
- console.log("没有成功解码的样本", "warning");
963
- }
964
- await this.getPendingAudioBufferQueue();
813
+ const savedTools = localStorage.getItem("mcpTools");
814
+ if (savedTools) {
815
+ try {
816
+ mcpTools = JSON.parse(savedTools);
817
+ } catch (e) {
818
+ console.log("加载MCP工具失败,使用默认工具", "warning");
819
+ mcpTools = [...defaultMcpTools];
965
820
  }
821
+ } else {
822
+ mcpTools = [...defaultMcpTools];
966
823
  }
967
824
 
968
- // 开始播放音频
969
- async startPlaying() {
970
- let scheduledEndTime = this.audioContext.currentTime; // 跟踪已调度音频的结束时间
971
-
972
- while (true) {
973
- // 初始缓冲:等待足够的样本再开始播放
974
- const minSamples = this.sampleRate * this.minAudioDuration * 2;
975
- if (!this.playing && this.queue.length < minSamples) {
976
- await this.getQueue(minSamples);
977
- }
978
- this.playing = true;
979
-
980
- // 持续播放队列中的音频,每次播放一个小块
981
- while (this.playing && this.queue.length > 0) {
982
- // 每次播放120ms的音频(2个Opus包)
983
- const playDuration = 0.12;
984
- const targetSamples = Math.floor(this.sampleRate * playDuration);
985
- const actualSamples = Math.min(this.queue.length, targetSamples);
986
-
987
- if (actualSamples === 0) break;
988
-
989
- const currentSamples = this.queue.splice(0, actualSamples);
990
- const audioBuffer = this.audioContext.createBuffer(
991
- this.channels,
992
- currentSamples.length,
993
- this.sampleRate
994
- );
995
- audioBuffer.copyToChannel(new Float32Array(currentSamples), 0);
825
+ // renderMcpTools();
826
+ // setupMcpEventListeners();
827
+ }
996
828
 
997
- // 创建音频源
998
- this.source = this.audioContext.createBufferSource();
999
- this.source.buffer = audioBuffer;
829
+ /**
830
+ * 渲染工具列表
831
+ */
832
+ function renderMcpTools() {
833
+ const container = document.getElementById("mcpToolsContainer");
834
+ const countSpan = document.getElementById("mcpToolsCount");
1000
835
 
1001
- // 精确调度播放时间
1002
- const currentTime = this.audioContext.currentTime;
1003
- const startTime = Math.max(scheduledEndTime, currentTime);
836
+ countSpan.textContent = `${mcpTools.length} 个工具`;
1004
837
 
1005
- // 直接连接到输出
1006
- this.source.connect(this.audioContext.destination);
838
+ if (mcpTools.length === 0) {
839
+ container.innerHTML =
840
+ '<div style="text-align: center; padding: 30px; color: #999;">暂无工具,点击下方按钮添加新工具</div>';
841
+ return;
842
+ }
1007
843
 
1008
- this.source.start(startTime);
844
+ container.innerHTML = mcpTools
845
+ .map((tool, index) => {
846
+ const paramCount = tool.inputSchema.properties
847
+ ? Object.keys(tool.inputSchema.properties).length
848
+ : 0;
849
+ const requiredCount = tool.inputSchema.required
850
+ ? tool.inputSchema.required.length
851
+ : 0;
852
+ const hasMockResponse =
853
+ tool.mockResponse && Object.keys(tool.mockResponse).length > 0;
1009
854
 
1010
- // 更新下一个音频块的调度时间
1011
- const duration = audioBuffer.duration;
1012
- scheduledEndTime = startTime + duration;
1013
- this.lastPlayTime = startTime;
855
+ return `
856
+ <div class="mcp-tool-card">
857
+ <div class="mcp-tool-header">
858
+ <div class="mcp-tool-name">${tool.name}</div>
859
+ <div class="mcp-tool-actions">
860
+ <button onclick="window.mcpModule.editMcpTool(${index})"
861
+ style="padding: 4px 10px; border: none; border-radius: 4px; background-color: #2196f3; color: white; cursor: pointer; font-size: 12px;">
862
+ ✏️ 编辑
863
+ </button>
864
+ <button onclick="window.mcpModule.deleteMcpTool(${index})"
865
+ style="padding: 4px 10px; border: none; border-radius: 4px; background-color: #f44336; color: white; cursor: pointer; font-size: 12px;">
866
+ 🗑️ 删除
867
+ </button>
868
+ </div>
869
+ </div>
870
+ <div class="mcp-tool-description">${tool.description}</div>
871
+ <div class="mcp-tool-info">
872
+ <div class="mcp-tool-info-row">
873
+ <span class="mcp-tool-info-label">参数数量:</span>
874
+ <span class="mcp-tool-info-value">${paramCount} 个 ${
875
+ requiredCount > 0 ? `(${requiredCount} 个必填)` : ""
876
+ }</span>
877
+ </div>
878
+ <div class="mcp-tool-info-row">
879
+ <span class="mcp-tool-info-label">模拟返回:</span>
880
+ <span class="mcp-tool-info-value">${
881
+ hasMockResponse
882
+ ? "✅ 已配置: " + JSON.stringify(tool.mockResponse)
883
+ : "⚪ 使用默认"
884
+ }</span>
885
+ </div>
886
+ </div>
887
+ </div>
888
+ `;
889
+ })
890
+ .join("");
891
+ }
1014
892
 
1015
- // 如果队列中数据不足,等待新数据
1016
- if (this.queue.length < targetSamples) {
1017
- break;
1018
- }
1019
- }
893
+ /**
894
+ * 渲染参数列表
895
+ */
896
+ function renderMcpProperties() {
897
+ const container = document.getElementById("mcpPropertiesContainer");
1020
898
 
1021
- // 等待新数据
1022
- await this.getQueue(minSamples);
1023
- }
899
+ if (mcpProperties.length === 0) {
900
+ container.innerHTML =
901
+ '<div style="text-align: center; padding: 20px; color: #999; font-size: 14px;">暂无参数,点击下方按钮添加参数</div>';
902
+ return;
1024
903
  }
904
+
905
+ container.innerHTML = mcpProperties
906
+ .map(
907
+ (prop, index) => `
908
+ <div class="mcp-property-item">
909
+ <div class="mcp-property-header">
910
+ <span class="mcp-property-name">${prop.name}</span>
911
+ <button type="button" onclick="window.mcpModule.deleteMcpProperty(${index})"
912
+ style="padding: 3px 8px; border: none; border-radius: 3px; background-color: #f44336; color: white; cursor: pointer; font-size: 11px;">
913
+ 删除
914
+ </button>
915
+ </div>
916
+ <div class="mcp-property-row">
917
+ <div>
918
+ <label class="mcp-small-label">参数名称 *</label>
919
+ <input type="text" class="mcp-small-input" value="${
920
+ prop.name
921
+ }"
922
+ onchange="window.mcpModule.updateMcpProperty(${index}, 'name', this.value)" required>
923
+ </div>
924
+ <div>
925
+ <label class="mcp-small-label">数据类型 *</label>
926
+ <select class="mcp-small-input" onchange="window.mcpModule.updateMcpProperty(${index}, 'type', this.value)">
927
+ <option value="string" ${
928
+ prop.type === "string" ? "selected" : ""
929
+ }>字符串</option>
930
+ <option value="integer" ${
931
+ prop.type === "integer" ? "selected" : ""
932
+ }>整数</option>
933
+ <option value="number" ${
934
+ prop.type === "number" ? "selected" : ""
935
+ }>数字</option>
936
+ <option value="boolean" ${
937
+ prop.type === "boolean" ? "selected" : ""
938
+ }>布尔值</option>
939
+ <option value="array" ${
940
+ prop.type === "array" ? "selected" : ""
941
+ }>数组</option>
942
+ <option value="object" ${
943
+ prop.type === "object" ? "selected" : ""
944
+ }>对象</option>
945
+ </select>
946
+ </div>
947
+ </div>
948
+ ${
949
+ prop.type === "integer" || prop.type === "number"
950
+ ? `
951
+ <div class="mcp-property-row">
952
+ <div>
953
+ <label class="mcp-small-label">最小值</label>
954
+ <input type="number" class="mcp-small-input" value="${
955
+ prop.minimum !== undefined ? prop.minimum : ""
956
+ }"
957
+ placeholder="可选" onchange="window.mcpModule.updateMcpProperty(${index}, 'minimum', this.value ? parseFloat(this.value) : undefined)">
958
+ </div>
959
+ <div>
960
+ <label class="mcp-small-label">最大值</label>
961
+ <input type="number" class="mcp-small-input" value="${
962
+ prop.maximum !== undefined ? prop.maximum : ""
963
+ }"
964
+ placeholder="可选" onchange="window.mcpModule.updateMcpProperty(${index}, 'maximum', this.value ? parseFloat(this.value) : undefined)">
965
+ </div>
966
+ </div>
967
+ `
968
+ : ""
969
+ }
970
+ <div class="mcp-property-row-full">
971
+ <label class="mcp-small-label">参数描述</label>
972
+ <input type="text" class="mcp-small-input" value="${
973
+ prop.description || ""
974
+ }"
975
+ placeholder="可选" onchange="window.mcpModule.updateMcpProperty(${index}, 'description', this.value)">
976
+ </div>
977
+ <label class="mcp-checkbox-label">
978
+ <input type="checkbox" ${prop.required ? "checked" : ""}
979
+ onchange="window.mcpModule.updateMcpProperty(${index}, 'required', this.checked)">
980
+ 必填参数
981
+ </label>
982
+ </div>
983
+ `
984
+ )
985
+ .join("");
1025
986
  }
1026
987
 
1027
- // 创建streamingContext实例的工厂函数
1028
- function createStreamingContext(
1029
- opusDecoder,
1030
- audioContext,
1031
- sampleRate,
1032
- channels,
1033
- minAudioDuration
1034
- ) {
1035
- return new StreamingContext(
1036
- opusDecoder,
1037
- audioContext,
1038
- sampleRate,
1039
- channels,
1040
- minAudioDuration
1041
- );
988
+ /**
989
+ * 添加参数
990
+ */
991
+ function addMcpProperty() {
992
+ mcpProperties.push({
993
+ name: `param_${mcpProperties.length + 1}`,
994
+ type: "string",
995
+ required: false,
996
+ description: "",
997
+ });
998
+ renderMcpProperties();
1042
999
  }
1043
1000
 
1044
- ;// CONCATENATED MODULE: ./src/utils/player.js
1045
- // 音频播放模块
1046
-
1047
-
1048
-
1049
- // 音频播放器类
1050
- class AudioPlayer {
1051
- constructor() {
1052
- // 音频参数
1053
- this.SAMPLE_RATE = 16000;
1054
- this.CHANNELS = 1;
1055
- this.FRAME_SIZE = 960;
1056
- this.MIN_AUDIO_DURATION = 0.12;
1057
-
1058
- // 状态
1059
- this.audioContext = null;
1060
- this.opusDecoder = null;
1061
- this.streamingContext = null;
1062
- this.queue = new BlockingQueue();
1063
- this.isPlaying = false;
1064
- }
1065
-
1066
- // 获取或创建AudioContext
1067
- getAudioContext() {
1068
- if (!this.audioContext) {
1069
- this.audioContext = new (window.AudioContext ||
1070
- window.webkitAudioContext)({
1071
- sampleRate: this.SAMPLE_RATE,
1072
- latencyHint: "interactive",
1073
- });
1074
- console.log(
1075
- "创建音频上下文,采样率: " + this.SAMPLE_RATE + "Hz",
1076
- "debug"
1077
- );
1001
+ /**
1002
+ * 更新参数
1003
+ */
1004
+ function updateMcpProperty(index, field, value) {
1005
+ if (field === "name") {
1006
+ const isDuplicate = mcpProperties.some(
1007
+ (p, i) => i !== index && p.name === value
1008
+ );
1009
+ if (isDuplicate) {
1010
+ alert("参数名称已存在,请使用不同的名称");
1011
+ renderMcpProperties();
1012
+ return;
1078
1013
  }
1079
- return this.audioContext;
1080
1014
  }
1081
1015
 
1082
- // 初始化Opus解码器
1083
- async initOpusDecoder() {
1084
- if (this.opusDecoder) return this.opusDecoder;
1085
-
1086
- try {
1087
- if (typeof window.ModuleInstance === "undefined") {
1088
- if (typeof Module !== "undefined") {
1089
- window.ModuleInstance = Module;
1090
- console.log("使用全局Module作为ModuleInstance", "info");
1091
- } else {
1092
- throw new Error("Opus库未加载,ModuleInstance和Module对象都不存在");
1093
- }
1094
- }
1095
-
1096
- const mod = window.ModuleInstance;
1097
-
1098
- this.opusDecoder = {
1099
- channels: this.CHANNELS,
1100
- rate: this.SAMPLE_RATE,
1101
- frameSize: this.FRAME_SIZE,
1102
- module: mod,
1103
- decoderPtr: null,
1016
+ mcpProperties[index][field] = value;
1104
1017
 
1105
- init: function () {
1106
- if (this.decoderPtr) return true;
1018
+ if (field === "type" && value !== "integer" && value !== "number") {
1019
+ delete mcpProperties[index].minimum;
1020
+ delete mcpProperties[index].maximum;
1021
+ renderMcpProperties();
1022
+ }
1023
+ }
1107
1024
 
1108
- const decoderSize = mod._opus_decoder_get_size(this.channels);
1109
- console.log(`Opus解码器大小: ${decoderSize}字节`, "debug");
1025
+ /**
1026
+ * 删除参数
1027
+ */
1028
+ function deleteMcpProperty(index) {
1029
+ mcpProperties.splice(index, 1);
1030
+ renderMcpProperties();
1031
+ }
1110
1032
 
1111
- this.decoderPtr = mod._malloc(decoderSize);
1112
- if (!this.decoderPtr) {
1113
- throw new Error("无法分配解码器内存");
1114
- }
1033
+ /**
1034
+ * 设置事件监听
1035
+ */
1036
+ function setupMcpEventListeners() {
1037
+ const toggleBtn = document.getElementById("toggleMcpTools");
1038
+ const panel = document.getElementById("mcpToolsPanel");
1039
+ const addBtn = document.getElementById("addMcpToolBtn");
1040
+ const modal = document.getElementById("mcpToolModal");
1041
+ const closeBtn = document.getElementById("closeMcpModalBtn");
1042
+ const cancelBtn = document.getElementById("cancelMcpBtn");
1043
+ const form = document.getElementById("mcpToolForm");
1044
+ const addPropertyBtn = document.getElementById("addMcpPropertyBtn");
1115
1045
 
1116
- const err = mod._opus_decoder_init(
1117
- this.decoderPtr,
1118
- this.rate,
1119
- this.channels
1120
- );
1046
+ toggleBtn.addEventListener("click", () => {
1047
+ const isExpanded = panel.classList.contains("expanded");
1048
+ panel.classList.toggle("expanded");
1049
+ toggleBtn.textContent = isExpanded ? "展开" : "收起";
1050
+ });
1121
1051
 
1122
- if (err < 0) {
1123
- this.destroy();
1124
- throw new Error(`Opus解码器初始化失败: ${err}`);
1125
- }
1052
+ addBtn.addEventListener("click", () => openMcpModal());
1053
+ closeBtn.addEventListener("click", closeMcpModal);
1054
+ cancelBtn.addEventListener("click", closeMcpModal);
1055
+ addPropertyBtn.addEventListener("click", addMcpProperty);
1126
1056
 
1127
- console.log("Opus解码器初始化成功", "success");
1128
- return true;
1129
- },
1057
+ modal.addEventListener("click", (e) => {
1058
+ if (e.target === modal) closeMcpModal();
1059
+ });
1130
1060
 
1131
- decode: function (opusData) {
1132
- if (!this.decoderPtr) {
1133
- if (!this.init()) {
1134
- throw new Error("解码器未初始化且无法初始化");
1135
- }
1136
- }
1061
+ form.addEventListener("submit", handleMcpSubmit);
1062
+ }
1137
1063
 
1138
- try {
1139
- const mod = this.module;
1064
+ /**
1065
+ * 打开模态框
1066
+ */
1067
+ function openMcpModal(index = null) {
1068
+ const isConnected = websocket && websocket.readyState === WebSocket.OPEN;
1069
+ if (isConnected) {
1070
+ alert("WebSocket 已连接,无法编辑工具");
1071
+ return;
1072
+ }
1140
1073
 
1141
- const opusPtr = mod._malloc(opusData.length);
1142
- mod.HEAPU8.set(opusData, opusPtr);
1074
+ mcpEditingIndex = index;
1075
+ const errorContainer = document.getElementById("mcpErrorContainer");
1076
+ errorContainer.innerHTML = "";
1143
1077
 
1144
- const pcmPtr = mod._malloc(this.frameSize * 2);
1078
+ if (index !== null) {
1079
+ document.getElementById("mcpModalTitle").textContent = "编辑工具";
1080
+ const tool = mcpTools[index];
1081
+ document.getElementById("mcpToolName").value = tool.name;
1082
+ document.getElementById("mcpToolDescription").value = tool.description;
1083
+ document.getElementById("mcpMockResponse").value = tool.mockResponse
1084
+ ? JSON.stringify(tool.mockResponse, null, 2)
1085
+ : "";
1145
1086
 
1146
- const decodedSamples = mod._opus_decode(
1147
- this.decoderPtr,
1148
- opusPtr,
1149
- opusData.length,
1150
- pcmPtr,
1151
- this.frameSize,
1152
- 0
1153
- );
1087
+ mcpProperties = [];
1088
+ const schema = tool.inputSchema;
1089
+ if (schema.properties) {
1090
+ Object.keys(schema.properties).forEach((key) => {
1091
+ const prop = schema.properties[key];
1092
+ mcpProperties.push({
1093
+ name: key,
1094
+ type: prop.type || "string",
1095
+ minimum: prop.minimum,
1096
+ maximum: prop.maximum,
1097
+ description: prop.description || "",
1098
+ required: schema.required && schema.required.includes(key),
1099
+ });
1100
+ });
1101
+ }
1102
+ } else {
1103
+ document.getElementById("mcpModalTitle").textContent = "添加工具";
1104
+ document.getElementById("mcpToolForm").reset();
1105
+ mcpProperties = [];
1106
+ }
1154
1107
 
1155
- if (decodedSamples < 0) {
1156
- mod._free(opusPtr);
1157
- mod._free(pcmPtr);
1158
- throw new Error(`Opus解码失败: ${decodedSamples}`);
1159
- }
1108
+ renderMcpProperties();
1109
+ document.getElementById("mcpToolModal").style.display = "block";
1110
+ }
1160
1111
 
1161
- const decodedData = new Int16Array(decodedSamples);
1162
- for (let i = 0; i < decodedSamples; i++) {
1163
- decodedData[i] = mod.HEAP16[(pcmPtr >> 1) + i];
1164
- }
1112
+ /**
1113
+ * 关闭模态框
1114
+ */
1115
+ function closeMcpModal() {
1116
+ document.getElementById("mcpToolModal").style.display = "none";
1117
+ mcpEditingIndex = null;
1118
+ document.getElementById("mcpToolForm").reset();
1119
+ mcpProperties = [];
1120
+ document.getElementById("mcpErrorContainer").innerHTML = "";
1121
+ }
1165
1122
 
1166
- mod._free(opusPtr);
1167
- mod._free(pcmPtr);
1123
+ /**
1124
+ * 处理表单提交
1125
+ */
1126
+ function handleMcpSubmit(e) {
1127
+ e.preventDefault();
1128
+ const errorContainer = document.getElementById("mcpErrorContainer");
1129
+ errorContainer.innerHTML = "";
1168
1130
 
1169
- return decodedData;
1170
- } catch (error) {
1171
- console.log(`Opus解码错误: ${error.message}`, "error");
1172
- return new Int16Array(0);
1173
- }
1174
- },
1131
+ const name = document.getElementById("mcpToolName").value.trim();
1132
+ const description = document
1133
+ .getElementById("mcpToolDescription")
1134
+ .value.trim();
1135
+ const mockResponseText = document
1136
+ .getElementById("mcpMockResponse")
1137
+ .value.trim();
1175
1138
 
1176
- destroy: function () {
1177
- if (this.decoderPtr) {
1178
- this.module._free(this.decoderPtr);
1179
- this.decoderPtr = null;
1180
- }
1181
- },
1182
- };
1139
+ // 检查名称重复
1140
+ const isDuplicate = mcpTools.some(
1141
+ (tool, index) => tool.name === name && index !== mcpEditingIndex
1142
+ );
1183
1143
 
1184
- if (!this.opusDecoder.init()) {
1185
- throw new Error("Opus解码器初始化失败");
1186
- }
1144
+ if (isDuplicate) {
1145
+ showMcpError("工具名称已存在,请使用不同的名称");
1146
+ return;
1147
+ }
1187
1148
 
1188
- return this.opusDecoder;
1189
- } catch (error) {
1190
- console.log(`Opus解码器初始化失败: ${error.message}`, "error");
1191
- this.opusDecoder = null;
1192
- throw error;
1149
+ // 解析模拟返回结果
1150
+ let mockResponse = null;
1151
+ if (mockResponseText) {
1152
+ try {
1153
+ mockResponse = JSON.parse(mockResponseText);
1154
+ } catch (e) {
1155
+ showMcpError("模拟返回结果不是有效的 JSON 格式: " + e.message);
1156
+ return;
1193
1157
  }
1194
1158
  }
1195
1159
 
1196
- // 启动音频缓冲
1197
- async startAudioBuffering() {
1198
- console.log("开始音频缓冲...", "info");
1199
-
1200
- this.initOpusDecoder().catch((error) => {
1201
- console.log(`预初始化Opus解码器失败: ${error.message}`, "warning");
1202
- });
1160
+ // 构建 inputSchema
1161
+ const inputSchema = {
1162
+ type: "object",
1163
+ properties: {},
1164
+ required: [],
1165
+ };
1203
1166
 
1204
- const timeout = 400;
1205
- while (true) {
1206
- const packets = await this.queue.dequeue(6, timeout, (count) => {
1207
- console.log(`缓冲超时,当前缓冲包数: ${count},开始播放`, "info");
1208
- });
1209
- if (packets.length) {
1210
- console.log(`已缓冲 ${packets.length} 个音频包,开始播放`, "info");
1211
- this.streamingContext.pushAudioBuffer(packets);
1212
- }
1167
+ mcpProperties.forEach((prop) => {
1168
+ const propSchema = { type: prop.type };
1213
1169
 
1214
- while (true) {
1215
- const data = await this.queue.dequeue(99, 30);
1216
- if (data.length) {
1217
- this.streamingContext.pushAudioBuffer(data);
1218
- } else {
1219
- break;
1220
- }
1221
- }
1170
+ if (prop.description) {
1171
+ propSchema.description = prop.description;
1222
1172
  }
1223
- }
1224
-
1225
- // 播放已缓冲的音频
1226
- async playBufferedAudio() {
1227
- try {
1228
- this.audioContext = this.getAudioContext();
1229
1173
 
1230
- if (!this.opusDecoder) {
1231
- console.log("初始化Opus解码器...", "info");
1232
- try {
1233
- this.opusDecoder = await this.initOpusDecoder();
1234
- if (!this.opusDecoder) {
1235
- throw new Error("解码器初始化失败");
1236
- }
1237
- console.log("Opus解码器初始化成功", "success");
1238
- } catch (error) {
1239
- console.log("Opus解码器初始化失败: " + error.message, "error");
1240
- this.isPlaying = false;
1241
- return;
1242
- }
1174
+ if (prop.type === "integer" || prop.type === "number") {
1175
+ if (prop.minimum !== undefined && prop.minimum !== "") {
1176
+ propSchema.minimum = prop.minimum;
1243
1177
  }
1244
-
1245
- if (!this.streamingContext) {
1246
- this.streamingContext = createStreamingContext(
1247
- this.opusDecoder,
1248
- this.audioContext,
1249
- this.SAMPLE_RATE,
1250
- this.CHANNELS,
1251
- this.MIN_AUDIO_DURATION
1252
- );
1178
+ if (prop.maximum !== undefined && prop.maximum !== "") {
1179
+ propSchema.maximum = prop.maximum;
1253
1180
  }
1254
-
1255
- this.streamingContext.decodeOpusFrames();
1256
- this.streamingContext.startPlaying();
1257
- } catch (error) {
1258
- console.log(`播放已缓冲的音频出错: ${error.message}`, "error");
1259
- this.isPlaying = false;
1260
- this.streamingContext = null;
1261
1181
  }
1262
- }
1263
1182
 
1264
- // 添加音频数据到队列
1265
- enqueueAudioData(opusData) {
1266
- if (opusData.length > 0) {
1267
- this.queue.enqueue(opusData);
1268
- } else {
1269
- console.log("收到空音频数据帧,可能是结束标志", "warning");
1270
- if (this.isPlaying && this.streamingContext) {
1271
- this.streamingContext.endOfStream = true;
1272
- }
1183
+ inputSchema.properties[prop.name] = propSchema;
1184
+
1185
+ if (prop.required) {
1186
+ inputSchema.required.push(prop.name);
1273
1187
  }
1188
+ });
1189
+
1190
+ if (inputSchema.required.length === 0) {
1191
+ delete inputSchema.required;
1274
1192
  }
1275
1193
 
1276
- // 预加载解码器
1277
- async preload() {
1278
- console.log("预加载Opus解码器...", "info");
1279
- try {
1280
- await this.initOpusDecoder();
1281
- console.log("Opus解码器预加载成功", "success");
1282
- } catch (error) {
1283
- console.log(
1284
- `Opus解码器预加载失败: ${error.message},将在需要时重试`,
1285
- "warning"
1286
- );
1287
- }
1194
+ const tool = { name, description, inputSchema, mockResponse };
1195
+
1196
+ if (mcpEditingIndex !== null) {
1197
+ mcpTools[mcpEditingIndex] = tool;
1198
+ console.log(`已更新工具: ${name}`, "success");
1199
+ } else {
1200
+ mcpTools.push(tool);
1201
+ console.log(`已添加工具: ${name}`, "success");
1288
1202
  }
1289
1203
 
1290
- // 启动播放系统
1291
- async start() {
1292
- await this.preload();
1293
- this.playBufferedAudio();
1294
- this.startAudioBuffering();
1204
+ saveMcpTools();
1205
+ renderMcpTools();
1206
+ closeMcpModal();
1207
+ }
1208
+
1209
+ /**
1210
+ * 显示错误
1211
+ */
1212
+ function showMcpError(message) {
1213
+ const errorContainer = document.getElementById("mcpErrorContainer");
1214
+ errorContainer.innerHTML = `<div class="mcp-error">${message}</div>`;
1215
+ }
1216
+
1217
+ /**
1218
+ * 编辑工具
1219
+ */
1220
+ function editMcpTool(index) {
1221
+ openMcpModal(index);
1222
+ }
1223
+
1224
+ /**
1225
+ * 删除工具
1226
+ */
1227
+ function deleteMcpTool(index) {
1228
+ const isConnected = websocket && websocket.readyState === WebSocket.OPEN;
1229
+ if (isConnected) {
1230
+ alert("WebSocket 已连接,无法编辑工具");
1231
+ return;
1232
+ }
1233
+ if (confirm(`确定要删除工具 "${mcpTools[index].name}" 吗?`)) {
1234
+ const toolName = mcpTools[index].name;
1235
+ mcpTools.splice(index, 1);
1236
+ saveMcpTools();
1237
+ renderMcpTools();
1238
+ console.log(`已删除工具: ${toolName}`, "info");
1295
1239
  }
1296
1240
  }
1297
1241
 
1298
- // 创建单例
1299
- let audioPlayerInstance = null;
1242
+ /**
1243
+ * 保存工具
1244
+ */
1245
+ function saveMcpTools() {
1246
+ localStorage.setItem("mcpTools", JSON.stringify(mcpTools));
1247
+ }
1300
1248
 
1301
- function player_getAudioPlayer() {
1302
- if (!audioPlayerInstance) {
1303
- audioPlayerInstance = new AudioPlayer();
1249
+ /**
1250
+ * 获取工具列表
1251
+ */
1252
+ function getMcpTools() {
1253
+ return mcpTools.map((tool) => ({
1254
+ name: tool.name,
1255
+ description: tool.description,
1256
+ inputSchema: tool.inputSchema,
1257
+ }));
1258
+ }
1259
+
1260
+ /**
1261
+ * 执行工具调用
1262
+ */
1263
+ function executeMcpTool(toolName, toolArgs) {
1264
+ const tool = mcpTools.find((t) => t.name === toolName);
1265
+ if (!tool) {
1266
+ console.log(`未找到工具: ${toolName}`, "error");
1267
+ return {
1268
+ success: false,
1269
+ error: `未知工具: ${toolName}`,
1270
+ };
1271
+ }
1272
+
1273
+ if (tool.name == "self.drink_car_list") {
1274
+ console.log("准备触发 gotoOrderEvent 事件"); // 增加此日志
1275
+ const event = new CustomEvent("gotoOrderEvent");
1276
+ window.dispatchEvent(event);
1277
+ return {
1278
+ success: true,
1279
+ message: `工具 ${toolName} 执行成功`,
1280
+ data: sessionStorage.getItem('cartList') || [],
1281
+ };
1282
+ } else if (tool.name == "self.drink_car_reset") {
1283
+ console.log("准备触发 resetOrderEvent 事件"); // 增加此日志
1284
+ const event = new CustomEvent("resetOrderEvent", {
1285
+ detail: toolArgs,
1286
+ });
1287
+ window.dispatchEvent(event);
1288
+ return {
1289
+ success: true,
1290
+ message: `工具 ${toolName} 执行成功`,
1291
+ data: sessionStorage.getItem('cartList') || [],
1292
+ }
1293
+ } else if (tool.name == "self.drink_order") {
1294
+ console.log("准备触发 orderEvent 事件"); // 增加此日志
1295
+ const event = new CustomEvent("orderEvent");
1296
+ window.dispatchEvent(event);
1304
1297
  }
1305
- return audioPlayerInstance;
1306
1298
  }
1307
1299
 
1300
+ // 暴露全局方法供 HTML 内联事件调用
1301
+ window.mcpModule = {
1302
+ updateMcpProperty,
1303
+ deleteMcpProperty,
1304
+ editMcpTool,
1305
+ deleteMcpTool,
1306
+ };
1307
+
1308
1308
  ;// CONCATENATED MODULE: ./src/utils/recorder.js
1309
1309
  // 音频录制模块
1310
1310
 
@@ -1340,7 +1340,7 @@ class AudioRecorder {
1340
1340
 
1341
1341
  // 获取AudioContext实例
1342
1342
  getAudioContext() {
1343
- const audioPlayer = player_getAudioPlayer();
1343
+ const audioPlayer = getAudioPlayer();
1344
1344
  return audioPlayer.getAudioContext();
1345
1345
  }
1346
1346
 
@@ -2150,7 +2150,7 @@ class WebSocketHandler {
2150
2150
  }
2151
2151
 
2152
2152
  const opusData = new Uint8Array(arrayBuffer);
2153
- const audioPlayer = player_getAudioPlayer();
2153
+ const audioPlayer = getAudioPlayer();
2154
2154
  audioPlayer.enqueueAudioData(opusData);
2155
2155
  } catch (error) {
2156
2156
  console.log(`处理二进制消息出错: ${error.message}`, "error");
@@ -2487,6 +2487,7 @@ function isWsConnected() {
2487
2487
 
2488
2488
 
2489
2489
 
2490
+
2490
2491
  /* harmony default export */ const lib_vue_loader_options_srcvue_type_script_lang_js = ({
2491
2492
  name: "ibiAiTalk",
2492
2493
  data() {