@aiyiran/myclaw 1.0.102 → 1.0.104

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.
@@ -414,7 +414,7 @@
414
414
  APPID: 'de6ec59e',
415
415
  APISecret: 'ZTA3ZDU3YjAyNDUyZGVkNjQwMGM5MWNi',
416
416
  APIKey: '37104a0463a5460d7571869324b667d5',
417
- vcn: 'aisbabyxu',
417
+ vcn: 'x4_yezi', // 小露 - 普通话女声
418
418
  onAudio: function (audioBuffer) {
419
419
  console.log('[myclaw-tts] audio chunk:', audioBuffer.byteLength, 'bytes');
420
420
  },
@@ -431,29 +431,29 @@
431
431
 
432
432
  /**
433
433
  * 从 .chat-tts-btn 按钮获取对应的消息文字
434
- * 按钮通常在消息气泡下方,需要向上查找
434
+ * DOM 结构:
435
+ * .chat-group-messages
436
+ * .chat-bubble
437
+ * .chat-text > p (消息文字)
438
+ * .chat-group-footer
439
+ * .chat-tts-btn
435
440
  */
436
441
  function extractMessageText(ttsBtn) {
437
- // 尝试找到包含此按钮的消息气泡
438
- var bubble = ttsBtn.closest('.chat-message__bubble');
439
- if (!bubble) {
440
- // 尝试上一级元素的兄弟元素
441
- var parent = ttsBtn.parentElement;
442
- if (parent) {
443
- var prevSibling = parent.previousElementSibling;
444
- if (prevSibling) {
445
- bubble = prevSibling.querySelector('.chat-message__bubble');
446
- }
447
- }
448
- }
442
+ // 向上找到 .chat-group-messages
443
+ var group = ttsBtn.closest('.chat-group-messages');
444
+ if (!group) return '';
449
445
 
450
- if (bubble) {
451
- // 获取气泡内的文字,移除多余空白
452
- var text = bubble.textContent || '';
453
- return text.replace(/\s+/g, ' ').trim();
454
- }
446
+ // 在 group 内找 .chat-bubble
447
+ var bubble = group.querySelector('.chat-bubble');
448
+ if (!bubble) return '';
449
+
450
+ // 在气泡内找 .chat-text 的文字
451
+ var chatText = bubble.querySelector('.chat-text');
452
+ if (!chatText) return '';
455
453
 
456
- return '';
454
+ // 获取文字,移除多余空白
455
+ var text = chatText.textContent || '';
456
+ return text.replace(/\s+/g, ' ').trim();
457
457
  }
458
458
 
459
459
  function hookTtsButton() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiyiran/myclaw",
