@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.
- package/assets/myclaw-inject.js +20 -20
- package/package.json +1 -1
- package/patch-manifest.json +1 -1
- package/patch.js +19 -0
- package/voice-output/voice-output.js +24 -0
package/assets/myclaw-inject.js
CHANGED
|
@@ -414,7 +414,7 @@
|
|
|
414
414
|
APPID: 'de6ec59e',
|
|
415
415
|
APISecret: 'ZTA3ZDU3YjAyNDUyZGVkNjQwMGM5MWNi',
|
|
416
416
|
APIKey: '37104a0463a5460d7571869324b667d5',
|
|
417
|
-
vcn: '
|
|
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
|
|
439
|
-
if (!
|
|
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
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
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
|
-
|
|
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
package/patch-manifest.json
CHANGED
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
|
// 停止之前的播放
|