ibi-ai-talk 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2665 @@
1
+ (function webpackUniversalModuleDefinition(root, factory) {
2
+ if(typeof exports === 'object' && typeof module === 'object')
3
+ module.exports = factory();
4
+ else if(typeof define === 'function' && define.amd)
5
+ define([], factory);
6
+ else if(typeof exports === 'object')
7
+ exports["index"] = factory();
8
+ else
9
+ root["index"] = factory();
10
+ })((typeof self !== 'undefined' ? self : this), () => {
11
+ return /******/ (() => { // webpackBootstrap
12
+ /******/ "use strict";
13
+ /******/ // The require scope
14
+ /******/ var __webpack_require__ = {};
15
+ /******/
16
+ /************************************************************************/
17
+ /******/ /* webpack/runtime/define property getters */
18
+ /******/ (() => {
19
+ /******/ // define getter functions for harmony exports
20
+ /******/ __webpack_require__.d = (exports, definition) => {
21
+ /******/ for(var key in definition) {
22
+ /******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
23
+ /******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
24
+ /******/ }
25
+ /******/ }
26
+ /******/ };
27
+ /******/ })();
28
+ /******/
29
+ /******/ /* webpack/runtime/hasOwnProperty shorthand */
30
+ /******/ (() => {
31
+ /******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
32
+ /******/ })();
33
+ /******/
34
+ /******/ /* webpack/runtime/publicPath */
35
+ /******/ (() => {
36
+ /******/ __webpack_require__.p = "";
37
+ /******/ })();
38
+ /******/
39
+ /************************************************************************/
40
+ var __webpack_exports__ = {};
41
+
42
+ // EXPORTS
43
+ __webpack_require__.d(__webpack_exports__, {
44
+ "default": () => (/* binding */ entry_lib)
45
+ });
46
+
47
+ ;// CONCATENATED MODULE: ./node_modules/@vue/cli-service/lib/commands/build/setPublicPath.js
48
+ /* eslint-disable no-var */
49
+ // This file is imported into lib/wc client bundles.
50
+
51
+ if (typeof window !== 'undefined') {
52
+ var currentScript = window.document.currentScript
53
+ if (false) { var getCurrentScript; }
54
+
55
+ var src = currentScript && currentScript.src.match(/(.+\/)[^/]+\.js(\?.*)?$/)
56
+ if (src) {
57
+ __webpack_require__.p = src[1] // eslint-disable-line
58
+ }
59
+ }
60
+
61
+ // Indicate to webpack that this file can be concatenated
62
+ /* harmony default export */ const setPublicPath = (null);
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
65
+ var render = function render(){var _vm=this,_c=_vm._self._c;return _c("div")
66
+ }
67
+ var staticRenderFns = []
68
+
69
+
70
+ ;// CONCATENATED MODULE: ./src/utils/opus-codec.js
71
+ // 检查Opus库是否已加载
72
+ function checkOpusLoaded() {
73
+ try {
74
+ // 检查Module是否存在(本地库导出的全局变量)
75
+ if (typeof Module === 'undefined') {
76
+ throw new Error('Opus库未加载,Module对象不存在');
77
+ }
78
+
79
+ // 尝试先使用Module.instance(libopus.js最后一行导出方式)
80
+ if (typeof Module.instance !== 'undefined' && typeof Module.instance._opus_decoder_get_size === 'function') {
81
+ // 使用Module.instance对象替换全局Module对象
82
+ window.ModuleInstance = Module.instance;
83
+ console.log('Opus库加载成功(使用Module.instance)', 'success');
84
+ }
85
+
86
+ // 如果没有Module.instance,检查全局Module函数
87
+ if (typeof Module._opus_decoder_get_size === 'function') {
88
+ window.ModuleInstance = Module;
89
+ console.log('Opus库加载成功(使用全局Module)', 'success');
90
+ }
91
+
92
+ throw new Error('Opus解码函数未找到,可能Module结构不正确');
93
+ } catch (err) {
94
+ console.log(`Opus库加载失败,请检查libopus.js文件是否存在且正确: ${err.message}`, 'error');
95
+ }
96
+ }
97
+
98
+
99
+ // 创建一个Opus编码器
100
+ let opusEncoder = null;
101
+ function initOpusEncoder() {
102
+ try {
103
+ if (opusEncoder) {
104
+ return opusEncoder; // 已经初始化过
105
+ }
106
+
107
+ if (!window.ModuleInstance) {
108
+ console.log('无法创建Opus编码器:ModuleInstance不可用', 'error');
109
+ return;
110
+ }
111
+
112
+ // 初始化一个Opus编码器
113
+ const mod = window.ModuleInstance;
114
+ const sampleRate = 16000; // 16kHz采样率
115
+ const channels = 1; // 单声道
116
+ const application = 2048; // OPUS_APPLICATION_VOIP = 2048
117
+
118
+ // 创建编码器
119
+ opusEncoder = {
120
+ channels: channels,
121
+ sampleRate: sampleRate,
122
+ frameSize: 960, // 60ms @ 16kHz = 60 * 16 = 960 samples
123
+ maxPacketSize: 4000, // 最大包大小
124
+ module: mod,
125
+
126
+ // 初始化编码器
127
+ init: function () {
128
+ try {
129
+ // 获取编码器大小
130
+ const encoderSize = mod._opus_encoder_get_size(this.channels);
131
+ console.log(`Opus编码器大小: ${encoderSize}字节`, 'info');
132
+
133
+ // 分配内存
134
+ this.encoderPtr = mod._malloc(encoderSize);
135
+ if (!this.encoderPtr) {
136
+ throw new Error("无法分配编码器内存");
137
+ }
138
+
139
+ // 初始化编码器
140
+ const err = mod._opus_encoder_init(
141
+ this.encoderPtr,
142
+ this.sampleRate,
143
+ this.channels,
144
+ application
145
+ );
146
+
147
+ if (err < 0) {
148
+ throw new Error(`Opus编码器初始化失败: ${err}`);
149
+ }
150
+
151
+ // 设置位率 (16kbps)
152
+ mod._opus_encoder_ctl(this.encoderPtr, 4002, 16000); // OPUS_SET_BITRATE
153
+
154
+ // 设置复杂度 (0-10, 越高质量越好但CPU使用越多)
155
+ mod._opus_encoder_ctl(this.encoderPtr, 4010, 5); // OPUS_SET_COMPLEXITY
156
+
157
+ // 设置使用DTX (不传输静音帧)
158
+ mod._opus_encoder_ctl(this.encoderPtr, 4016, 1); // OPUS_SET_DTX
159
+
160
+ console.log("Opus编码器初始化成功", 'success');
161
+ return true;
162
+ } catch (error) {
163
+ if (this.encoderPtr) {
164
+ mod._free(this.encoderPtr);
165
+ this.encoderPtr = null;
166
+ }
167
+ console.log(`Opus编码器初始化失败: ${error.message}`, 'error');
168
+ return false;
169
+ }
170
+ },
171
+
172
+ // 编码PCM数据为Opus
173
+ encode: function (pcmData) {
174
+ if (!this.encoderPtr) {
175
+ if (!this.init()) {
176
+ return null;
177
+ }
178
+ }
179
+
180
+ try {
181
+ const mod = this.module;
182
+
183
+ // 为PCM数据分配内存
184
+ const pcmPtr = mod._malloc(pcmData.length * 2); // 2字节/int16
185
+
186
+ // 将PCM数据复制到HEAP
187
+ for (let i = 0; i < pcmData.length; i++) {
188
+ mod.HEAP16[(pcmPtr >> 1) + i] = pcmData[i];
189
+ }
190
+
191
+ // 为输出分配内存
192
+ const outPtr = mod._malloc(this.maxPacketSize);
193
+
194
+ // 进行编码
195
+ const encodedLen = mod._opus_encode(
196
+ this.encoderPtr,
197
+ pcmPtr,
198
+ this.frameSize,
199
+ outPtr,
200
+ this.maxPacketSize
201
+ );
202
+
203
+ if (encodedLen < 0) {
204
+ throw new Error(`Opus编码失败: ${encodedLen}`);
205
+ }
206
+
207
+ // 复制编码后的数据
208
+ const opusData = new Uint8Array(encodedLen);
209
+ for (let i = 0; i < encodedLen; i++) {
210
+ opusData[i] = mod.HEAPU8[outPtr + i];
211
+ }
212
+
213
+ // 释放内存
214
+ mod._free(pcmPtr);
215
+ mod._free(outPtr);
216
+
217
+ return opusData;
218
+ } catch (error) {
219
+ console.log(`Opus编码出错: ${error.message}`, 'error');
220
+ return null;
221
+ }
222
+ },
223
+
224
+ // 销毁编码器
225
+ destroy: function () {
226
+ if (this.encoderPtr) {
227
+ this.module._free(this.encoderPtr);
228
+ this.encoderPtr = null;
229
+ }
230
+ }
231
+ };
232
+
233
+ opusEncoder.init();
234
+ return opusEncoder;
235
+ } catch (error) {
236
+ console.log(`创建Opus编码器失败: ${error.message}`, 'error');
237
+ return false;
238
+ }
239
+ }
240
+ ;// CONCATENATED MODULE: ./src/utils/tools.js
241
+ // 全局变量
242
+ let mcpTools = [];
243
+ let mcpEditingIndex = null;
244
+ let mcpProperties = [];
245
+ let websocket = null; // 将从外部设置
246
+
247
+ /**
248
+ * 设置 WebSocket 实例
249
+ * @param {WebSocket} ws - WebSocket 连接实例
250
+ */
251
+ function setWebSocket(ws) {
252
+ websocket = ws;
253
+ }
254
+
255
+ /**
256
+ * 初始化 MCP 工具
257
+ */
258
+ async function initMcpTools() {
259
+ // 加载默认工具数据
260
+ const defaultMcpTools = await fetch("/default-mcp-tools.json").then((res) =>
261
+ res.json()
262
+ );
263
+
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];
271
+ }
272
+ } else {
273
+ mcpTools = [...defaultMcpTools];
274
+ }
275
+
276
+ // renderMcpTools();
277
+ // setupMcpEventListeners();
278
+ }
279
+
280
+ /**
281
+ * 渲染工具列表
282
+ */
283
+ function renderMcpTools() {
284
+ const container = document.getElementById("mcpToolsContainer");
285
+ const countSpan = document.getElementById("mcpToolsCount");
286
+
287
+ countSpan.textContent = `${mcpTools.length} 个工具`;
288
+
289
+ if (mcpTools.length === 0) {
290
+ container.innerHTML =
291
+ '<div style="text-align: center; padding: 30px; color: #999;">暂无工具,点击下方按钮添加新工具</div>';
292
+ return;
293
+ }
294
+
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;
305
+
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
+ }
343
+
344
+ /**
345
+ * 渲染参数列表
346
+ */
347
+ function renderMcpProperties() {
348
+ const container = document.getElementById("mcpPropertiesContainer");
349
+
350
+ if (mcpProperties.length === 0) {
351
+ container.innerHTML =
352
+ '<div style="text-align: center; padding: 20px; color: #999; font-size: 14px;">暂无参数,点击下方按钮添加参数</div>';
353
+ return;
354
+ }
355
+
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
+ }
438
+
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
+ }
451
+
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;
464
+ }
465
+ }
466
+
467
+ mcpProperties[index][field] = value;
468
+
469
+ if (field === "type" && value !== "integer" && value !== "number") {
470
+ delete mcpProperties[index].minimum;
471
+ delete mcpProperties[index].maximum;
472
+ renderMcpProperties();
473
+ }
474
+ }
475
+
476
+ /**
477
+ * 删除参数
478
+ */
479
+ function deleteMcpProperty(index) {
480
+ mcpProperties.splice(index, 1);
481
+ renderMcpProperties();
482
+ }
483
+
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");
496
+
497
+ toggleBtn.addEventListener("click", () => {
498
+ const isExpanded = panel.classList.contains("expanded");
499
+ panel.classList.toggle("expanded");
500
+ toggleBtn.textContent = isExpanded ? "展开" : "收起";
501
+ });
502
+
503
+ addBtn.addEventListener("click", () => openMcpModal());
504
+ closeBtn.addEventListener("click", closeMcpModal);
505
+ cancelBtn.addEventListener("click", closeMcpModal);
506
+ addPropertyBtn.addEventListener("click", addMcpProperty);
507
+
508
+ modal.addEventListener("click", (e) => {
509
+ if (e.target === modal) closeMcpModal();
510
+ });
511
+
512
+ form.addEventListener("submit", handleMcpSubmit);
513
+ }
514
+
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
+ }
524
+
525
+ mcpEditingIndex = index;
526
+ const errorContainer = document.getElementById("mcpErrorContainer");
527
+ errorContainer.innerHTML = "";
528
+
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
+ : "";
537
+
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
+ });
552
+ }
553
+ } else {
554
+ document.getElementById("mcpModalTitle").textContent = "添加工具";
555
+ document.getElementById("mcpToolForm").reset();
556
+ mcpProperties = [];
557
+ }
558
+
559
+ renderMcpProperties();
560
+ document.getElementById("mcpToolModal").style.display = "block";
561
+ }
562
+
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 = "";
572
+ }
573
+
574
+ /**
575
+ * 处理表单提交
576
+ */
577
+ function handleMcpSubmit(e) {
578
+ e.preventDefault();
579
+ const errorContainer = document.getElementById("mcpErrorContainer");
580
+ errorContainer.innerHTML = "";
581
+
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
+
590
+ // 检查名称重复
591
+ const isDuplicate = mcpTools.some(
592
+ (tool, index) => tool.name === name && index !== mcpEditingIndex
593
+ );
594
+
595
+ if (isDuplicate) {
596
+ showMcpError("工具名称已存在,请使用不同的名称");
597
+ return;
598
+ }
599
+
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;
608
+ }
609
+ }
610
+
611
+ // 构建 inputSchema
612
+ const inputSchema = {
613
+ type: "object",
614
+ properties: {},
615
+ required: [],
616
+ };
617
+
618
+ mcpProperties.forEach((prop) => {
619
+ const propSchema = { type: prop.type };
620
+
621
+ if (prop.description) {
622
+ propSchema.description = prop.description;
623
+ }
624
+
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
+ }
633
+
634
+ inputSchema.properties[prop.name] = propSchema;
635
+
636
+ if (prop.required) {
637
+ inputSchema.required.push(prop.name);
638
+ }
639
+ });
640
+
641
+ if (inputSchema.required.length === 0) {
642
+ delete inputSchema.required;
643
+ }
644
+
645
+ const tool = { name, description, inputSchema, mockResponse };
646
+
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
+ }
654
+
655
+ saveMcpTools();
656
+ renderMcpTools();
657
+ closeMcpModal();
658
+ }
659
+
660
+ /**
661
+ * 显示错误
662
+ */
663
+ function showMcpError(message) {
664
+ const errorContainer = document.getElementById("mcpErrorContainer");
665
+ errorContainer.innerHTML = `<div class="mcp-error">${message}</div>`;
666
+ }
667
+
668
+ /**
669
+ * 编辑工具
670
+ */
671
+ function editMcpTool(index) {
672
+ openMcpModal(index);
673
+ }
674
+
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
+ }
692
+
693
+ /**
694
+ * 保存工具
695
+ */
696
+ function saveMcpTools() {
697
+ localStorage.setItem("mcpTools", JSON.stringify(mcpTools));
698
+ }
699
+
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
+ }
710
+
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
+ }
723
+
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
+ }
750
+
751
+ // 暴露全局方法供 HTML 内联事件调用
752
+ window.mcpModule = {
753
+ updateMcpProperty,
754
+ deleteMcpProperty,
755
+ editMcpTool,
756
+ deleteMcpTool,
757
+ };
758
+
759
+ ;// CONCATENATED MODULE: ./src/utils/blocking-queue.js
760
+ class BlockingQueue {
761
+ #items = [];
762
+ #waiters = []; // {resolve, reject, min, timer, onTimeout}
763
+
764
+ /* 空队列一次性闸门 */
765
+ #emptyPromise = null;
766
+ #emptyResolve = null;
767
+
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
+ }
785
+
786
+ // 唤醒所有正在等的 waiter
787
+ this.#wakeWaiters();
788
+ }
789
+
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 };
806
+
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
+ }
815
+
816
+ this.#waiters.push(waiter);
817
+ });
818
+ }
819
+
820
+ /* 空队列闸门生成器 */
821
+ #waitForFirstItem() {
822
+ if (!this.#emptyPromise) {
823
+ this.#emptyPromise = new Promise(r => (this.#emptyResolve = r));
824
+ }
825
+ return this.#emptyPromise;
826
+ }
827
+
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
+ }
838
+
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);
844
+ }
845
+ }
846
+
847
+ #flush() {
848
+ const snapshot = [...this.#items];
849
+ this.#items.length = 0;
850
+ return snapshot;
851
+ }
852
+
853
+ /* 当前缓存长度(不含等待者) */
854
+ get length() {
855
+ return this.#items.length;
856
+ }
857
+ }
858
+ ;// CONCATENATED MODULE: ./src/utils/stream-context.js
859
+
860
+
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;
872
+
873
+ // 音频参数
874
+ this.sampleRate = sampleRate;
875
+ this.channels = channels;
876
+ this.minAudioDuration = minAudioDuration;
877
+
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; // 上次播放的时间戳
888
+ }
889
+
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
+ }
926
+
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
+ }
935
+
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
+ }
954
+
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();
965
+ }
966
+ }
967
+
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);
996
+
997
+ // 创建音频源
998
+ this.source = this.audioContext.createBufferSource();
999
+ this.source.buffer = audioBuffer;
1000
+
1001
+ // 精确调度播放时间
1002
+ const currentTime = this.audioContext.currentTime;
1003
+ const startTime = Math.max(scheduledEndTime, currentTime);
1004
+
1005
+ // 直接连接到输出
1006
+ this.source.connect(this.audioContext.destination);
1007
+
1008
+ this.source.start(startTime);
1009
+
1010
+ // 更新下一个音频块的调度时间
1011
+ const duration = audioBuffer.duration;
1012
+ scheduledEndTime = startTime + duration;
1013
+ this.lastPlayTime = startTime;
1014
+
1015
+ // 如果队列中数据不足,等待新数据
1016
+ if (this.queue.length < targetSamples) {
1017
+ break;
1018
+ }
1019
+ }
1020
+
1021
+ // 等待新数据
1022
+ await this.getQueue(minSamples);
1023
+ }
1024
+ }
1025
+ }
1026
+
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
+ );
1042
+ }
1043
+
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
+ );
1078
+ }
1079
+ return this.audioContext;
1080
+ }
1081
+
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,
1104
+
1105
+ init: function () {
1106
+ if (this.decoderPtr) return true;
1107
+
1108
+ const decoderSize = mod._opus_decoder_get_size(this.channels);
1109
+ console.log(`Opus解码器大小: ${decoderSize}字节`, "debug");
1110
+
1111
+ this.decoderPtr = mod._malloc(decoderSize);
1112
+ if (!this.decoderPtr) {
1113
+ throw new Error("无法分配解码器内存");
1114
+ }
1115
+
1116
+ const err = mod._opus_decoder_init(
1117
+ this.decoderPtr,
1118
+ this.rate,
1119
+ this.channels
1120
+ );
1121
+
1122
+ if (err < 0) {
1123
+ this.destroy();
1124
+ throw new Error(`Opus解码器初始化失败: ${err}`);
1125
+ }
1126
+
1127
+ console.log("Opus解码器初始化成功", "success");
1128
+ return true;
1129
+ },
1130
+
1131
+ decode: function (opusData) {
1132
+ if (!this.decoderPtr) {
1133
+ if (!this.init()) {
1134
+ throw new Error("解码器未初始化且无法初始化");
1135
+ }
1136
+ }
1137
+
1138
+ try {
1139
+ const mod = this.module;
1140
+
1141
+ const opusPtr = mod._malloc(opusData.length);
1142
+ mod.HEAPU8.set(opusData, opusPtr);
1143
+
1144
+ const pcmPtr = mod._malloc(this.frameSize * 2);
1145
+
1146
+ const decodedSamples = mod._opus_decode(
1147
+ this.decoderPtr,
1148
+ opusPtr,
1149
+ opusData.length,
1150
+ pcmPtr,
1151
+ this.frameSize,
1152
+ 0
1153
+ );
1154
+
1155
+ if (decodedSamples < 0) {
1156
+ mod._free(opusPtr);
1157
+ mod._free(pcmPtr);
1158
+ throw new Error(`Opus解码失败: ${decodedSamples}`);
1159
+ }
1160
+
1161
+ const decodedData = new Int16Array(decodedSamples);
1162
+ for (let i = 0; i < decodedSamples; i++) {
1163
+ decodedData[i] = mod.HEAP16[(pcmPtr >> 1) + i];
1164
+ }
1165
+
1166
+ mod._free(opusPtr);
1167
+ mod._free(pcmPtr);
1168
+
1169
+ return decodedData;
1170
+ } catch (error) {
1171
+ console.log(`Opus解码错误: ${error.message}`, "error");
1172
+ return new Int16Array(0);
1173
+ }
1174
+ },
1175
+
1176
+ destroy: function () {
1177
+ if (this.decoderPtr) {
1178
+ this.module._free(this.decoderPtr);
1179
+ this.decoderPtr = null;
1180
+ }
1181
+ },
1182
+ };
1183
+
1184
+ if (!this.opusDecoder.init()) {
1185
+ throw new Error("Opus解码器初始化失败");
1186
+ }
1187
+
1188
+ return this.opusDecoder;
1189
+ } catch (error) {
1190
+ console.log(`Opus解码器初始化失败: ${error.message}`, "error");
1191
+ this.opusDecoder = null;
1192
+ throw error;
1193
+ }
1194
+ }
1195
+
1196
+ // 启动音频缓冲
1197
+ async startAudioBuffering() {
1198
+ console.log("开始音频缓冲...", "info");
1199
+
1200
+ this.initOpusDecoder().catch((error) => {
1201
+ console.log(`预初始化Opus解码器失败: ${error.message}`, "warning");
1202
+ });
1203
+
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
+ }
1213
+
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
+ }
1222
+ }
1223
+ }
1224
+
1225
+ // 播放已缓冲的音频
1226
+ async playBufferedAudio() {
1227
+ try {
1228
+ this.audioContext = this.getAudioContext();
1229
+
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
+ }
1243
+ }
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
+ );
1253
+ }
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
+ }
1262
+ }
1263
+
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
+ }
1273
+ }
1274
+ }
1275
+
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
+ }
1288
+ }
1289
+
1290
+ // 启动播放系统
1291
+ async start() {
1292
+ await this.preload();
1293
+ this.playBufferedAudio();
1294
+ this.startAudioBuffering();
1295
+ }
1296
+ }
1297
+
1298
+ // 创建单例
1299
+ let audioPlayerInstance = null;
1300
+
1301
+ function player_getAudioPlayer() {
1302
+ if (!audioPlayerInstance) {
1303
+ audioPlayerInstance = new AudioPlayer();
1304
+ }
1305
+ return audioPlayerInstance;
1306
+ }
1307
+
1308
+ ;// CONCATENATED MODULE: ./src/utils/recorder.js
1309
+ // 音频录制模块
1310
+
1311
+
1312
+
1313
+ // 音频录制器类
1314
+ class AudioRecorder {
1315
+ constructor() {
1316
+ this.isRecording = false;
1317
+ this.audioContext = null;
1318
+ this.analyser = null;
1319
+ this.audioProcessor = null;
1320
+ this.audioProcessorType = null;
1321
+ this.audioSource = null;
1322
+ this.opusEncoder = null;
1323
+ this.pcmDataBuffer = new Int16Array();
1324
+ this.audioBuffers = [];
1325
+ this.totalAudioSize = 0;
1326
+ this.visualizationRequest = null;
1327
+ this.recordingTimer = null;
1328
+ this.websocket = null;
1329
+
1330
+ // 回调函数
1331
+ this.onRecordingStart = null;
1332
+ this.onRecordingStop = null;
1333
+ this.onVisualizerUpdate = null;
1334
+ }
1335
+
1336
+ // 设置WebSocket实例
1337
+ setWebSocket(ws) {
1338
+ this.websocket = ws;
1339
+ }
1340
+
1341
+ // 获取AudioContext实例
1342
+ getAudioContext() {
1343
+ const audioPlayer = player_getAudioPlayer();
1344
+ return audioPlayer.getAudioContext();
1345
+ }
1346
+
1347
+ // 初始化编码器
1348
+ initEncoder() {
1349
+ if (!this.opusEncoder) {
1350
+ this.opusEncoder = initOpusEncoder();
1351
+ }
1352
+ return this.opusEncoder;
1353
+ }
1354
+
1355
+ // PCM处理器代码
1356
+ getAudioProcessorCode() {
1357
+ return `
1358
+ class AudioRecorderProcessor extends AudioWorkletProcessor {
1359
+ constructor() {
1360
+ super();
1361
+ this.buffers = [];
1362
+ this.frameSize = 960;
1363
+ this.buffer = new Int16Array(this.frameSize);
1364
+ this.bufferIndex = 0;
1365
+ this.isRecording = false;
1366
+
1367
+ this.port.onmessage = (event) => {
1368
+ if (event.data.command === 'start') {
1369
+ this.isRecording = true;
1370
+ this.port.postMessage({ type: 'status', status: 'started' });
1371
+ } else if (event.data.command === 'stop') {
1372
+ this.isRecording = false;
1373
+
1374
+ if (this.bufferIndex > 0) {
1375
+ const finalBuffer = this.buffer.slice(0, this.bufferIndex);
1376
+ this.port.postMessage({
1377
+ type: 'buffer',
1378
+ buffer: finalBuffer
1379
+ });
1380
+ this.bufferIndex = 0;
1381
+ }
1382
+
1383
+ this.port.postMessage({ type: 'status', status: 'stopped' });
1384
+ }
1385
+ };
1386
+ }
1387
+
1388
+ process(inputs, outputs, parameters) {
1389
+ if (!this.isRecording) return true;
1390
+
1391
+ const input = inputs[0][0];
1392
+ if (!input) return true;
1393
+
1394
+ for (let i = 0; i < input.length; i++) {
1395
+ if (this.bufferIndex >= this.frameSize) {
1396
+ this.port.postMessage({
1397
+ type: 'buffer',
1398
+ buffer: this.buffer.slice(0)
1399
+ });
1400
+ this.bufferIndex = 0;
1401
+ }
1402
+
1403
+ this.buffer[this.bufferIndex++] = Math.max(-32768, Math.min(32767, Math.floor(input[i] * 32767)));
1404
+ }
1405
+
1406
+ return true;
1407
+ }
1408
+ }
1409
+
1410
+ registerProcessor('audio-recorder-processor', AudioRecorderProcessor);
1411
+ `;
1412
+ }
1413
+
1414
+ // 创建音频处理器
1415
+ async createAudioProcessor() {
1416
+ this.audioContext = this.getAudioContext();
1417
+
1418
+ try {
1419
+ if (this.audioContext.audioWorklet) {
1420
+ const blob = new Blob([this.getAudioProcessorCode()], {
1421
+ type: "application/javascript",
1422
+ });
1423
+ const url = URL.createObjectURL(blob);
1424
+ await this.audioContext.audioWorklet.addModule(url);
1425
+ URL.revokeObjectURL(url);
1426
+
1427
+ const audioProcessor = new AudioWorkletNode(
1428
+ this.audioContext,
1429
+ "audio-recorder-processor"
1430
+ );
1431
+
1432
+ audioProcessor.port.onmessage = (event) => {
1433
+ if (event.data.type === "buffer") {
1434
+ this.processPCMBuffer(event.data.buffer);
1435
+ }
1436
+ };
1437
+
1438
+ console.log("使用AudioWorklet处理音频", "success");
1439
+
1440
+ const silent = this.audioContext.createGain();
1441
+ silent.gain.value = 0;
1442
+ audioProcessor.connect(silent);
1443
+ silent.connect(this.audioContext.destination);
1444
+ return { node: audioProcessor, type: "worklet" };
1445
+ } else {
1446
+ console.log(
1447
+ "AudioWorklet不可用,使用ScriptProcessorNode作为回退方案",
1448
+ "warning"
1449
+ );
1450
+ return this.createScriptProcessor();
1451
+ }
1452
+ } catch (error) {
1453
+ console.log(
1454
+ `创建音频处理器失败: ${error.message},尝试回退方案`,
1455
+ "error"
1456
+ );
1457
+ return this.createScriptProcessor();
1458
+ }
1459
+ }
1460
+
1461
+ // 创建ScriptProcessor作为回退
1462
+ createScriptProcessor() {
1463
+ try {
1464
+ const frameSize = 4096;
1465
+ const scriptProcessor = this.audioContext.createScriptProcessor(
1466
+ frameSize,
1467
+ 1,
1468
+ 1
1469
+ );
1470
+
1471
+ scriptProcessor.onaudioprocess = (event) => {
1472
+ if (!this.isRecording) return;
1473
+
1474
+ const input = event.inputBuffer.getChannelData(0);
1475
+ const buffer = new Int16Array(input.length);
1476
+
1477
+ for (let i = 0; i < input.length; i++) {
1478
+ buffer[i] = Math.max(
1479
+ -32768,
1480
+ Math.min(32767, Math.floor(input[i] * 32767))
1481
+ );
1482
+ }
1483
+
1484
+ this.processPCMBuffer(buffer);
1485
+ };
1486
+
1487
+ const silent = this.audioContext.createGain();
1488
+ silent.gain.value = 0;
1489
+ scriptProcessor.connect(silent);
1490
+ silent.connect(this.audioContext.destination);
1491
+
1492
+ console.log("使用ScriptProcessorNode作为回退方案成功", "warning");
1493
+ return { node: scriptProcessor, type: "processor" };
1494
+ } catch (fallbackError) {
1495
+ console.log(`回退方案也失败: ${fallbackError.message}`, "error");
1496
+ return null;
1497
+ }
1498
+ }
1499
+
1500
+ // 处理PCM缓冲数据
1501
+ processPCMBuffer(buffer) {
1502
+ if (!this.isRecording) return;
1503
+
1504
+ const newBuffer = new Int16Array(this.pcmDataBuffer.length + buffer.length);
1505
+ newBuffer.set(this.pcmDataBuffer);
1506
+ newBuffer.set(buffer, this.pcmDataBuffer.length);
1507
+ this.pcmDataBuffer = newBuffer;
1508
+
1509
+ const samplesPerFrame = 960;
1510
+
1511
+ while (this.pcmDataBuffer.length >= samplesPerFrame) {
1512
+ const frameData = this.pcmDataBuffer.slice(0, samplesPerFrame);
1513
+ this.pcmDataBuffer = this.pcmDataBuffer.slice(samplesPerFrame);
1514
+
1515
+ this.encodeAndSendOpus(frameData);
1516
+ }
1517
+ }
1518
+
1519
+ // 编码并发送Opus数据
1520
+ encodeAndSendOpus(pcmData = null) {
1521
+ if (!this.opusEncoder) {
1522
+ console.log("Opus编码器未初始化", "error");
1523
+ return;
1524
+ }
1525
+
1526
+ try {
1527
+ if (pcmData) {
1528
+ const opusData = this.opusEncoder.encode(pcmData);
1529
+ if (opusData && opusData.length > 0) {
1530
+ this.audioBuffers.push(opusData.buffer);
1531
+ this.totalAudioSize += opusData.length;
1532
+
1533
+ if (this.websocket && this.websocket.readyState === WebSocket.OPEN) {
1534
+ try {
1535
+ this.websocket.send(opusData.buffer);
1536
+ } catch (error) {
1537
+ console.log(`WebSocket发送错误: ${error.message}`, "error");
1538
+ }
1539
+ }
1540
+ } else {
1541
+ log("Opus编码失败,无有效数据返回", "error");
1542
+ }
1543
+ } else {
1544
+ if (this.pcmDataBuffer.length > 0) {
1545
+ const samplesPerFrame = 960;
1546
+ if (this.pcmDataBuffer.length < samplesPerFrame) {
1547
+ const paddedBuffer = new Int16Array(samplesPerFrame);
1548
+ paddedBuffer.set(this.pcmDataBuffer);
1549
+ this.encodeAndSendOpus(paddedBuffer);
1550
+ } else {
1551
+ this.encodeAndSendOpus(
1552
+ this.pcmDataBuffer.slice(0, samplesPerFrame)
1553
+ );
1554
+ }
1555
+ this.pcmDataBuffer = new Int16Array(0);
1556
+ }
1557
+ }
1558
+ } catch (error) {
1559
+ console.log(`Opus编码错误: ${error.message}`, "error");
1560
+ }
1561
+ }
1562
+
1563
+ // 开始录音
1564
+ async start() {
1565
+ try {
1566
+ if (!this.initEncoder()) {
1567
+ console.log("无法启动录音: Opus编码器初始化失败", "error");
1568
+ return false;
1569
+ }
1570
+
1571
+ const stream = await navigator.mediaDevices.getUserMedia({
1572
+ audio: {
1573
+ echoCancellation: true,
1574
+ noiseSuppression: true,
1575
+ sampleRate: 16000,
1576
+ channelCount: 1,
1577
+ latency: { ideal: 0.02, max: 0.05 },
1578
+ // Chrome 扩展参数(非标准,可能变动)
1579
+ googNoiseSuppression: true, // 启用 Chrome 噪声抑制
1580
+ googNoiseSuppression2: 3, // 级别设置(1-3,数值越高抑制越强,不同版本可能有差异)
1581
+ googAutoGainControl: true, // 自动增益控制
1582
+ googHighpassFilter: true, // 高通滤波器(过滤低频噪声)
1583
+ },
1584
+ });
1585
+
1586
+ this.audioContext = this.getAudioContext();
1587
+
1588
+ if (this.audioContext.state === "suspended") {
1589
+ await this.audioContext.resume();
1590
+ }
1591
+
1592
+ const processorResult = await this.createAudioProcessor();
1593
+ if (!processorResult) {
1594
+ console.log("无法创建音频处理器", "error");
1595
+ return false;
1596
+ }
1597
+
1598
+ this.audioProcessor = processorResult.node;
1599
+ this.audioProcessorType = processorResult.type;
1600
+
1601
+ this.audioSource = this.audioContext.createMediaStreamSource(stream);
1602
+ this.analyser = this.audioContext.createAnalyser();
1603
+ this.analyser.fftSize = 2048;
1604
+
1605
+ this.audioSource.connect(this.analyser);
1606
+ this.audioSource.connect(this.audioProcessor);
1607
+
1608
+ this.pcmDataBuffer = new Int16Array();
1609
+ this.audioBuffers = [];
1610
+ this.totalAudioSize = 0;
1611
+ this.isRecording = true;
1612
+
1613
+ if (this.audioProcessorType === "worklet" && this.audioProcessor.port) {
1614
+ this.audioProcessor.port.postMessage({ command: "start" });
1615
+ }
1616
+
1617
+ // 发送监听开始消息
1618
+ if (this.websocket && this.websocket.readyState === WebSocket.OPEN) {
1619
+ const listenMessage = {
1620
+ type: "listen",
1621
+ mode: localStorage.getItem("listenMode") || "wakeup",
1622
+ state: "start",
1623
+ };
1624
+
1625
+ console.log(
1626
+ `发送录音开始消息: ${JSON.stringify(listenMessage)}`,
1627
+ "info"
1628
+ );
1629
+ this.websocket.send(JSON.stringify(listenMessage));
1630
+ } else {
1631
+ console.log("WebSocket未连接,无法发送开始消息", "error");
1632
+ return false;
1633
+ }
1634
+
1635
+ // 启动录音计时器
1636
+ let recordingSeconds = 0;
1637
+ this.recordingTimer = setInterval(() => {
1638
+ recordingSeconds += 0.1;
1639
+ if (this.onRecordingStart) {
1640
+ this.onRecordingStart(recordingSeconds);
1641
+ }
1642
+ }, 100);
1643
+
1644
+ console.log("开始PCM直接录音", "success");
1645
+ return true;
1646
+ } catch (error) {
1647
+ console.log(`直接录音启动错误: ${error.message}`, "error");
1648
+ this.isRecording = false;
1649
+ return false;
1650
+ }
1651
+ }
1652
+
1653
+ // 停止录音
1654
+ stop() {
1655
+ if (!this.isRecording) return false;
1656
+
1657
+ try {
1658
+ this.isRecording = false;
1659
+
1660
+ if (this.audioProcessor) {
1661
+ if (this.audioProcessorType === "worklet" && this.audioProcessor.port) {
1662
+ this.audioProcessor.port.postMessage({ command: "stop" });
1663
+ }
1664
+
1665
+ this.audioProcessor.disconnect();
1666
+ this.audioProcessor = null;
1667
+ }
1668
+
1669
+ if (this.audioSource) {
1670
+ this.audioSource.disconnect();
1671
+ this.audioSource = null;
1672
+ }
1673
+
1674
+ if (this.visualizationRequest) {
1675
+ cancelAnimationFrame(this.visualizationRequest);
1676
+ this.visualizationRequest = null;
1677
+ }
1678
+
1679
+ if (this.recordingTimer) {
1680
+ clearInterval(this.recordingTimer);
1681
+ this.recordingTimer = null;
1682
+ }
1683
+
1684
+ // 编码并发送剩余的数据
1685
+ this.encodeAndSendOpus();
1686
+
1687
+ // 发送结束信号
1688
+ if (this.websocket && this.websocket.readyState === WebSocket.OPEN) {
1689
+ const emptyOpusFrame = new Uint8Array(0);
1690
+ this.websocket.send(emptyOpusFrame);
1691
+
1692
+ const stopMessage = {
1693
+ type: "listen",
1694
+ mode: localStorage.getItem("listenMode") || "wakeup",
1695
+ state: "stop",
1696
+ };
1697
+
1698
+ this.websocket.send(JSON.stringify(stopMessage));
1699
+ console.log("已发送录音停止信号", "info");
1700
+ }
1701
+
1702
+ if (this.onRecordingStop) {
1703
+ this.onRecordingStop();
1704
+ }
1705
+
1706
+ console.log("停止PCM直接录音", "success");
1707
+ return true;
1708
+ } catch (error) {
1709
+ console.log(`直接录音停止错误: ${error.message}`, "error");
1710
+ return false;
1711
+ }
1712
+ }
1713
+
1714
+ // 获取分析器
1715
+ getAnalyser() {
1716
+ return this.analyser;
1717
+ }
1718
+ }
1719
+
1720
+ // 创建单例
1721
+ let audioRecorderInstance = null;
1722
+
1723
+ function recorder_getAudioRecorder() {
1724
+ if (!audioRecorderInstance) {
1725
+ audioRecorderInstance = new AudioRecorder();
1726
+ }
1727
+ return audioRecorderInstance;
1728
+ }
1729
+
1730
+ ;// CONCATENATED MODULE: ./src/utils/ota-connector.js
1731
+ // WebSocket 连接
1732
+ async function webSocketConnect(otaUrl, config) {
1733
+
1734
+ if (!validateConfig(config)) {
1735
+ return;
1736
+ }
1737
+
1738
+ // 发送OTA请求并获取返回的websocket信息
1739
+ const otaResult = await sendOTA(otaUrl, config);
1740
+ if (!otaResult) {
1741
+ console.log('无法从OTA服务器获取信息', 'error');
1742
+ return;
1743
+ }
1744
+
1745
+ // 从OTA响应中提取websocket信息
1746
+ const { websocket } = otaResult;
1747
+ if (!websocket || !websocket.url) {
1748
+ console.log('OTA响应中缺少websocket信息', 'error');
1749
+ return;
1750
+ }
1751
+
1752
+ // 使用OTA返回的websocket URL
1753
+ let connUrl = new URL(websocket.url);
1754
+
1755
+ // 添加token参数(从OTA响应中获取)
1756
+ if (websocket.token) {
1757
+ if (websocket.token.startsWith("Bearer ")) {
1758
+ connUrl.searchParams.append('authorization', websocket.token);
1759
+ } else {
1760
+ connUrl.searchParams.append('authorization', 'Bearer ' + websocket.token);
1761
+ }
1762
+ }
1763
+
1764
+ // 添加认证参数(保持原有逻辑)
1765
+ connUrl.searchParams.append('device-id', config.deviceId);
1766
+ connUrl.searchParams.append('client-id', config.clientId);
1767
+
1768
+ const wsurl = connUrl.toString()
1769
+
1770
+ console.log(`正在连接: ${wsurl}`, 'info');
1771
+
1772
+ return new WebSocket(connUrl.toString());
1773
+ }
1774
+
1775
+ // 验证配置
1776
+ function validateConfig(config) {
1777
+ if (!config.deviceMac) {
1778
+ console.log('设备MAC地址不能为空', 'error');
1779
+ return false;
1780
+ }
1781
+ if (!config.clientId) {
1782
+ console.log('客户端ID不能为空', 'error');
1783
+ return false;
1784
+ }
1785
+ return true;
1786
+ }
1787
+
1788
+ // 判断wsUrl路径是否存在错误
1789
+ function validateWsUrl(wsUrl) {
1790
+ if (wsUrl === '') return false;
1791
+ // 检查URL格式
1792
+ if (!wsUrl.startsWith('ws://') && !wsUrl.startsWith('wss://')) {
1793
+ console.log('URL格式错误,必须以ws://或wss://开头', 'error');
1794
+ return false;
1795
+ }
1796
+ return true
1797
+ }
1798
+
1799
+
1800
+ // OTA发送请求,验证状态,并返回响应数据
1801
+ async function sendOTA(otaUrl, config) {
1802
+ try {
1803
+ const res = await fetch(otaUrl, {
1804
+ method: 'POST',
1805
+ headers: {
1806
+ 'Content-Type': 'application/json',
1807
+ 'Device-Id': config.deviceId,
1808
+ 'Client-Id': config.clientId
1809
+ },
1810
+ body: JSON.stringify({
1811
+ version: 0,
1812
+ uuid: '',
1813
+ application: {
1814
+ name: 'xiaozhi-web-test',
1815
+ version: '1.0.0',
1816
+ compile_time: '2025-04-16 10:00:00',
1817
+ idf_version: '4.4.3',
1818
+ elf_sha256: '1234567890abcdef1234567890abcdef1234567890abcdef'
1819
+ },
1820
+ ota: { label: 'xiaozhi-web-test' },
1821
+ board: {
1822
+ type: 'xiaozhi-web-test',
1823
+ ssid: 'xiaozhi-web-test',
1824
+ rssi: 0,
1825
+ channel: 0,
1826
+ ip: '192.168.1.1',
1827
+ mac: config.deviceMac
1828
+ },
1829
+ flash_size: 0,
1830
+ minimum_free_heap_size: 0,
1831
+ mac_address: config.deviceMac,
1832
+ chip_model_name: '',
1833
+ chip_info: { model: 0, cores: 0, revision: 0, features: 0 },
1834
+ partition_table: [{ label: '', type: 0, subtype: 0, address: 0, size: 0 }]
1835
+ })
1836
+ });
1837
+
1838
+ if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
1839
+
1840
+ const result = await res.json();
1841
+ return result; // 返回完整的响应数据
1842
+ } catch (err) {
1843
+ return null; // 失败返回null
1844
+ }
1845
+ }
1846
+ ;// CONCATENATED MODULE: ./src/utils/manager.js
1847
+ // 生成随机MAC地址
1848
+ function generateRandomMac() {
1849
+ const hexDigits = "0123456789ABCDEF";
1850
+ let mac = "";
1851
+ for (let i = 0; i < 6; i++) {
1852
+ if (i > 0) mac += ":";
1853
+ for (let j = 0; j < 2; j++) {
1854
+ mac += hexDigits.charAt(Math.floor(Math.random() * 16));
1855
+ }
1856
+ }
1857
+ return mac;
1858
+ }
1859
+
1860
+ // 加载配置
1861
+ function loadConfig() {
1862
+ // 从localStorage加载MAC地址,如果没有则生成新的
1863
+ let savedMac = localStorage.getItem("xz_tester_deviceMac");
1864
+ if (!savedMac) {
1865
+ savedMac = generateRandomMac();
1866
+ localStorage.setItem("xz_tester_deviceMac", savedMac);
1867
+ }
1868
+ }
1869
+
1870
+ // 获取配置值
1871
+ function getConfig() {
1872
+ return {
1873
+ deviceId: localStorage.getItem("MAC"), // 使用MAC地址作为deviceId
1874
+ deviceName: "测试设备",
1875
+ deviceMac: localStorage.getItem("MAC"),
1876
+ clientId: "web_test_client",
1877
+ token: "your-token1",
1878
+ };
1879
+ }
1880
+
1881
+ // 保存连接URL
1882
+ function saveConnectionUrls() {
1883
+ const otaUrl = localStorage.getItem("otaUrl");
1884
+ localStorage.setItem("xz_tester_otaUrl", otaUrl);
1885
+ }
1886
+
1887
+ ;// CONCATENATED MODULE: ./src/utils/websocket.js
1888
+ // WebSocket消息处理模块
1889
+
1890
+
1891
+
1892
+
1893
+
1894
+
1895
+ // WebSocket处理器类
1896
+ class WebSocketHandler {
1897
+ constructor() {
1898
+ this.websocket = null;
1899
+ this.onConnectionStateChange = null;
1900
+ this.onRecordButtonStateChange = null;
1901
+ this.onSessionStateChange = null;
1902
+ this.onSessionEmotionChange = null;
1903
+ this.currentSessionId = null;
1904
+ this.isRemoteSpeaking = false;
1905
+ this.heartbeatTimer = null;
1906
+
1907
+ // 重连相关配置
1908
+ this.reconnectConfig = {
1909
+ maxRetries: 10, // 最大重连次数
1910
+ baseDelay: 1000, // 基础重连延迟(ms)
1911
+ maxDelay: 30000, // 最大重连延迟(ms)
1912
+ retryCount: 0, // 当前重连次数
1913
+ reconnectTimer: null, // 重连定时器
1914
+ isReconnecting: false, // 是否正在重连中
1915
+ manualDisconnect: false, // 是否手动断开连接
1916
+ };
1917
+ }
1918
+
1919
+ // 在 WebSocketHandler 类中添加
1920
+ startHeartbeat() {
1921
+ this.stopHeartbeat(); // 先清除之前的定时器
1922
+ this.sendHeartbeat();
1923
+ }
1924
+
1925
+ stopHeartbeat() {
1926
+ if (this.heartbeatTimer) {
1927
+ clearTimeout(this.heartbeatTimer);
1928
+ this.heartbeatTimer = null;
1929
+ }
1930
+ }
1931
+
1932
+ sendHeartbeat() {
1933
+ if (this.websocket?.readyState === WebSocket.OPEN) {
1934
+ try {
1935
+ this.websocket.send("1");
1936
+ } catch (error) {
1937
+ console.error("心跳发送失败:", error);
1938
+ }
1939
+ }
1940
+
1941
+ this.heartbeatTimer = setTimeout(() => {
1942
+ this.sendHeartbeat();
1943
+ }, 5000);
1944
+ }
1945
+
1946
+ // 发送hello握手消息
1947
+ async sendHelloMessage() {
1948
+ if (!this.websocket || this.websocket.readyState !== WebSocket.OPEN)
1949
+ return false;
1950
+
1951
+ this.startHeartbeat(); // 启动心跳
1952
+ try {
1953
+ const config = getConfig();
1954
+
1955
+ const helloMessage = {
1956
+ type: "hello",
1957
+ device_id: config.deviceId,
1958
+ device_name: config.deviceName,
1959
+ device_mac: config.deviceMac,
1960
+ token: config.token,
1961
+ seat: localStorage.getItem("SEAT"),
1962
+ features: {
1963
+ mcp: true,
1964
+ },
1965
+ };
1966
+
1967
+ console.log("发送hello握手消息", "info");
1968
+ this.websocket.send(JSON.stringify(helloMessage));
1969
+
1970
+ return new Promise((resolve) => {
1971
+ const timeout = setTimeout(() => {
1972
+ console.log("等待hello响应超时", "error");
1973
+ console.log('提示: 请尝试点击"测试认证"按钮进行连接排查', "info");
1974
+ resolve(false);
1975
+ }, 5000);
1976
+
1977
+ const onMessageHandler = (event) => {
1978
+ try {
1979
+ const response = JSON.parse(event.data);
1980
+ if (response.type === "hello" && response.session_id) {
1981
+ console.log(
1982
+ `服务器握手成功,会话ID: ${response.session_id}`,
1983
+ "success"
1984
+ );
1985
+ // 握手成功后重置重连计数器
1986
+ this.reconnectConfig.retryCount = 0;
1987
+ clearTimeout(timeout);
1988
+ this.websocket.removeEventListener("message", onMessageHandler);
1989
+ resolve(true);
1990
+ }
1991
+ } catch (e) {
1992
+ // 忽略非JSON消息
1993
+ }
1994
+ };
1995
+
1996
+ this.websocket.addEventListener("message", onMessageHandler);
1997
+ });
1998
+ } catch (error) {
1999
+ console.log(`发送hello消息错误: ${error.message}`, "error");
2000
+ return false;
2001
+ }
2002
+ }
2003
+
2004
+ // 处理文本消息
2005
+ handleTextMessage(message) {
2006
+ if (message.type === "hello") {
2007
+ } else if (message.type === "tts") {
2008
+ this.handleTTSMessage(message);
2009
+ } else if (message.type === "audio") {
2010
+ } else if (message.type === "stt") {
2011
+ const event = new CustomEvent("wsSendMessage", {
2012
+ detail: message,
2013
+ });
2014
+ window.dispatchEvent(event);
2015
+ } else if (message.type === "llm") {
2016
+ } else if (message.type === "mcp") {
2017
+ this.handleMCPMessage(message);
2018
+ } else if (message.type === "json_data") {
2019
+ if (message.state === "drinks") {
2020
+ const event = new CustomEvent("drinkListEvent", {
2021
+ detail: message.data,
2022
+ });
2023
+ window.dispatchEvent(event);
2024
+ } else if (message.state === "book") {
2025
+ const event = new CustomEvent("bookListEvent", {
2026
+ detail: message
2027
+ });
2028
+ window.dispatchEvent(event);
2029
+ }
2030
+ } else if (message.type === "view_action") {
2031
+ const event = new CustomEvent("viewActionEvent", {
2032
+ detail: message.state,
2033
+ });
2034
+ window.dispatchEvent(event);
2035
+ } else {
2036
+ console.log(`未知消息类型: ${message.type}`, "warning");
2037
+ }
2038
+ }
2039
+
2040
+ // 处理TTS消息
2041
+ handleTTSMessage(message) {
2042
+ if (message.state === "start") {
2043
+ console.log("服务器开始发送语音", "info");
2044
+ this.currentSessionId = message.session_id;
2045
+ const event = new CustomEvent("startThink");
2046
+ window.dispatchEvent(event);
2047
+ this.isRemoteSpeaking = true;
2048
+ if (this.onSessionStateChange) {
2049
+ this.onSessionStateChange(true);
2050
+ }
2051
+ } else if (message.state === "sentence_start") {
2052
+ const event = new CustomEvent("startVolic", {
2053
+ detail: message.text,
2054
+ });
2055
+ window.dispatchEvent(event);
2056
+ console.log(`服务器发送语音段: ${message.text}`, "info");
2057
+ } else if (message.state === "sentence_end") {
2058
+ console.log(`语音段结束: ${message.text}`, "info");
2059
+ } else if (message.state === "stop") {
2060
+ const event = new CustomEvent("stopVolic");
2061
+ window.dispatchEvent(event);
2062
+ console.log("服务器语音传输结束", "info");
2063
+ this.isRemoteSpeaking = false;
2064
+ if (this.onRecordButtonStateChange) {
2065
+ this.onRecordButtonStateChange(false);
2066
+ }
2067
+ if (this.onSessionStateChange) {
2068
+ this.onSessionStateChange(false);
2069
+ }
2070
+ }
2071
+ }
2072
+
2073
+ // 处理MCP消息
2074
+ handleMCPMessage(message) {
2075
+ const payload = message.payload || {};
2076
+ console.log(`服务器下发: ${JSON.stringify(message)}`, "info");
2077
+
2078
+ if (payload.method === "tools/list") {
2079
+ const tools = getMcpTools();
2080
+ const replyMessage = JSON.stringify({
2081
+ session_id: message.session_id || "",
2082
+ type: "mcp",
2083
+ payload: {
2084
+ jsonrpc: "2.0",
2085
+ id: payload.id,
2086
+ result: {
2087
+ tools: tools,
2088
+ },
2089
+ },
2090
+ });
2091
+ console.log(`客户端上报: ${replyMessage}`, "info");
2092
+ this.websocket.send(replyMessage);
2093
+ console.log(`回复MCP工具列表: ${tools.length} 个工具`, "info");
2094
+ } else if (payload.method === "tools/call") {
2095
+ const toolName = payload.params?.name;
2096
+ const toolArgs = payload.params?.arguments;
2097
+
2098
+ console.log(
2099
+ `调用工具: ${toolName} 参数: ${JSON.stringify(toolArgs)}`,
2100
+ "info"
2101
+ );
2102
+
2103
+ const result = executeMcpTool(toolName, toolArgs);
2104
+
2105
+ const replyMessage = JSON.stringify({
2106
+ session_id: message.session_id || "",
2107
+ type: "mcp",
2108
+ payload: {
2109
+ jsonrpc: "2.0",
2110
+ id: payload.id,
2111
+ result: {
2112
+ content: [
2113
+ {
2114
+ type: "text",
2115
+ text: JSON.stringify(result),
2116
+ },
2117
+ ],
2118
+ isError: false,
2119
+ },
2120
+ },
2121
+ });
2122
+
2123
+ console.log(`客户端上报: ${replyMessage}`, "info");
2124
+ this.websocket.send(replyMessage);
2125
+ } else if (payload.method === "initialize") {
2126
+ console.log(
2127
+ `收到工具初始化请求: ${JSON.stringify(payload.params)}`,
2128
+ "info"
2129
+ );
2130
+ } else {
2131
+ console.log(`未知的MCP方法: ${payload.method}`, "warning");
2132
+ }
2133
+ }
2134
+
2135
+ // 处理二进制消息
2136
+ async handleBinaryMessage(data) {
2137
+ try {
2138
+ let arrayBuffer;
2139
+ if (data instanceof ArrayBuffer) {
2140
+ arrayBuffer = data;
2141
+ } else if (data instanceof Blob) {
2142
+ arrayBuffer = await data.arrayBuffer();
2143
+ console.log(
2144
+ `收到Blob音频数据,大小: ${arrayBuffer.byteLength}字节`,
2145
+ "debug"
2146
+ );
2147
+ } else {
2148
+ console.log(`收到未知类型的二进制数据: ${typeof data}`, "warning");
2149
+ return;
2150
+ }
2151
+
2152
+ const opusData = new Uint8Array(arrayBuffer);
2153
+ const audioPlayer = player_getAudioPlayer();
2154
+ audioPlayer.enqueueAudioData(opusData);
2155
+ } catch (error) {
2156
+ console.log(`处理二进制消息出错: ${error.message}`, "error");
2157
+ }
2158
+ }
2159
+
2160
+ // 计算重连延迟(指数退避策略)
2161
+ calculateReconnectDelay() {
2162
+ // 指数退避 + 随机抖动,避免多个客户端同时重连
2163
+ const delay = Math.min(
2164
+ this.reconnectConfig.baseDelay *
2165
+ Math.pow(2, this.reconnectConfig.retryCount),
2166
+ this.reconnectConfig.maxDelay
2167
+ );
2168
+ // 添加±20%的随机抖动
2169
+ const jitter = delay * 0.2 * (Math.random() - 0.5);
2170
+ return Math.round(delay + jitter);
2171
+ }
2172
+
2173
+ // 触发自动重连
2174
+ triggerReconnect() {
2175
+ // 如果是手动断开连接,不进行重连
2176
+ if (this.reconnectConfig.manualDisconnect) {
2177
+ console.log("手动断开连接,不进行自动重连", "info");
2178
+ return;
2179
+ }
2180
+
2181
+ // 检查是否达到最大重连次数
2182
+ if (this.reconnectConfig.retryCount >= this.reconnectConfig.maxRetries) {
2183
+ console.log(
2184
+ `已达到最大重连次数(${this.reconnectConfig.maxRetries}),停止重连`,
2185
+ "error"
2186
+ );
2187
+ this.reconnectConfig.isReconnecting = false;
2188
+ if (this.onConnectionStateChange) {
2189
+ this.onConnectionStateChange(false);
2190
+ }
2191
+ return;
2192
+ }
2193
+
2194
+ // 计算重连延迟
2195
+ const delay = this.calculateReconnectDelay();
2196
+ this.reconnectConfig.retryCount++;
2197
+
2198
+ console.log(
2199
+ `准备进行第${this.reconnectConfig.retryCount}次重连,延迟${delay}ms`,
2200
+ "info"
2201
+ );
2202
+
2203
+ // 设置重连定时器
2204
+ this.reconnectConfig.reconnectTimer = setTimeout(async () => {
2205
+ console.log(`开始第${this.reconnectConfig.retryCount}次重连`, "info");
2206
+ try {
2207
+ const success = await this.connect();
2208
+ if (success) {
2209
+ console.log("重连成功", "success");
2210
+ this.reconnectConfig.isReconnecting = false;
2211
+ } else {
2212
+ console.log(
2213
+ `第${this.reconnectConfig.retryCount}次重连失败`,
2214
+ "error"
2215
+ );
2216
+ this.triggerReconnect();
2217
+ }
2218
+ } catch (error) {
2219
+ console.log(`重连出错: ${error.message}`, "error");
2220
+ this.triggerReconnect();
2221
+ }
2222
+ }, delay);
2223
+ }
2224
+
2225
+ // 停止自动重连
2226
+ stopReconnect() {
2227
+ if (this.reconnectConfig.reconnectTimer) {
2228
+ clearTimeout(this.reconnectConfig.reconnectTimer);
2229
+ this.reconnectConfig.reconnectTimer = null;
2230
+ }
2231
+ this.reconnectConfig.isReconnecting = false;
2232
+ this.reconnectConfig.retryCount = 0;
2233
+ }
2234
+
2235
+ // 连接WebSocket服务器
2236
+ async connect() {
2237
+ // 如果正在重连中,先停止之前的重连
2238
+ this.stopReconnect();
2239
+
2240
+ const config = getConfig();
2241
+ console.log("正在检查OTA状态...", "info");
2242
+ saveConnectionUrls();
2243
+
2244
+ try {
2245
+ const otaUrl = localStorage.getItem("xz_tester_otaUrl");
2246
+ const ws = await webSocketConnect(otaUrl, config);
2247
+ if (ws === undefined) {
2248
+ // 连接失败,触发重连
2249
+ if (
2250
+ !this.reconnectConfig.isReconnecting &&
2251
+ !this.reconnectConfig.manualDisconnect
2252
+ ) {
2253
+ this.reconnectConfig.isReconnecting = true;
2254
+ this.triggerReconnect();
2255
+ }
2256
+ return false;
2257
+ }
2258
+
2259
+ this.websocket = ws;
2260
+
2261
+ // 设置接收二进制数据的类型为ArrayBuffer
2262
+ this.websocket.binaryType = "arraybuffer";
2263
+
2264
+ // 设置 MCP 模块的 WebSocket 实例
2265
+ setWebSocket(this.websocket);
2266
+
2267
+ // 设置录音器的WebSocket
2268
+ const audioRecorder = recorder_getAudioRecorder();
2269
+ audioRecorder.setWebSocket(this.websocket);
2270
+
2271
+ this.setupEventHandlers();
2272
+
2273
+ return true;
2274
+ } catch (error) {
2275
+ console.log(`连接错误: ${error.message}`, "error");
2276
+ if (this.onConnectionStateChange) {
2277
+ this.onConnectionStateChange(false);
2278
+ }
2279
+
2280
+ // 连接出错,触发重连
2281
+ if (
2282
+ !this.reconnectConfig.isReconnecting &&
2283
+ !this.reconnectConfig.manualDisconnect
2284
+ ) {
2285
+ this.reconnectConfig.isReconnecting = true;
2286
+ this.triggerReconnect();
2287
+ }
2288
+
2289
+ return false;
2290
+ }
2291
+ }
2292
+
2293
+ // 设置事件处理器
2294
+ setupEventHandlers() {
2295
+ this.websocket.onopen = async () => {
2296
+ const url = localStorage.getItem("xz_tester_wsUrl");
2297
+ console.log(`已连接到服务器: ${url}`, "success");
2298
+
2299
+ if (this.onConnectionStateChange) {
2300
+ this.onConnectionStateChange(true);
2301
+ }
2302
+
2303
+ // 连接成功后,默认状态为聆听中
2304
+ this.isRemoteSpeaking = false;
2305
+ if (this.onSessionStateChange) {
2306
+ this.onSessionStateChange(false);
2307
+ }
2308
+
2309
+ await this.sendHelloMessage();
2310
+ };
2311
+
2312
+ this.websocket.onclose = (event) => {
2313
+ console.log(
2314
+ `已断开连接,代码: ${event.code}, 原因: ${event.reason}`,
2315
+ "info"
2316
+ );
2317
+ this.stopHeartbeat();
2318
+
2319
+ // 清除MCP的WebSocket引用
2320
+ setWebSocket(null);
2321
+
2322
+ // 停止录音
2323
+ const audioRecorder = recorder_getAudioRecorder();
2324
+ audioRecorder.stop();
2325
+
2326
+ if (this.onConnectionStateChange) {
2327
+ this.onConnectionStateChange(false);
2328
+ }
2329
+
2330
+ // 如果不是手动断开连接,触发自动重连
2331
+ if (
2332
+ !this.reconnectConfig.manualDisconnect &&
2333
+ !this.reconnectConfig.isReconnecting
2334
+ ) {
2335
+ // 1000: 正常关闭, 1001: 客户端离开, 这两种情况不自动重连
2336
+ if (event.code !== 1000 && event.code !== 1001) {
2337
+ console.log("检测到异常断开连接,准备自动重连", "warning");
2338
+ this.reconnectConfig.isReconnecting = true;
2339
+ this.triggerReconnect();
2340
+ }
2341
+ }
2342
+
2343
+ // 重置手动断开标记(以便下次可以重连)
2344
+ this.reconnectConfig.manualDisconnect = false;
2345
+ };
2346
+
2347
+ this.websocket.onerror = (error) => {
2348
+ console.log(`WebSocket错误: ${error.message || "未知错误"}`, "error");
2349
+
2350
+ if (this.onConnectionStateChange) {
2351
+ this.onConnectionStateChange(false);
2352
+ }
2353
+ };
2354
+
2355
+ this.websocket.onmessage = (event) => {
2356
+ try {
2357
+ if (typeof event.data === "string") {
2358
+ const message = JSON.parse(event.data);
2359
+ this.handleTextMessage(message);
2360
+ } else {
2361
+ this.handleBinaryMessage(event.data);
2362
+ }
2363
+ } catch (error) {
2364
+ console.log(`WebSocket消息处理错误: ${error.message}`, "error");
2365
+ }
2366
+ };
2367
+ }
2368
+
2369
+ // 断开连接
2370
+ disconnect() {
2371
+ // 标记为手动断开连接
2372
+ this.reconnectConfig.manualDisconnect = true;
2373
+ this.stopReconnect();
2374
+
2375
+ if (!this.websocket) return;
2376
+
2377
+ // 正常关闭连接
2378
+ this.websocket.close(1000, "Manual disconnect");
2379
+ const audioRecorder = recorder_getAudioRecorder();
2380
+ audioRecorder.stop();
2381
+ }
2382
+
2383
+ // 发送文本消息
2384
+ sendTextMessage(text) {
2385
+ try {
2386
+ // 如果对方正在说话,先发送打断消息
2387
+ const abortMessage = {
2388
+ session_id: this.currentSessionId,
2389
+ type: "abort",
2390
+ reason: "wake_word_detected",
2391
+ };
2392
+ this.websocket.send(JSON.stringify(abortMessage));
2393
+ console.log("发送打断消息", "info");
2394
+
2395
+ const listenMessage = {
2396
+ type: "listen",
2397
+ mode: localStorage.getItem("listenMode") || "wakeup",
2398
+ state: "detect",
2399
+ text: text,
2400
+ };
2401
+
2402
+ this.websocket.send(JSON.stringify(listenMessage));
2403
+ console.log(`发送文本消息: ${text}`, "info6666");
2404
+
2405
+ return true;
2406
+ } catch (error) {
2407
+ console.log(`发送消息错误: ${error.message}`, "error");
2408
+ return false;
2409
+ }
2410
+ }
2411
+
2412
+ // 获取WebSocket实例
2413
+ getWebSocket() {
2414
+ return this.websocket;
2415
+ }
2416
+
2417
+ // 检查是否已连接
2418
+ isConnected() {
2419
+ return this.websocket && this.websocket.readyState === WebSocket.OPEN;
2420
+ }
2421
+
2422
+ // 获取重连状态
2423
+ getReconnectStatus() {
2424
+ return {
2425
+ isReconnecting: this.reconnectConfig.isReconnecting,
2426
+ retryCount: this.reconnectConfig.retryCount,
2427
+ maxRetries: this.reconnectConfig.maxRetries,
2428
+ };
2429
+ }
2430
+
2431
+ // 重置重连配置
2432
+ resetReconnectConfig() {
2433
+ this.stopReconnect();
2434
+ this.reconnectConfig.retryCount = 0;
2435
+ this.reconnectConfig.isReconnecting = false;
2436
+ this.reconnectConfig.manualDisconnect = false;
2437
+ }
2438
+ }
2439
+
2440
+ // 创建单例
2441
+ let wsHandlerInstance = null;
2442
+
2443
+ function getWebSocketHandler() {
2444
+ if (!wsHandlerInstance) {
2445
+ wsHandlerInstance = new WebSocketHandler();
2446
+ }
2447
+ return wsHandlerInstance;
2448
+ }
2449
+
2450
+ ;// CONCATENATED MODULE: ./src/utils/controller.js
2451
+ // UI控制模块
2452
+
2453
+
2454
+
2455
+ // ws连接事件监听
2456
+ async function wsConnectEventListeners() {
2457
+ const wsHandler = getWebSocketHandler();
2458
+ await wsHandler.connect();
2459
+ }
2460
+
2461
+ // ws关闭事件监听
2462
+ async function wsCloseEventListeners() {
2463
+ const wsHandler = getWebSocketHandler();
2464
+ await wsHandler.disconnect();
2465
+ }
2466
+
2467
+ // 语音播放
2468
+ async function startEventAudioPlayer() {
2469
+ const audioRecorder = recorder_getAudioRecorder();
2470
+ await audioRecorder.start();
2471
+ }
2472
+
2473
+ // 语音暂停
2474
+ async function stopEventAudioPlayer() {
2475
+ const audioRecorder = getAudioRecorder();
2476
+ await audioRecorder.stop();
2477
+ }
2478
+
2479
+
2480
+ // 判断ws是否连接
2481
+ function isWsConnected() {
2482
+ const wsHandler = getWebSocketHandler();
2483
+ return wsHandler.isConnected();
2484
+ }
2485
+ ;// CONCATENATED MODULE: ./node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./src/index.vue?vue&type=script&lang=js
2486
+
2487
+
2488
+
2489
+
2490
+ /* harmony default export */ const lib_vue_loader_options_srcvue_type_script_lang_js = ({
2491
+ name: "ibiAiTalk",
2492
+ data() {
2493
+ return {
2494
+ audioPlayer: null,
2495
+ };
2496
+ },
2497
+ props: {
2498
+ listenMode: {
2499
+ type: String,
2500
+ default: "wakeup",
2501
+ },
2502
+ otaUrl: {
2503
+ type: String,
2504
+ default: "",
2505
+ },
2506
+ macAddress: {
2507
+ type: String,
2508
+ default: "",
2509
+ },
2510
+ },
2511
+ async mounted() {
2512
+ localStorage.setItem("MAC", this.macAddress);
2513
+ localStorage.setItem("otaUrl", this.otaUrl);
2514
+ localStorage.setItem("listenMode", this.listenMode);
2515
+ checkOpusLoaded();
2516
+ initOpusEncoder();
2517
+ initMcpTools();
2518
+ if (!isWsConnected()) {
2519
+ await wsConnectEventListeners();
2520
+ }
2521
+ this.audioPlayer = getAudioPlayer();
2522
+ await this.audioPlayer.start();
2523
+ await startEventAudioPlayer();
2524
+ },
2525
+ beforeDestroy() {
2526
+ this.audioPlayer.stop().catch(() => {});
2527
+ // 关闭WebSocket连接
2528
+ if (isWsConnected()) {
2529
+ wsCloseEventListeners().catch(() => {});
2530
+ }
2531
+ },
2532
+ });
2533
+
2534
+ ;// CONCATENATED MODULE: ./src/index.vue?vue&type=script&lang=js
2535
+ /* harmony default export */ const srcvue_type_script_lang_js = (lib_vue_loader_options_srcvue_type_script_lang_js);
2536
+ ;// CONCATENATED MODULE: ./node_modules/@vue/vue-loader-v15/lib/runtime/componentNormalizer.js
2537
+ /* globals __VUE_SSR_CONTEXT__ */
2538
+
2539
+ // IMPORTANT: Do NOT use ES2015 features in this file (except for modules).
2540
+ // This module is a runtime utility for cleaner component module output and will
2541
+ // be included in the final webpack user bundle.
2542
+
2543
+ function normalizeComponent(
2544
+ scriptExports,
2545
+ render,
2546
+ staticRenderFns,
2547
+ functionalTemplate,
2548
+ injectStyles,
2549
+ scopeId,
2550
+ moduleIdentifier /* server only */,
2551
+ shadowMode /* vue-cli only */
2552
+ ) {
2553
+ // Vue.extend constructor export interop
2554
+ var options =
2555
+ typeof scriptExports === 'function' ? scriptExports.options : scriptExports
2556
+
2557
+ // render functions
2558
+ if (render) {
2559
+ options.render = render
2560
+ options.staticRenderFns = staticRenderFns
2561
+ options._compiled = true
2562
+ }
2563
+
2564
+ // functional template
2565
+ if (functionalTemplate) {
2566
+ options.functional = true
2567
+ }
2568
+
2569
+ // scopedId
2570
+ if (scopeId) {
2571
+ options._scopeId = 'data-v-' + scopeId
2572
+ }
2573
+
2574
+ var hook
2575
+ if (moduleIdentifier) {
2576
+ // server build
2577
+ hook = function (context) {
2578
+ // 2.3 injection
2579
+ context =
2580
+ context || // cached call
2581
+ (this.$vnode && this.$vnode.ssrContext) || // stateful
2582
+ (this.parent && this.parent.$vnode && this.parent.$vnode.ssrContext) // functional
2583
+ // 2.2 with runInNewContext: true
2584
+ if (!context && typeof __VUE_SSR_CONTEXT__ !== 'undefined') {
2585
+ context = __VUE_SSR_CONTEXT__
2586
+ }
2587
+ // inject component styles
2588
+ if (injectStyles) {
2589
+ injectStyles.call(this, context)
2590
+ }
2591
+ // register component module identifier for async chunk inferrence
2592
+ if (context && context._registeredComponents) {
2593
+ context._registeredComponents.add(moduleIdentifier)
2594
+ }
2595
+ }
2596
+ // used by ssr in case component is cached and beforeCreate
2597
+ // never gets called
2598
+ options._ssrRegister = hook
2599
+ } else if (injectStyles) {
2600
+ hook = shadowMode
2601
+ ? function () {
2602
+ injectStyles.call(
2603
+ this,
2604
+ (options.functional ? this.parent : this).$root.$options.shadowRoot
2605
+ )
2606
+ }
2607
+ : injectStyles
2608
+ }
2609
+
2610
+ if (hook) {
2611
+ if (options.functional) {
2612
+ // for template-only hot-reload because in that case the render fn doesn't
2613
+ // go through the normalizer
2614
+ options._injectStyles = hook
2615
+ // register for functional component in vue file
2616
+ var originalRender = options.render
2617
+ options.render = function renderWithStyleInjection(h, context) {
2618
+ hook.call(context)
2619
+ return originalRender(h, context)
2620
+ }
2621
+ } else {
2622
+ // inject component registration as beforeCreate hook
2623
+ var existing = options.beforeCreate
2624
+ options.beforeCreate = existing ? [].concat(existing, hook) : [hook]
2625
+ }
2626
+ }
2627
+
2628
+ return {
2629
+ exports: scriptExports,
2630
+ options: options
2631
+ }
2632
+ }
2633
+
2634
+ ;// CONCATENATED MODULE: ./src/index.vue
2635
+
2636
+
2637
+
2638
+
2639
+
2640
+ /* normalize component */
2641
+ ;
2642
+ var component = normalizeComponent(
2643
+ srcvue_type_script_lang_js,
2644
+ render,
2645
+ staticRenderFns,
2646
+ false,
2647
+ null,
2648
+ null,
2649
+ null
2650
+
2651
+ )
2652
+
2653
+ /* harmony default export */ const src_0 = (component.exports);
2654
+ ;// CONCATENATED MODULE: ./node_modules/@vue/cli-service/lib/commands/build/entry-lib.js
2655
+
2656
+
2657
+ /* harmony default export */ const entry_lib = (src_0);
2658
+
2659
+
2660
+ __webpack_exports__ = __webpack_exports__["default"];
2661
+ /******/ return __webpack_exports__;
2662
+ /******/ })()
2663
+ ;
2664
+ });
2665
+ //# sourceMappingURL=index.umd.js.map