diff --git a/docs/Deployment.md b/docs/Deployment.md index 01f0afeb..99aa8682 100644 --- a/docs/Deployment.md +++ b/docs/Deployment.md @@ -269,7 +269,7 @@ LLM: 如果你能看到,类似以下日志,则是本项目服务启动成功的标志。 ``` -250427 13:04:20[0.3.11_SiFuChTTnofu][__main__]-INFO-OTA接口是 http://192.168.4.123:8002/xiaozhi/ota/ +250427 13:04:20[0.3.11_SiFuChTTnofu][__main__]-INFO-OTA接口是 http://192.168.4.123:8003/xiaozhi/ota/ 250427 13:04:20[0.3.11_SiFuChTTnofu][__main__]-INFO-Websocket地址是 ws://192.168.4.123:8000/xiaozhi/v1/ 250427 13:04:20[0.3.11_SiFuChTTnofu][__main__]-INFO-=======上面的地址是websocket协议地址,请勿用浏览器访问======= 250427 13:04:20[0.3.11_SiFuChTTnofu][__main__]-INFO-如想测试websocket请用谷歌浏览器打开test目录下的test_page.html @@ -280,7 +280,7 @@ LLM: 但是如果你用docker部署,那么你的日志里给出的接口地址信息就不是真实的接口地址。 最正确的方法,是根据电脑的局域网IP来确定你的接口地址。 -如果你的电脑的局域网IP比如是`192.168.1.25`,那么你的接口地址就是:`ws://192.168.1.25:8000/xiaozhi/v1/`,对应的OTA地址就是:`http://192.168.1.25:8002/xiaozhi/ota/`。 +如果你的电脑的局域网IP比如是`192.168.1.25`,那么你的接口地址就是:`ws://192.168.1.25:8000/xiaozhi/v1/`,对应的OTA地址就是:`http://192.168.1.25:8003/xiaozhi/ota/`。 这个信息很有用的,后面`编译esp32固件`需要用到。 diff --git a/docs/firmware-build.md b/docs/firmware-build.md index fffac288..64584b27 100644 --- a/docs/firmware-build.md +++ b/docs/firmware-build.md @@ -9,7 +9,7 @@ ### 如果你用的是简单Server部署 此刻,请你用浏览器打开你的ota地址,例如我的ota地址 ``` -http://192.168.1.25:8002/xiaozhi/ota/ +http://192.168.1.25:8003/xiaozhi/ota/ ``` 如果显示“OTA接口运行正常,向设备发送的websocket地址是:ws://xxx:8000/xiaozhi/v1/ diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/AgentController.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/AgentController.java index c9cefce8..1a331f03 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/AgentController.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/AgentController.java @@ -110,6 +110,7 @@ public class AgentController { entity.setAsrModelId(template.getAsrModelId()); entity.setVadModelId(template.getVadModelId()); entity.setLlmModelId(template.getLlmModelId()); + entity.setVllmModelId(template.getVllmModelId()); entity.setTtsModelId(template.getTtsModelId()); entity.setTtsVoiceId(template.getTtsVoiceId()); entity.setMemModelId(template.getMemModelId()); diff --git a/main/manager-web/src/views/DictManagement.vue b/main/manager-web/src/views/DictManagement.vue index f4de8350..9f6c80ac 100644 --- a/main/manager-web/src/views/DictManagement.vue +++ b/main/manager-web/src/views/DictManagement.vue @@ -538,7 +538,7 @@ export default { .data-table { border-radius: 6px; - overflow-y: auto; + overflow-y: hidden; background-color: transparent !important; --table-max-height: calc(100vh - 40vh); max-height: var(--table-max-height); diff --git a/main/xiaozhi-server/core/handle/helloHandle.py b/main/xiaozhi-server/core/handle/helloHandle.py index 1edeefcd..f5a3b0cd 100644 --- a/main/xiaozhi-server/core/handle/helloHandle.py +++ b/main/xiaozhi-server/core/handle/helloHandle.py @@ -2,6 +2,7 @@ import time import json import random import asyncio +from core.utils.dialogue import Message from core.utils.util import audio_to_data from core.handle.sendAudioHandle import sendAudioMessage, send_stt_message from core.utils.util import remove_punctuation_and_length, opus_datas_to_wav_bytes @@ -67,20 +68,32 @@ async def checkWakeupWords(conn, text): # 获取当前音色 voice = getattr(conn.tts, "voice", "default") + if not voice: + voice = "default" # 获取唤醒词回复配置 response = wakeup_words_config.get_wakeup_response(voice) + if not response or not response.get("file_path"): + response = { + "voice": "default", + "file_path": "config/assets/wakeup_words.wav", + "time": 0, + "text": "哈啰啊,我是小智啦,声音好听的台湾女孩一枚,超开心认识你耶,最近在忙啥,别忘了给我来点有趣的料哦,我超爱听八卦的啦", + } # 播放唤醒词回复 conn.client_abort = False - opus_packets, _ = audio_to_data(response["file_path"]) + opus_packets, _ = audio_to_data(response.get("file_path")) - conn.logger.bind(tag=TAG).info(f"播放唤醒词回复: {response['text']}") - await sendAudioMessage(conn, SentenceType.FIRST, opus_packets, response["text"]) + conn.logger.bind(tag=TAG).info(f"播放唤醒词回复: {response.get('text')}") + await sendAudioMessage(conn, SentenceType.FIRST, opus_packets, response.get("text")) await sendAudioMessage(conn, SentenceType.LAST, [], None) + # 补充对话 + conn.dialogue.put(Message(role="assistant", content=response.get("text"))) + # 检查是否需要更新唤醒词回复 - if time.time() - response["time"] > WAKEUP_CONFIG["refresh_time"]: + if time.time() - response.get("time", 0) > WAKEUP_CONFIG["refresh_time"]: if not _wakeup_response_lock.locked(): asyncio.create_task(wakeupWordsResponse(conn)) return True @@ -100,7 +113,7 @@ async def wakeupWordsResponse(conn): question = ( "此刻用户正在和你说```" + wakeup_word - + "```。\n请你根据以上用户的内容进行简短回复。要像一个人正常人一样说话,不要像机器人一样说话。\n" + + "```。\n请你根据以上用户的内容进行20-30字回复。要符合系统设置的角色情感和态度,不要像机器人一样说话。\n" + "请勿对这条内容本身进行任何解释和回应,请勿返回表情符号,仅返回对用户的内容的回复。" ) diff --git a/main/xiaozhi-server/core/providers/tts/base.py b/main/xiaozhi-server/core/providers/tts/base.py index 296625e5..1a9d069c 100644 --- a/main/xiaozhi-server/core/providers/tts/base.py +++ b/main/xiaozhi-server/core/providers/tts/base.py @@ -1,4 +1,5 @@ import os +import re import queue import uuid import asyncio @@ -169,15 +170,18 @@ class TTSProviderBase(ABC): content_type=ContentType.ACTION, ) ) - self.tts_text_queue.put( - TTSMessageDTO( - sentence_id=sentence_id, - sentence_type=SentenceType.MIDDLE, - content_type=content_type, - content_detail=content_detail, - content_file=content_file, + # 对于单句的文本,进行分段处理 + segments = re.split(r'([。!?!?;;\n])', content_detail) + for seg in segments: + self.tts_text_queue.put( + TTSMessageDTO( + sentence_id=sentence_id, + sentence_type=SentenceType.MIDDLE, + content_type=content_type, + content_detail=seg, + content_file=content_file, + ) ) - ) self.tts_text_queue.put( TTSMessageDTO( sentence_id=sentence_id, diff --git a/main/xiaozhi-server/core/providers/tts/cozecn.py b/main/xiaozhi-server/core/providers/tts/cozecn.py index ede08928..6c5d0547 100644 --- a/main/xiaozhi-server/core/providers/tts/cozecn.py +++ b/main/xiaozhi-server/core/providers/tts/cozecn.py @@ -11,8 +11,8 @@ class TTSProvider(TTSProviderBase): self.voice = config.get("private_voice") else: self.voice = config.get("voice") - self.response_format = config.get("response_format", "mp3") - self.audio_file_type = config.get("response_format", "mp3") + self.response_format = config.get("response_format", "wav") + self.audio_file_type = config.get("response_format", "wav") self.host = "api.coze.cn" self.api_url = f"https://{self.host}/v1/audio/speech" diff --git a/main/xiaozhi-server/core/utils/textUtils.py b/main/xiaozhi-server/core/utils/textUtils.py index 7fadd3ff..603d964c 100644 --- a/main/xiaozhi-server/core/utils/textUtils.py +++ b/main/xiaozhi-server/core/utils/textUtils.py @@ -9,26 +9,38 @@ def get_string_no_punctuation_or_emoji(s): end = len(chars) - 1 while end >= start and is_punctuation_or_emoji(chars[end]): end -= 1 - return ''.join(chars[start:end + 1]) + return "".join(chars[start : end + 1]) + def is_punctuation_or_emoji(char): """检查字符是否为空格、指定标点或表情符号""" # 定义需要去除的中英文标点(包括全角/半角) punctuation_set = { - ',', ',', # 中文逗号 + 英文逗号 - '。', '.', # 中文句号 + 英文句号 - '!', '!', # 中文感叹号 + 英文感叹号 - '-', '-', # 英文连字符 + 中文全角横线 - '、' # 中文顿号 + ",", + ",", # 中文逗号 + 英文逗号 + "。", + ".", # 中文句号 + 英文句号 + "!", + "!", # 中文感叹号 + 英文感叹号 + "-", + "-", # 英文连字符 + 中文全角横线 + "、", # 中文顿号 + "[", + "]", # 方括号 + "【", + "】", # 中文方括号 } if char.isspace() or char in punctuation_set: return True # 检查表情符号(保留原有逻辑) code_point = ord(char) emoji_ranges = [ - (0x1F600, 0x1F64F), (0x1F300, 0x1F5FF), - (0x1F680, 0x1F6FF), (0x1F900, 0x1F9FF), - (0x1FA70, 0x1FAFF), (0x2600, 0x26FF), - (0x2700, 0x27BF) + (0x1F600, 0x1F64F), + (0x1F300, 0x1F5FF), + (0x1F680, 0x1F6FF), + (0x1F900, 0x1F9FF), + (0x1FA70, 0x1FAFF), + (0x2600, 0x26FF), + (0x2700, 0x27BF), ] - return any(start <= code_point <= end for start, end in emoji_ranges) \ No newline at end of file + return any(start <= code_point <= end for start, end in emoji_ranges) diff --git a/main/xiaozhi-server/core/utils/wakeup_word.py b/main/xiaozhi-server/core/utils/wakeup_word.py index 29929081..ab58f290 100644 --- a/main/xiaozhi-server/core/utils/wakeup_word.py +++ b/main/xiaozhi-server/core/utils/wakeup_word.py @@ -1,4 +1,5 @@ import os +import re import yaml import time import hashlib @@ -88,33 +89,30 @@ class WakeupWordsConfig: voice = hashlib.md5(voice.encode()).hexdigest() """获取唤醒词回复配置""" config = self._load_config() - default_response = { - "voice": "default", - "file_path": "config/assets/wakeup_words.wav", - "time": 0, - "text": "哈啰啊,我是小智啦,声音好听的台湾女孩一枚,超开心认识你耶,最近在忙啥,别忘了给我来点有趣的料哦,我超爱听八卦的啦", - } if not config or voice not in config: - return default_response + return None # 检查文件大小 file_path = config[voice]["file_path"] if not os.path.exists(file_path) or os.stat(file_path).st_size < (15 * 1024): - return default_response + return None return config[voice] def update_wakeup_response(self, voice: str, file_path: str, text: str): """更新唤醒词回复配置""" try: + # 过滤表情符号 + filtered_text = re.sub(r'[\U0001F600-\U0001F64F\U0001F900-\U0001F9FF]', '', text) + config = self._load_config() voice_hash = hashlib.md5(voice.encode()).hexdigest() config[voice_hash] = { "voice": voice, "file_path": file_path, "time": time.time(), - "text": text, + "text": filtered_text, } self._save_config(config) except Exception as e: