@aiyiran/myclaw 1.0.98 → 1.0.99

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.
@@ -26,7 +26,7 @@
26
26
  var cursorOffset = 0; // 录音开始时光标在 textarea 中的位置
27
27
  var injected = false;
28
28
 
29
- // ═══ 1. 顶部版本号横条 ═══
29
+ // ═══ 1. 右下角版本标签(点击测试麦克风) ═══
30
30
  function createVersionBar() {
31
31
  if (document.querySelector("#myclaw-version-bar")) return;
32
32
 
@@ -34,36 +34,29 @@
34
34
  bar.id = "myclaw-version-bar";
35
35
  bar.style.cssText = [
36
36
  "position: fixed",
37
- "top: 0",
38
- "left: 0",
39
- "right: 0",
40
- "height: 28px",
41
- "line-height: 28px",
42
- "background: linear-gradient(90deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%)",
43
- "color: #e94560",
44
- "font-size: 12px",
37
+ "bottom: 8px",
38
+ "right: 8px",
39
+ "padding: 2px 8px",
40
+ "background: none",
41
+ "color: rgba(150, 150, 150, 0.4)",
42
+ "font-size: 10px",
45
43
  "font-family: monospace",
46
- "text-align: center",
47
44
  "z-index: 99999",
48
- "box-shadow: 0 1px 4px rgba(0,0,0,0.3)",
49
45
  "user-select: none",
50
- "letter-spacing: 0.5px",
46
+ "cursor: pointer",
47
+ "transition: color 0.2s",
51
48
  ].join(";");
52
- bar.textContent = "\uD83D\uDC3E MyClaw v" + MYCLAW_VERSION;
49
+ bar.textContent = "\uD83D\uDC3E v" + MYCLAW_VERSION;
50
+ bar.title = "\u70B9\u51FB\u6D4B\u8BD5\u9EA6\u514B\u98CE";
53
51
 
54
- // 测试麦克风按钮
55
- var testBtn = document.createElement("button");
56
- testBtn.textContent = "\uD83D\uDD0A \u6D4B\u8BD5\u9EA6\u514B\u98CE";
57
- testBtn.style.cssText = "background:#e94560;color:white;border:none;padding:2px 8px;border-radius:4px;cursor:pointer;font-size:11px;font-family:monospace;";
58
- testBtn.onclick = function(e) {
52
+ bar.onmouseenter = function () { bar.style.color = "rgba(150, 150, 150, 0.8)"; };
53
+ bar.onmouseleave = function () { bar.style.color = "rgba(150, 150, 150, 0.4)"; };
54
+ bar.onclick = function (e) {
59
55
  e.stopPropagation();
60
- console.log("[myclaw] \u6D4B\u8BD5\u9EA6\u514B\u98CE\u70B9\u51FB");
61
56
  testMicrophone();
62
57
  };
63
- bar.appendChild(testBtn);
64
58
 
65
- document.body.prepend(bar);
66
- document.body.style.paddingTop = "28px";
59
+ document.body.appendChild(bar);
67
60
  }
68
61
 
69
62
  // \u6D4B\u8BD5\u9EA6\u514B\u98CE\u51FD\u6570
@@ -183,6 +176,16 @@
183
176
  btn.classList.remove("agent-chat__input-btn--recording");
184
177
  btn.title = "\u8baf\u98de\u8bed\u97f3";
185
178
  }
179
+
180
+ // 同步 textarea 边框状态
181
+ var ta = document.querySelector(".agent-chat__input textarea");
182
+ if (ta) {
183
+ if (recording) {
184
+ ta.classList.add("myclaw-voice-active");
185
+ } else {
186
+ ta.classList.remove("myclaw-voice-active");
187
+ }
188
+ }
186
189
  }
187
190
 
188
191
  // ═══ 4. 录音控制 ═══
@@ -195,6 +198,8 @@
195
198
 