3
- "version": "1.0.102",
3
+ "version": "1.0.104",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "_doc": "MyClaw 注入清单 (auto-generated)。strategy: auto | on | off",
3
- "_generated": "2026-04-01T18:02:28.628Z",
3
+ "_generated": "2026-04-01T18:15:05.980Z",
4
4
  "agents": [
5
5
  {
6
6
  "id": "danci",
package/patch.js CHANGED
@@ -24,6 +24,7 @@ const path = require('path');
24
24
  const INJECT_MARKER = '<!-- myclaw-inject -->';
25
25
  const INJECT_FILENAME = 'myclaw-inject.js';
26
26
  const VOICE_SDK_FILENAME = 'voice-input.js';
27
+ const VOICE_OUTPUT_SDK_FILENAME = 'voice-output.js';
27
28
  const BACKUP_SUFFIX = '.myclaw-backup';
28
29
 
29
30
  /**
@@ -109,6 +110,8 @@ function patch() {
109
110
  const injectDest = path.join(uiDir, INJECT_FILENAME);
110
111
  const voiceSdkSrc = path.join(__dirname, 'voice-input', VOICE_SDK_FILENAME);
111
112
  const voiceSdkDest = path.join(uiDir, VOICE_SDK_FILENAME);
113
+ const voiceOutputSdkSrc = path.join(__dirname, 'voice-output', VOICE_OUTPUT_SDK_FILENAME);
114
+ const voiceOutputSdkDest = path.join(uiDir, VOICE_OUTPUT_SDK_FILENAME);
112
115
  const version = getMyclawVersion();
113
116
 
114
117
  console.log('[myclaw-patch] 📍 找到 control-ui: ' + uiDir);
@@ -145,6 +148,14 @@ function patch() {
145
148
  // 非致命错误,继续执行
146
149
  }
147
150
 
151
+ // 4. 复制 VoiceOutput SDK(TTS)
152
+ try {
153
+ fs.copyFileSync(voiceOutputSdkSrc, voiceOutputSdkDest);
154
+ console.log('[myclaw-patch] 📄 TTS SDK 已复制: ' + voiceOutputSdkDest);
155
+ } catch (err) {
156
+ console.error('[myclaw-patch] ⚠️ TTS SDK 复制失败 (非致命): ' + err.message);
157
+ }
158
+
148
159
  // 4. Patch index.html(幂等)
149
160
  try {
150
161
  let html = fs.readFileSync(indexPath, 'utf8');
@@ -159,6 +170,7 @@ function patch() {
159
170
  // 在 </body> 前注入(SDK 先加载,inject 后加载)
160
171
  const injection = [
161
172
  INJECT_MARKER,
173
+ '<script src="./' + VOICE_OUTPUT_SDK_FILENAME + '"></script>',
162
174
  '<script src="./' + VOICE_SDK_FILENAME + '"></script>',
163
175
  '<script src="./' + INJECT_FILENAME + '"></script>',
164
176
  '',
@@ -236,6 +248,7 @@ function unpatch() {
236
248
  const backupPath = indexPath + BACKUP_SUFFIX;
237
249
  const injectDest = path.join(uiDir, INJECT_FILENAME);
238
250
  const voiceSdkDest = path.join(uiDir, VOICE_SDK_FILENAME);
251
+ const voiceOutputSdkDest = path.join(uiDir, VOICE_OUTPUT_SDK_FILENAME);
239
252
 
240
253
  // 恢复备份
241
254
  if (fs.existsSync(backupPath)) {
@@ -256,6 +269,12 @@ function unpatch() {
256
269
  console.log('[myclaw-patch] ✅ 语音 SDK 已删除');
257
270
  }
258
271
 
272
+ // 删除 TTS SDK
273
+ if (fs.existsSync(voiceOutputSdkDest)) {
274
+ fs.unlinkSync(voiceOutputSdkDest);
275
+ console.log('[myclaw-patch] ✅ TTS SDK 已删除');
276
+ }
277
+
259
278
  // 恢复 gateway-cli-*.js 的 Permissions-Policy
260
279
  try {
261
280
  const distParent = path.resolve(uiDir, '..');
@@ -107,9 +107,33 @@
107
107
  VoiceOutput.prototype._playAudio = function (audioBuffer) {
108
108
  var self = this;
109
109
 
110
+ // 尝试 Web Audio API(可以解码 MP3)
111
+ try {
112
+ this._initAudioContext();
113
+ this.audioContext.decodeAudioData(audioBuffer.slice(0), function (decodedBuffer) {
114
+ console.log('[tts] decoded audio:', decodedBuffer.duration, 's,', decodedBuffer.numberOfChannels, 'ch');
115
+ self._playBuffer(decodedBuffer);
116
+ }, function (err) {
117
+ console.error('[tts] decode error, trying blob URL:', err);
118
+ // fallback: 用 Blob URL 方式
119
+ self._playAudioBlob(audioBuffer);
120
+ });
121
+ return;
122
+ } catch (e) {
123
+ console.error('[tts] Web Audio API error:', e);
124
+ // fallback: 用 Blob URL 方式
125
+ }
126
+
127
+ this._playAudioBlob(audioBuffer);
128
+ };
129
+
130
+ VoiceOutput.prototype._playAudioBlob = function (audioBuffer) {
131
+ var self = this;
132
+
110
133
  // 创建 Blob URL 用于 <audio> 元素播放
111
134
  var mimeType = this.aue === 'lame' ? 'audio/mpeg' : 'audio/wav';
112
135
  var blob = new Blob([audioBuffer], { type: mimeType });
136
+ console.log('[tts] blob size:', blob.size, 'type:', mimeType);
113
137
  var blobUrl = URL.createObjectURL(blob);
114
138
 
115
139
  // 停止之前的播放