ibi-ai-talk 1.0.0 → 1.0.2

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