add:实时打断对话

This commit is contained in:
hrz
2025-02-04 12:20:10 +08:00
parent 9c166fbf4b
commit 93acdb5861
5 changed files with 44 additions and 7 deletions
+6 -2
View File
@@ -27,13 +27,13 @@
## 已实现 ## 已实现
- `xiaozhi-esp32` 通信 WebSocket 协议 - `xiaozhi-esp32` 通信 WebSocket 协议
- 支持实时打断对话,试一下在聊天的时候说一句`你好小智`
- 支持国语、粤语、英语、日语、韩语 5 种语言识别(FunASR(默认)) - 支持国语、粤语、英语、日语、韩语 5 种语言识别(FunASR(默认))
- 自由更换 LLM(支持ChatGLM(默认)、Dify、DeepSeek - 自由更换 LLM(支持ChatGLM(默认)、Dify、DeepSeek
- 自由更换 TTS(支持EdgeTTS(默认)、火山引擎豆包TTS) - 自由更换 TTS(支持EdgeTTS(默认)、火山引擎豆包TTS)
## 正在实现 ## 正在实现
- 打断对话
- 按键手动对话 - 按键手动对话
- 长时间不聊天进入休眠状态 - 长时间不聊天进入休眠状态
- 对话记忆 - 对话记忆
@@ -213,7 +213,11 @@ https://espressif.github.io/esp-launchpad/
建议:大模型和TTS都是依赖接口,如果网络环境不佳,可以考虑换成本地模型。或多尝试切换不同的接口模型。 建议:大模型和TTS都是依赖接口,如果网络环境不佳,可以考虑换成本地模型。或多尝试切换不同的接口模型。
## 3、更多问题,可联系我们反馈 ## 3、为啥我的ChatGLMLLM回复有点问题,明明它才是小智,却把我当小智。
建议:第一步可以先`配置文件`里的调整提示词。第二步,`配置文件`中用的模型是免费的模型:`glm-4-flash`,可以考虑换成收费版本的。
## 4、更多问题,可联系我们反馈
![图片](docs/images/wechat.jpg) ![图片](docs/images/wechat.jpg)
+7 -2
View File
@@ -32,13 +32,13 @@ To fully experience this project, follow these steps:
## Implemented ## Implemented
- `xiaozhi-esp32` WebSocket communication protocol - `xiaozhi-esp32` WebSocket communication protocol
- Support interrupt dialogue in real time, try to say a sentence when chatting `你好小智`
- Support for 5 languages: Mandarin, Cantonese, English, Japanese, Korean (FunASR - default) - Support for 5 languages: Mandarin, Cantonese, English, Japanese, Korean (FunASR - default)
- Flexible LLM switching (ChatGLM - default, Dify, DeepSeek) - Flexible LLM switching (ChatGLM - default, Dify, DeepSeek)
- Flexible TTS switching (EdgeTTS - default, ByteDance Doubao TTS) - Flexible TTS switching (EdgeTTS - default, ByteDance Doubao TTS)
## In Progress ## In Progress
- Conversation interruption
- Manual button-triggered dialogue - Manual button-triggered dialogue
- Sleep mode after inactivity - Sleep mode after inactivity
- Dialogue memory - Dialogue memory
@@ -223,7 +223,12 @@ both are slow, the network environment may need to be optimized.
Suggestions: Both big models and TTS are dependent interfaces. If the network environment is not good, you can consider Suggestions: Both big models and TTS are dependent interfaces. If the network environment is not good, you can consider
changing the local model. Or try to switch different interface models. changing the local model. Or try to switch different interface models.
## 3、For more questions, contact us to feedback ## 3、Why is my ChatGLMLLM replying to a bit? Obviously it is Xiaozhi, but treats me as Xiaozhi.
Suggestion: The first step can be to adjust the prompt words in the configuration file. The second step, the model used
in the configuration file is the free model: `glm-4-flash`. You might consider switching to the paid version.
## 4、For more questions, contact us to feedback
![图片](docs/images/wechat.jpg) ![图片](docs/images/wechat.jpg)
+14 -3
View File
@@ -12,6 +12,7 @@ from collections import deque
from core.utils.util import is_segment from core.utils.util import is_segment
from core.utils.dialogue import Message, Dialogue from core.utils.dialogue import Message, Dialogue
from core.handle.textHandle import handleTextMessage from core.handle.textHandle import handleTextMessage
from core.handle.abortHandle import handleAbortMessage
from core.handle.helloHandle import handleHelloMessage from core.handle.helloHandle import handleHelloMessage
from core.utils.util import get_string_no_punctuation_or_emoji from core.utils.util import get_string_no_punctuation_or_emoji
from concurrent.futures import ThreadPoolExecutor, TimeoutError from concurrent.futures import ThreadPoolExecutor, TimeoutError
@@ -28,6 +29,7 @@ class ConnectionHandler:
self.session_id = None self.session_id = None
self.prompt = None self.prompt = None
self.welcome_msg = None self.welcome_msg = None
self.client_abort = False
# 线程任务相关 # 线程任务相关
self.loop = asyncio.get_event_loop() self.loop = asyncio.get_event_loop()
@@ -100,6 +102,8 @@ class ConnectionHandler:
msg_json = json.loads(message) msg_json = json.loads(message)
if msg_json["type"] == "hello": if msg_json["type"] == "hello":
await handleHelloMessage(self, "你好") await handleHelloMessage(self, "你好")
if msg_json["type"] == "abort":
await handleAbortMessage(self)
except json.JSONDecodeError: except json.JSONDecodeError:
await handleTextMessage(self, message) await handleTextMessage(self, message)
@@ -126,6 +130,11 @@ class ConnectionHandler:
self.llm_finish_task = False self.llm_finish_task = False
for content in llm_responses: for content in llm_responses:
response_message.append(content) response_message.append(content)
# 如果中途被打断,就停止生成
if self.client_abort:
start = len(response_message)
break
end_time = time.time() # 记录结束时间 end_time = time.time() # 记录结束时间
self.logger.debug(f"大模型返回时间时间: {end_time - start_time} 秒, 生成token={content}") self.logger.debug(f"大模型返回时间时间: {end_time - start_time} 秒, 生成token={content}")
if is_segment(response_message): if is_segment(response_message):
@@ -169,9 +178,11 @@ class ConnectionHandler:
except Exception as e: except Exception as e:
self.logger.error(f"TTS 任务出错: {e}") self.logger.error(f"TTS 任务出错: {e}")
continue continue
asyncio.run_coroutine_threadsafe( if not self.client_abort:
sendAudioMessage(self, opus_datas, duration, text), self.loop # 如果没有中途打断就发送语音
) asyncio.run_coroutine_threadsafe(
sendAudioMessage(self, opus_datas, duration, text), self.loop
)
if self.tts.delete_audio_file and os.path.exists(tts_file): if self.tts.delete_audio_file and os.path.exists(tts_file):
os.remove(tts_file) os.remove(tts_file)
except Exception as e: except Exception as e:
+16
View File
@@ -0,0 +1,16 @@
import json
import logging
logger = logging.getLogger(__name__)
async def handleAbortMessage(conn):
logger.info("Abort message received")
# 设置成打断状态,会自动打断llm、tts任务
conn.client_abort = True
# 打断屏显任务
conn.stop_all_tasks()
# 打断客户端说话状态
await conn.websocket.send(json.dumps({"type": "tts", "state": "stop", "session_id": conn.session_id}))
conn.clearSpeakStatus()
logger.info("Abort message received-end")
+1
View File
@@ -20,6 +20,7 @@ async def handleAudioMessage(conn, audio):
conn.asr_audio.append(audio) conn.asr_audio.append(audio)
# 如果本段有声音,且已经停止了 # 如果本段有声音,且已经停止了
if conn.client_voice_stop: if conn.client_voice_stop:
conn.client_abort = False
conn.asr_server_receive = False conn.asr_server_receive = False
text, file_path = conn.asr.speech_to_text(conn.asr_audio, conn.session_id) text, file_path = conn.asr.speech_to_text(conn.asr_audio, conn.session_id)
logger.info(f"识别文本: {text}") logger.info(f"识别文本: {text}")