myagent-ai 1.7.1 → 1.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myagent-ai",
3
- "version": "1.7.1",
3
+ "version": "1.7.2",
4
4
  "description": "本地桌面端执行型AI助手 - Open Interpreter 风格 | Local Desktop Execution-Oriented AI Assistant",
5
5
  "main": "main.py",
6
6
  "bin": {
@@ -65,4 +65,4 @@
65
65
  "departments/",
66
66
  "web/"
67
67
  ]
68
- }
68
+ }
@@ -2582,6 +2582,7 @@ function insertQuick(text) {
2582
2582
  // ══════════════════════════════════════════════════════
2583
2583
  // ── TTS (Text-to-Speech) Manager ──
2584
2584
  // ══════════════════════════════════════════════════════
2585
+ // 支持分段流式播放:文本边生成边朗读,遇到句子边界立即合成播放
2585
2586
 
2586
2587
  // Simple hash function for text caching
2587
2588
  function simpleHash(str) {
@@ -2603,6 +2604,13 @@ const ttsManager = {
2603
2604
  cache: new Map(), // textHash -> blobUrl
2604
2605
  voice: 'zh-CN-XiaoxiaoNeural',
2605
2606
  speed: '+0%',
2607
+ // ── 分段流式状态 ──
2608
+ _streamActive: false, // 是否正在流式模式
2609
+ _streamBuffer: '', // 当前缓冲区(积累到句子边界前)
2610
+ _audioQueue: [], // 待播放的音频 blobUrl 队列
2611
+ _audioPlaying: false, // 队列是否正在播放
2612
+ _stopRequested: false, // 是否已请求停止
2613
+ _streamMsgIndex: -1, // 流式模式对应的消息索引
2606
2614
 
2607
2615
  init() {
2608
2616
  // Load TTS enabled state from localStorage
@@ -2613,15 +2621,25 @@ const ttsManager = {
2613
2621
  this.updateButtonUI();
2614
2622
  // Audio event handlers
2615
2623
  this.audio.addEventListener('ended', () => {
2616
- this.isPlaying = false;
2617
- this.currentMsgIndex = -1;
2618
- this.updatePlayingIndicator();
2624
+ if (this._streamActive) {
2625
+ // 流式模式:播放队列下一段
2626
+ this._playNextInQueue();
2627
+ } else {
2628
+ this.isPlaying = false;
2629
+ this.currentMsgIndex = -1;
2630
+ this.updatePlayingIndicator();
2631
+ }
2619
2632
  });
2620
2633
  this.audio.addEventListener('error', (e) => {
2621
2634
  console.error('TTS audio error:', e);
2622
- this.isPlaying = false;
2623
- this.currentMsgIndex = -1;
2624
- this.updatePlayingIndicator();
2635
+ if (this._streamActive) {
2636
+ // 流式模式:跳过错误段,播放下一段
2637
+ this._playNextInQueue();
2638
+ } else {
2639
+ this.isPlaying = false;
2640
+ this.currentMsgIndex = -1;
2641
+ this.updatePlayingIndicator();
2642
+ }
2625
2643
  });
2626
2644
  },
2627
2645
 
@@ -2652,10 +2670,16 @@ const ttsManager = {
2652
2670
  },
2653
2671
 
2654
2672
  stop() {
2673
+ this._stopRequested = true;
2655
2674
  this.audio.pause();
2656
2675
  this.audio.currentTime = 0;
2657
2676
  this.isPlaying = false;
2658
2677
  this.currentMsgIndex = -1;
2678
+ this._streamActive = false;
2679
+ this._streamBuffer = '';
2680
+ this._audioQueue = [];
2681
+ this._audioPlaying = false;
2682
+ this._streamMsgIndex = -1;
2659
2683
  this.updatePlayingIndicator();
2660
2684
  },
2661
2685
 
@@ -2666,10 +2690,216 @@ const ttsManager = {
2666
2690
  }
2667
2691
  },
2668
2692
 
2693
+ // ════════════════════════════════════════════
2694
+ // ── 分段流式 TTS:text_delta 回调 ──
2695
+ // ════════════════════════════════════════════
2696
+
2697
+ /**
2698
+ * 开始流式 TTS 会话
2699
+ * @param {number} msgIndex - 消息索引
2700
+ */
2701
+ _startStream(msgIndex) {
2702
+ this._stopRequested = false;
2703
+ this._streamActive = true;
2704
+ this._streamBuffer = '';
2705
+ this._audioQueue = [];
2706
+ this._audioPlaying = false;
2707
+ this._streamMsgIndex = msgIndex;
2708
+ this.currentMsgIndex = msgIndex;
2709
+ this.isPlaying = true;
2710
+ },
2711
+
2712
+ /**
2713
+ * 流式推送文本增量
2714
+ * 在 flow_engine.js 的 text_delta 处理中调用
2715
+ * 积累到句子边界时自动触发 TTS 合成
2716
+ * @param {string} delta - 新增文本片段
2717
+ */
2718
+ streamDelta(delta) {
2719
+ if (!this.enabled || !this._streamActive || this._stopRequested) return;
2720
+ if (!delta || !delta.trim()) return;
2721
+
2722
+ this._streamBuffer += delta;
2723
+
2724
+ // 检测句子边界:中文句号/感叹号/问号,英文句号+空格,或换行
2725
+ var boundaryPattern = /[。!?]|\.(?:\s|$)|\n/;
2726
+ var boundaryIdx = -1;
2727
+ for (var i = 0; i < this._streamBuffer.length; i++) {
2728
+ if (boundaryPattern.test(this._streamBuffer[i])) {
2729
+ boundaryIdx = i;
2730
+ break;
2731
+ }
2732
+ }
2733
+
2734
+ // 还没到句子边界,但如果缓冲区已经很长(>200字),强制切分
2735
+ if (boundaryIdx === -1 && this._streamBuffer.length > 200) {
2736
+ // 在最后一个逗号或空格处切分
2737
+ var lastComma = -1;
2738
+ for (var j = 0; j < this._streamBuffer.length; j++) {
2739
+ var ch = this._streamBuffer[j];
2740
+ if (ch === ',' || ch === ',' || ch === ';' || ch === ';' || ch === ' ' || ch === ':') {
2741
+ lastComma = j;
2742
+ }
2743
+ }
2744
+ if (lastComma > 0) {
2745
+ boundaryIdx = lastComma;
2746
+ } else {
2747
+ boundaryIdx = this._streamBuffer.length;
2748
+ }
2749
+ }
2750
+
2751
+ if (boundaryIdx !== -1) {
2752
+ // 提取到边界的文本
2753
+ var sentence = this._streamBuffer.substring(0, boundaryIdx + 1).trim();
2754
+ this._streamBuffer = this._streamBuffer.substring(boundaryIdx + 1);
2755
+
2756
+ if (sentence) {
2757
+ var cleanSentence = this._cleanForStreamTTS(sentence);
2758
+ if (cleanSentence) {
2759
+ this._enqueueTTS(cleanSentence);
2760
+ }
2761
+ }
2762
+ }
2763
+ },
2764
+
2765
+ /**
2766
+ * 刷新剩余缓冲区(流结束时调用)
2767
+ * 将 buffer 中剩余的文本立即合成
2768
+ */
2769
+ streamFlush() {
2770
+ if (!this.enabled || !this._streamActive || this._stopRequested) return;
2771
+ var remaining = this._streamBuffer.trim();
2772
+ this._streamBuffer = '';
2773
+ if (remaining) {
2774
+ var cleanText = this._cleanForStreamTTS(remaining);
2775
+ if (cleanText) {
2776
+ this._enqueueTTS(cleanText);
2777
+ }
2778
+ }
2779
+ // 标记流式阶段结束(队列播完后自动清理状态)
2780
+ this._streamActive = false;
2781
+ },
2782
+
2783
+ /**
2784
+ * 清理文本用于流式 TTS(去 HTML/代码块/执行结果等)
2785
+ */
2786
+ _cleanForStreamTTS(text) {
2787
+ // 去除代码块
2788
+ text = text.replace(/```[\s\S]*?```/g, '');
2789
+ // 去除执行结果标记
2790
+ text = text.replace(/^\s*[✅❌⏰]\s*\[执行结果\].*/gm, '');
2791
+ // 去除 HTML 标签
2792
+ text = text.replace(/<svg[^>]*>[\s\S]*?<\/svg>/gi, '');
2793
+ text = text.replace(/<img[^>]*>/gi, '');
2794
+ text = text.replace(/<br\s*\/?>/gi, '\n');
2795
+ text = text.replace(/<[^>]+>/g, '');
2796
+ // 去除 emoji
2797
+ text = text.replace(/[\u{1F300}-\u{1FAFF}]/gu, '');
2798
+ text = text.replace(/[\u{2600}-\u{27BF}]/gu, '');
2799
+ text = text.replace(/[\u{FE00}-\u{FE0F}]/gu, '');
2800
+ text = text.replace(/[\u{200D}]/gu, '');
2801
+ text = text.replace(/[\u{20E3}]/gu, '');
2802
+ text = text.replace(/[\u{2300}-\u{23FF}]/gu, '');
2803
+ text = text.replace(/[\u{2B50}-\u{2B55}]/gu, '');
2804
+ text = text.replace(/[\u{203C}-\u{3299}]/gu, '');
2805
+ text = text.replace(/[\u{E0020}-\u{E007F}]/gu, '');
2806
+ text = text.replace(/[✅❌⚠️🔄⏰🔒💻🔍📁🧠🌐🛠👋🤖🎯💡🚀👍🎯📊📝🔊🔍💬📌✨✓✗→←↓↑⏹⬇⬆↩]/g, '');
2807
+ // 去除多余换行
2808
+ text = text.replace(/\n{2,}/g, '\n');
2809
+ text = text.trim();
2810
+ return text || null;
2811
+ },
2812
+
2813
+ /**
2814
+ * 将文本加入 TTS 合成队列(异步,不阻塞)
2815
+ */
2816
+ _enqueueTTS(text) {
2817
+ if (this._stopRequested) return;
2818
+ var self = this;
2819
+
2820
+ (async function() {
2821
+ try {
2822
+ var hash = simpleHash(text);
2823
+ var blobUrl = self.cache.get(hash);
2824
+
2825
+ if (!blobUrl) {
2826
+ var resp = await fetch('/api/tts', {
2827
+ method: 'POST',
2828
+ headers: { 'Content-Type': 'application/json' },
2829
+ body: JSON.stringify({
2830
+ text: text,
2831
+ voice: self.voice,
2832
+ speed: self.speed,
2833
+ }),
2834
+ });
2835
+
2836
+ if (!resp.ok) {
2837
+ var errData = await resp.json().catch(function() { return {}; });
2838
+ throw new Error(errData.error || 'TTS 请求失败');
2839
+ }
2840
+
2841
+ var blob = await resp.blob();
2842
+ blobUrl = URL.createObjectURL(blob);
2843
+ self.cache.set(hash, blobUrl);
2844
+ }
2845
+
2846
+ if (!self._stopRequested) {
2847
+ self._audioQueue.push(blobUrl);
2848
+ // 如果还没开始播放队列,立即开始
2849
+ if (!self._audioPlaying) {
2850
+ self._playNextInQueue();
2851
+ }
2852
+ }
2853
+ } catch (e) {
2854
+ console.error('TTS stream chunk error:', e);
2855
+ }
2856
+ })();
2857
+ },
2858
+
2859
+ /**
2860
+ * 播放队列中的下一段音频
2861
+ */
2862
+ _playNextInQueue() {
2863
+ if (this._stopRequested) {
2864
+ this.isPlaying = false;
2865
+ this._audioPlaying = false;
2866
+ this.currentMsgIndex = -1;
2867
+ this.updatePlayingIndicator();
2868
+ return;
2869
+ }
2870
+
2871
+ if (this._audioQueue.length === 0) {
2872
+ // 队列空了,检查流式是否已结束
2873
+ if (!this._streamActive) {
2874
+ // 流结束且队列为空 → 播放完成
2875
+ this.isPlaying = false;
2876
+ this._audioPlaying = false;
2877
+ this.currentMsgIndex = -1;
2878
+ this.updatePlayingIndicator();
2879
+ }
2880
+ // 如果流还在继续,等待新的音频入队
2881
+ return;
2882
+ }
2883
+
2884
+ var blobUrl = this._audioQueue.shift();
2885
+ this.audio.src = blobUrl;
2886
+ this._audioPlaying = true;
2887
+
2888
+ var self = this;
2889
+ this.audio.play().catch(function(e) {
2890
+ console.error('TTS play queue error:', e);
2891
+ self._playNextInQueue();
2892
+ });
2893
+ },
2894
+
2895
+ // ════════════════════════════════════════════
2896
+ // ── 完整消息 TTS(非流式,兼容手动点击) ──
2897
+ // ════════════════════════════════════════════
2898
+
2669
2899
  async speak(msgIndex) {
2670
2900
  if (msgIndex < 0 || msgIndex >= state.messages.length) return;
2671
2901
  const msg = state.messages[msgIndex];
2672
- if (!msg || msg.role !== 'user' && !msg.content) return;
2902
+ if (!msg || msg.role !== 'assistant' && !msg.content) return;
2673
2903
 
2674
2904
  // 跳过命令执行结果(以 [执行结果] 开头的消息)
2675
2905
  var rawText = msg.content.replace(/<[^>]+>/g, '');
@@ -2677,24 +2907,23 @@ const ttsManager = {
2677
2907
 
2678
2908
  // 去除 HTML 标签(msg.content 是 HTML 格式,SVG 图标等会被朗读)
2679
2909
  let text = msg.content
2680
- .replace(/<svg[^>]*>[\s\S]*?<\/svg>/gi, '') // 移除 SVG 图标
2681
- .replace(/<img[^>]*>/gi, '[图片]') // 图片替换为文字
2682
- .replace(/<br\s*\/?>/gi, '\n') // <br> 转换为换行
2683
- .replace(/<[^>]+>/g, '') // 移除所有 HTML 标签
2684
- // emoji 和特殊符号过滤
2685
- .replace(/[\u{1F300}-\u{1FAFF}]/gu, '') // 全部 Emoji 范围
2686
- .replace(/[\u{2600}-\u{27BF}]/gu, '') // 杂项/装饰符号
2687
- .replace(/[\u{FE00}-\u{FE0F}]/gu, '') // 变体选择符
2688
- .replace(/[\u{200D}]/gu, '') // ZWJ 零宽连接符
2689
- .replace(/[\u{20E3}]/gu, '') // 组合符号
2690
- .replace(/[\u{2300}-\u{23FF}]/gu, '') // 技术符号
2691
- .replace(/[\u{2B50}-\u{2B55}]/gu, '') // 星星等
2692
- .replace(/[\u{203C}-\u{3299}]/gu, '') // CJK 符号
2693
- .replace(/[\u{E0020}-\u{E007F}]/gu, '') // 标签
2694
- .replace(/[✅❌⚠️🔄⏰🔒💻🔍📁🧠🌐🛠️👋🤖🎯💡🚀👍🎯📊📝🔊🔍💬📌✨✓✗→←↓↑⏹⬇⬆↩]/g, '') // 常用图标
2695
- .replace(/```[\s\S]*?```/g, '代码块') // 代码块替换为文字
2696
- .replace(/`[^`]+`/g, function(m) { return m.slice(1,-1); }) // 保留内联代码文字但去引号
2697
- .replace(/\n{2,}/g, '\n') // 多余换行
2910
+ .replace(/<svg[^>]*>[\s\S]*?<\/svg>/gi, '')
2911
+ .replace(/<img[^>]*>/gi, '[图片]')
2912
+ .replace(/<br\s*\/?>/gi, '\n')
2913
+ .replace(/<[^>]+>/g, '')
2914
+ .replace(/[\u{1F300}-\u{1FAFF}]/gu, '')
2915
+ .replace(/[\u{2600}-\u{27BF}]/gu, '')
2916
+ .replace(/[\u{FE00}-\u{FE0F}]/gu, '')
2917
+ .replace(/[\u{200D}]/gu, '')
2918
+ .replace(/[\u{20E3}]/gu, '')
2919
+ .replace(/[\u{2300}-\u{23FF}]/gu, '')
2920
+ .replace(/[\u{2B50}-\u{2B55}]/gu, '')
2921
+ .replace(/[\u{203C}-\u{3299}]/gu, '')
2922
+ .replace(/[\u{E0020}-\u{E007F}]/gu, '')
2923
+ .replace(/[✅❌⚠️🔄⏰🔒💻🔍📁🧠🌐🛠👋🤖🎯💡🚀👍🎯📊📝🔊🔍💬📌✨✓✗→←↓↑⏹⬇⬆↩]/g, '')
2924
+ .replace(/```[\s\S]*?```/g, '代码块')
2925
+ .replace(/`[^`]+`/g, function(m) { return m.slice(1,-1); })
2926
+ .replace(/\n{2,}/g, '\n')
2698
2927
  .trim();
2699
2928
 
2700
2929
  if (!text) return;
@@ -1,3 +1,4 @@
1
+ <string>:27: SyntaxWarning: invalid escape sequence '\s'
1
2
  // ══════════════════════════════════════════════════════
2
3
  // ── Flow Engine: 文本处理流引擎 ──
3
4
  // ── 负责消息发送、SSE 流式处理、大文本检测与分段、
@@ -738,6 +739,11 @@ async function sendMessage() {
738
739
  fullResponse += evt.content;
739
740
  state.messages[msgIdx].content = fullResponse;
740
741
  throttledStreamUpdate(msgIdx);
742
+ // ── 分段流式 TTS:推送增量文本 ──
743
+ if (ttsManager.enabled && !ttsManager._streamActive) {
744
+ ttsManager._startStream(msgIdx);
745
+ }
746
+ ttsManager.streamDelta(evt.content);
741
747
  } else if (evt.type === 'thought_delta') {
742
748
  // Agent 思考过程增量文本(流式推送,单独显示)
743
749
  fullThought += evt.content;
@@ -843,10 +849,9 @@ async function sendMessage() {
843
849
  state.agentSessions[state.activeAgent] = [...state.sessions];
844
850
  renderSessions();
845
851
 
846
- // Auto-play TTS if enabled (skip command execution results)
847
- if (ttsManager.enabled && fullResponse && !fullResponse.match(/^\s*[✅❌⏰]\s*\[执行结果\]/m)) {
848
- const idx = state.messages.length - 1;
849
- ttsManager.speak(idx);
852
+ // ── 分段流式 TTS:刷新剩余缓冲区 ──
853
+ if (ttsManager.enabled && ttsManager._streamActive) {
854
+ ttsManager.streamFlush();
850
855
  }
851
856
  } catch (e) {
852
857
  if (e.name === 'AbortError') {