From 486e16cfeb2c26acfe785ed9efe0e8f121d790c9 Mon Sep 17 00:00:00 2001 From: "Ding, Shuo" Date: Tue, 19 Aug 2025 11:44:18 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9TTS=E5=89=8D=E7=9A=84?= =?UTF-8?q?=E5=88=86=E5=8F=A5=E5=92=8C=E5=85=A8=E5=B1=8F=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=201=E3=80=81TTS=E5=89=8D=E7=9A=84=E5=88=86=E5=8F=A5=E5=B7=B2?= =?UTF-8?q?=E7=BB=8F=E5=8F=AF=E4=BB=A5=E6=AD=A3=E5=B8=B8=E7=A7=BB=E9=99=A4?= =?UTF-8?q?=E5=9B=BE=E7=89=87url=E4=BA=86=202=E3=80=81=E6=89=93=E5=BC=80fu?= =?UTF-8?q?ll=5Fscreen=E4=B8=AD=E7=9A=84=E5=85=A8=E5=B1=8F=E5=90=8E?= =?UTF-8?q?=E5=BB=B6=E8=BF=9F=E5=87=BA=E7=8E=B0=E2=80=9C=E6=AC=A2=E8=BF=8E?= =?UTF-8?q?=E8=AF=AD=E2=80=9D=EF=BC=8C=E9=81=BF=E5=85=8D=E6=8A=A5=E9=94=99?= =?UTF-8?q?=20---------=20Issue=EF=BC=9A=201=E3=80=81=E5=88=86=E5=8F=A5?= =?UTF-8?q?=E5=90=8E=E7=9A=84=E6=96=87=E5=AD=97=E6=8F=90=E4=BA=A4tts?= =?UTF-8?q?=EF=BC=8C=E4=BC=9A=E5=87=BA=E7=8E=B0websocket=E7=A9=BA=E9=97=B2?= =?UTF-8?q?=E6=97=B6=E9=97=B4=E8=BF=87=E9=95=BF=EF=BC=8C=E5=AF=BC=E8=87=B4?= =?UTF-8?q?=E8=AF=AD=E9=9F=B3=E6=8F=90=E5=89=8D=E7=BB=93=E6=9D=9F=E7=9A=84?= =?UTF-8?q?=E6=83=85=E5=86=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/screens/full_screen.dart | 4 +- lib/services/chat_sse_service.dart | 78 ++++++++++++++++++++++-------- 2 files changed, 59 insertions(+), 23 deletions(-) diff --git a/lib/screens/full_screen.dart b/lib/screens/full_screen.dart index ed9d22e..ff7b280 100644 --- a/lib/screens/full_screen.dart +++ b/lib/screens/full_screen.dart @@ -67,12 +67,12 @@ class _FullScreenState extends State { _scrollToBottom(); }); if (messageService.messages.isEmpty) { - // Future.delayed(const Duration(milliseconds: 500), () { + Future.delayed(const Duration(milliseconds: 500), () { String text = Intl.getCurrentLocale().startsWith('zh') ? "您好,我是众众,请问有什么可以帮您?" : "Hi, I'm Zhongzhong, may I help you ? "; messageService.addMessage(text, false, MessageStatus.normal); - // }); + }); } return ChatBox( scrollController: _scrollController, diff --git a/lib/services/chat_sse_service.dart b/lib/services/chat_sse_service.dart index d39454a..6fdeceb 100644 --- a/lib/services/chat_sse_service.dart +++ b/lib/services/chat_sse_service.dart @@ -35,7 +35,59 @@ class ChatSseService { print('初始化用户ID: $_cachedUserId'); } String responseText = ''; - String tempText = ''; + StringBuffer buffer = StringBuffer(); + // 英文分句符 + final enEnders = RegExp(r'[.!?]'); + // 中文分句符 + final zhEnders = RegExp(r'[。!?]'); + // 专有名词保护 + String protectIDUNYX(String text) => text.replaceAll('ID.UNYX', 'ID_UNYX'); + String restoreIDUNYX(String text) => text.replaceAll('ID_UNYX', 'ID.UNYX'); + Future processBuffer(bool isChinese) async { + String txt = buffer.toString(); + // 先过滤 markdown 图片语法 + int imgStart = txt.indexOf('!['); + while (imgStart != -1) { + int imgEnd = txt.indexOf(')', imgStart); + if (imgEnd == -1) { + // 图片语法未闭合,缓存剩余部分 + buffer.clear(); + buffer.write(txt.substring(imgStart)); + return; + } + // 移除图片语法 + txt = txt.substring(0, imgStart) + txt.substring(imgEnd + 1); + imgStart = txt.indexOf('!['); + } + // 彻底移除 markdown 有序/无序列表序号(如 1.、2.、-、*、+) + txt = txt.replaceAll(RegExp(r'(^|\n)[ \t]*[0-9]+\.[ \t]*'), '\n'); + txt = txt.replaceAll(RegExp(r'(^|\n)[ \t]*[-\*\+][ \t]+'), '\n'); + // 分句符 + RegExp enders = isChinese ? zhEnders : enEnders; + int lastEnd = 0; + // 本地缓存句子 + List sentences = []; + for (final match in enders.allMatches(txt)) { + int endIndex = match.end; + String sentence = txt.substring(lastEnd, endIndex).trim(); + if (sentence.isNotEmpty) { + sentences.add(sentence); + } + lastEnd = endIndex; + } + // 只在达到完整句子时调用 TtsUtil.send + for (final s in sentences) { + String ttsStr=CommonUtil.cleanText(s, true); + // print("发送数据到TTS: $ttsStr"); + TtsUtil.send(ttsStr); + } + // 缓存剩余不完整部分 + String remain = txt.substring(lastEnd).trim(); + buffer.clear(); + if (remain.isNotEmpty) { + buffer.write(remain); + } + } _currentClient = HttpClient(); try { final chatUri = Uri.parse('http://143.64.185.20:18606/chat'); @@ -62,7 +114,7 @@ class ChatSseService { if (line.startsWith('data:')) { final jsonStr = line.substring(5).trim(); if (jsonStr == '[DONE]' || jsonStr.contains('message_end')) { - TtsUtil.complete(); + await processBuffer(isChinese); onStreamResponse(messageId, responseText, true); break; } @@ -74,27 +126,12 @@ class ChatSseService { } if (jsonData['event'].toString().contains('message')) { final textChunk = - jsonData.containsKey('answer') ? jsonData['answer'] : ''; + jsonData.containsKey('answer') ? jsonData['answer'] : ''; if (_isAborted) { break; } - tempText += textChunk; - if (tempText.contains("ID.")) { - tempText = tempText.replaceAllMapped( - RegExp(r'ID\.', caseSensitive: false), (m) => 'ID '); - } - int endIndex = _getCompleteTextEndIndex(tempText); - String completeText = CommonUtil.cleanText(tempText.substring(0, endIndex).trim(), true); - if (completeText.isNotEmpty) { - if (_isFirstData && _startTtsFuture != null) { - await _startTtsFuture; - _isFirstData = false; - } - completeText += isChinese ? ',' : ','; - print("----------------------Send text: " + completeText); - TtsUtil.send(completeText); - } - tempText = tempText.substring(endIndex).trim(); + buffer.write(textChunk); + await processBuffer(isChinese); responseText += textChunk; onStreamResponse(messageId, responseText, false); } @@ -109,7 +146,6 @@ class ChatSseService { } catch (e) { // todo } finally { - print("------------------------------finally"); resetRequest(); } }