196
199
  voice = new window.VoiceInput({
197
200
  onResult: function (text) {
201
+ // 如果用户已点停止,忽略异步返回的残留结果(防止文字重复)
202
+ if (!recording) return;
198
203
  // 讯飞实时返回识别文字,替换到光标位置
199
204
  pendingText = text;
200
205
  updateTextAtCursor(pendingText);
@@ -262,15 +267,19 @@
262
267
  }
263
268
 
264
269
  function stopVoice() {
270
+ // 先关标志位,阻止 onResult 异步回调继续写入(核心防重复)
265
271
  recording = false;
266
272
  updateButtonUI();
267
273
 
274
+ // 立即快照当前 textarea 值作为最终文字
275
+ var finalText = getTextareaValue();
276
+
268
277
  if (voice) {
269
278
  voice.stop();
270
279
  }
271
280
 
272
- // 最终提交文字
273
- committedText = getTextareaValue();
281
+ // 用快照覆盖,确保后续异步返回不影响
282
+ committedText = finalText;
274
283
  pendingText = "";
275
284
 
276
285
  console.log("[myclaw-voice] \u505c\u6b62\u5f55\u97f3");
@@ -284,10 +293,9 @@
284
293
 
285
294
  var btn = createVoiceButton();
286
295
 
287
- // 插入到 token 计数之前,或追加到末尾
288
- var tokenCount = toolbar.querySelector(".agent-chat__token-count");
289
- if (tokenCount) {
290
- toolbar.insertBefore(btn, tokenCount);
296
+ // 插入到工具栏最前面(排在 Attach file 等按钮之前)
297
+ if (toolbar.firstChild) {
298
+ toolbar.insertBefore(btn, toolbar.firstChild);
291
299
  } else {
292
300
  toolbar.appendChild(btn);
293
301
  }
@@ -296,9 +304,113 @@
296
304
  console.log("[myclaw-inject] \u2705 \u8bed\u97f3\u6309\u94ae\u5df2\u6ce8\u5165");
297
305
  }
298
306
 
307
+ // ═══ 注入录音态样式 ═══
308
+ function injectStyles() {
309
+ if (document.querySelector("#myclaw-voice-styles")) return;
310
+ var style = document.createElement("style");
311
+ style.id = "myclaw-voice-styles";
312
+ style.textContent = [
313
+ /* 隐藏原生 Voice input 按钮(避免与 MyClaw 语音按钮混淆) */
314
+ "button[title=\"Voice input\"]:not(#myclaw-voice-btn) { display: none !important; }",
315
+ /* 按钮录音态:红色脉冲 */
316
+ ".agent-chat__input-btn--recording {",
317
+ " color: #ff4444 !important;",
318
+ " animation: myclaw-pulse 1s ease-in-out infinite !important;",
319
+ " position: relative;",
320
+ "}",
321
+ ".agent-chat__input-btn--recording::after {",
322
+ " content: '';",
323
+ " position: absolute;",
324
+ " top: -2px; left: -2px; right: -2px; bottom: -2px;",
325
+ " border-radius: 50%;",
326
+ " border: 2px solid #ff4444;",
327
+ " animation: myclaw-ring 1.5s ease-out infinite;",
328
+ " pointer-events: none;",
329
+ "}",
330
+ /* textarea 录音态:左侧红色竖线 + 微弱红色背景 */
331
+ ".myclaw-voice-active {",
332
+ " border-left: 3px solid #ff4444 !important;",
333
+ " background: rgba(255, 68, 68, 0.03) !important;",
334
+ " transition: all 0.2s ease;",
335
+ "}",
336
+ /* 脉冲动画 */
337
+ "@keyframes myclaw-pulse {",
338
+ " 0%, 100% { opacity: 1; transform: scale(1); }",
339
+ " 50% { opacity: 0.6; transform: scale(1.1); }",
340
+ "}",
341
+ /* 扩散环动画 */
342
+ "@keyframes myclaw-ring {",
343
+ " 0% { transform: scale(0.8); opacity: 0.8; }",
344
+ " 100% { transform: scale(1.6); opacity: 0; }",
345
+ "}",
346
+ ].join("\n");
347
+ document.head.appendChild(style);
348
+ }
349
+ // ═══ 6. 拦截发送按钮 ═══
350
+
351
+ var sendHooked = false;
352
+
353
+ function hookSendButton() {
354
+ if (sendHooked) return;
355
+ sendHooked = true;
356
+
357
+ // 用捕获阶段拦截,确保在原生 handler 之前执行
358
+ document.addEventListener("click", function (e) {
359
+ var btn = e.target.closest("button.chat-send-btn, button[title=\"Send\"]");
360
+ if (!btn) return;
361
+
362
+ var text = getTextareaValue();
363
+ if (!text || !text.trim()) return; // 空文字不处理
364
+
365
+ // 1) 停止语音输入
366
+ if (recording) {
367
+ stopVoice();
368
+ }
369
+
370
+ // 2) 复制到剪贴板
371
+ try {
372
+ navigator.clipboard.writeText(text).then(function () {
373
+ console.log("[myclaw-send] 📋 已复制到剪贴板:", text.substring(0, 50) + (text.length > 50 ? "..." : ""));
374
+ }).catch(function () {
375
+ // fallback: 老方法
376
+ fallbackCopy(text);
377
+ });
378
+ } catch (ex) {
379
+ fallbackCopy(text);
380
+ }
381
+
382
+ // 3) 让原生 click 继续走(发送消息)
383
+ // 不 preventDefault,不 stopPropagation
384
+
385
+ // 4) 延迟清空 textarea(等原生 handler 读完值后再清)
386
+ setTimeout(function () {
387
+ setTextareaValue("");
388
+ committedText = "";
389
+ pendingText = "";
390
+ cursorOffset = 0;
391
+ console.log("[myclaw-send] ✅ 发送完成,输入框已清空");
392
+ }, 100);
393
+
394
+ }, true); // true = 捕获阶段
395
+
396
+ console.log("[myclaw-inject] ✅ 发送按钮拦截器已挂载");
397
+ }
398
+
399
+ function fallbackCopy(text) {
400
+ var ta = document.createElement("textarea");
401
+ ta.value = text;
402
+ ta.style.cssText = "position:fixed;left:-9999px;";
403
+ document.body.appendChild(ta);
404
+ ta.select();
405
+ try { document.execCommand("copy"); } catch (e) {}
406
+ document.body.removeChild(ta);
407
+ console.log("[myclaw-send] 📋 已复制(fallback)");
408
+ }
409
+
299
410
  // ═══ 启动 ═══
300
411
  function init() {
301
412
  createVersionBar();
413
+ injectStyles();
302
414
 
303
415
  // 初始化 VoiceInput SDK
304
416
  initVoice();
@@ -315,6 +427,9 @@
315
427
 
316
428
  // 尝试立即注入
317
429
  injectButton();
430
+
431
+ // 挂载发送拦截
432
+ hookSendButton();
318
433
  }
319
434
 
320
435
  if (document.readyState === "loading") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiyiran/myclaw",
3
- "version": "1.0.98",
3
+ "version": "1.0.99",
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-01T15:56:26.709Z",
3
+ "_generated": "2026-04-01T16:54:33.165Z",
4
4
  "agents": [
5
5
  {
6
6
  "id": "danci",
package/start.js CHANGED
@@ -78,12 +78,19 @@ function startWindows() {
78
78
  }
79
79
  console.log('');
80
80
 
81
- if (!gatewayRunning) {
82
- // Gateway 未运行: 先升级,再在新窗口启动
83
- console.log('[update] 检查 MyClaw 更新...');
84
- try { execSync('myclaw update', { stdio: 'inherit', timeout: 120000 }); } catch {}
85
- console.log('');
81
+ // 无论 Gateway 是否已运行,都先检查更新 + patch
82
+ console.log('[update] 检查 MyClaw 更新...');
83
+ try { execSync('myclaw update', { stdio: 'inherit', timeout: 120000 }); } catch {}
84
+ console.log('');
85
+
86
+ console.log('[patch] 刷新 UI 插件...');
87
+ try {
88
+ execSync('wsl -d OpenClaw -- myclaw patch', { stdio: 'inherit', timeout: 30000 });
89
+ } catch {}
90
+ console.log('');
86
91
 
92
+ if (!gatewayRunning) {
93
+ // Gateway 未运行: 在新窗口启动(launch 内部会再 patch 一次,幂等无害)
87
94
  console.log('[launch] 在新窗口启动 Gateway...');
88
95
  try {
89
96
  execSync('start "OpenClaw Gateway" wsl -d OpenClaw -- myclaw launch', {
@@ -94,6 +101,15 @@ function startWindows() {
94
101
  } catch (err) {
95
102
  console.error(' ' + C.r + '[失败]' + C.nc + ' ' + err.message);
96
103
  }
104
+ } else {
105
+ // Gateway 已在运行,重启使 patch 生效
106
+ console.log('[restart] 重启 Gateway 使插件生效...');
107
+ try {
108
+ execSync('wsl -d OpenClaw -- openclaw gateway restart', { stdio: 'pipe', timeout: 15000 });
109
+ console.log(' ' + C.g + '[OK]' + C.nc);
110
+ } catch {
111
+ console.log(' ' + C.y + '[跳过]' + C.nc + ' 重启失败,手动 myclaw restart');
112
+ }
97
113
  }
98
114
 
99
115
  // Step 4: 打开浏览器