mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-22 15:13:55 +08:00
Merge branch 'main' into agent-plugin
This commit is contained in:
+2
-2
@@ -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地址是 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协议地址,请勿用浏览器访问=======
|
||||||
250427 13:04:20[0.3.11_SiFuChTTnofu][__main__]-INFO-如想测试websocket请用谷歌浏览器打开test目录下的test_page.html
|
250427 13:04:20[0.3.11_SiFuChTTnofu][__main__]-INFO-如想测试websocket请用谷歌浏览器打开test目录下的test_page.html
|
||||||
@@ -280,7 +280,7 @@ LLM:
|
|||||||
但是如果你用docker部署,那么你的日志里给出的接口地址信息就不是真实的接口地址。
|
但是如果你用docker部署,那么你的日志里给出的接口地址信息就不是真实的接口地址。
|
||||||
|
|
||||||
最正确的方法,是根据电脑的局域网IP来确定你的接口地址。
|
最正确的方法,是根据电脑的局域网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固件`需要用到。
|
这个信息很有用的,后面`编译esp32固件`需要用到。
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
### 如果你用的是简单Server部署
|
### 如果你用的是简单Server部署
|
||||||
此刻,请你用浏览器打开你的ota地址,例如我的ota地址
|
此刻,请你用浏览器打开你的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/
|
如果显示“OTA接口运行正常,向设备发送的websocket地址是:ws://xxx:8000/xiaozhi/v1/
|
||||||
|
|
||||||
|
|||||||
@@ -110,6 +110,7 @@ public class AgentController {
|
|||||||
entity.setAsrModelId(template.getAsrModelId());
|
entity.setAsrModelId(template.getAsrModelId());
|
||||||
entity.setVadModelId(template.getVadModelId());
|
entity.setVadModelId(template.getVadModelId());
|
||||||
entity.setLlmModelId(template.getLlmModelId());
|
entity.setLlmModelId(template.getLlmModelId());
|
||||||
|
entity.setVllmModelId(template.getVllmModelId());
|
||||||
entity.setTtsModelId(template.getTtsModelId());
|
entity.setTtsModelId(template.getTtsModelId());
|
||||||
entity.setTtsVoiceId(template.getTtsVoiceId());
|
entity.setTtsVoiceId(template.getTtsVoiceId());
|
||||||
entity.setMemModelId(template.getMemModelId());
|
entity.setMemModelId(template.getMemModelId());
|
||||||
|
|||||||
@@ -538,7 +538,7 @@ export default {
|
|||||||
|
|
||||||
.data-table {
|
.data-table {
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
overflow-y: auto;
|
overflow-y: hidden;
|
||||||
background-color: transparent !important;
|
background-color: transparent !important;
|
||||||
--table-max-height: calc(100vh - 40vh);
|
--table-max-height: calc(100vh - 40vh);
|
||||||
max-height: var(--table-max-height);
|
max-height: var(--table-max-height);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import time
|
|||||||
import json
|
import json
|
||||||
import random
|
import random
|
||||||
import asyncio
|
import asyncio
|
||||||
|
from core.utils.dialogue import Message
|
||||||
from core.utils.util import audio_to_data
|
from core.utils.util import audio_to_data
|
||||||
from core.handle.sendAudioHandle import sendAudioMessage, send_stt_message
|
from core.handle.sendAudioHandle import sendAudioMessage, send_stt_message
|
||||||
from core.utils.util import remove_punctuation_and_length, opus_datas_to_wav_bytes
|
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")
|
voice = getattr(conn.tts, "voice", "default")
|
||||||
|
if not voice:
|
||||||
|
voice = "default"
|
||||||
|
|
||||||
# 获取唤醒词回复配置
|
# 获取唤醒词回复配置
|
||||||
response = wakeup_words_config.get_wakeup_response(voice)
|
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
|
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']}")
|
conn.logger.bind(tag=TAG).info(f"播放唤醒词回复: {response.get('text')}")
|
||||||
await sendAudioMessage(conn, SentenceType.FIRST, opus_packets, response["text"])
|
await sendAudioMessage(conn, SentenceType.FIRST, opus_packets, response.get("text"))
|
||||||
await sendAudioMessage(conn, SentenceType.LAST, [], None)
|
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():
|
if not _wakeup_response_lock.locked():
|
||||||
asyncio.create_task(wakeupWordsResponse(conn))
|
asyncio.create_task(wakeupWordsResponse(conn))
|
||||||
return True
|
return True
|
||||||
@@ -100,7 +113,7 @@ async def wakeupWordsResponse(conn):
|
|||||||
question = (
|
question = (
|
||||||
"此刻用户正在和你说```"
|
"此刻用户正在和你说```"
|
||||||
+ wakeup_word
|
+ wakeup_word
|
||||||
+ "```。\n请你根据以上用户的内容进行简短回复。要像一个人正常人一样说话,不要像机器人一样说话。\n"
|
+ "```。\n请你根据以上用户的内容进行20-30字回复。要符合系统设置的角色情感和态度,不要像机器人一样说话。\n"
|
||||||
+ "请勿对这条内容本身进行任何解释和回应,请勿返回表情符号,仅返回对用户的内容的回复。"
|
+ "请勿对这条内容本身进行任何解释和回应,请勿返回表情符号,仅返回对用户的内容的回复。"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import queue
|
import queue
|
||||||
import uuid
|
import uuid
|
||||||
import asyncio
|
import asyncio
|
||||||
@@ -169,15 +170,18 @@ class TTSProviderBase(ABC):
|
|||||||
content_type=ContentType.ACTION,
|
content_type=ContentType.ACTION,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.tts_text_queue.put(
|
# 对于单句的文本,进行分段处理
|
||||||
TTSMessageDTO(
|
segments = re.split(r'([。!?!?;;\n])', content_detail)
|
||||||
sentence_id=sentence_id,
|
for seg in segments:
|
||||||
sentence_type=SentenceType.MIDDLE,
|
self.tts_text_queue.put(
|
||||||
content_type=content_type,
|
TTSMessageDTO(
|
||||||
content_detail=content_detail,
|
sentence_id=sentence_id,
|
||||||
content_file=content_file,
|
sentence_type=SentenceType.MIDDLE,
|
||||||
|
content_type=content_type,
|
||||||
|
content_detail=seg,
|
||||||
|
content_file=content_file,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
self.tts_text_queue.put(
|
self.tts_text_queue.put(
|
||||||
TTSMessageDTO(
|
TTSMessageDTO(
|
||||||
sentence_id=sentence_id,
|
sentence_id=sentence_id,
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ class TTSProvider(TTSProviderBase):
|
|||||||
self.voice = config.get("private_voice")
|
self.voice = config.get("private_voice")
|
||||||
else:
|
else:
|
||||||
self.voice = config.get("voice")
|
self.voice = config.get("voice")
|
||||||
self.response_format = config.get("response_format", "mp3")
|
self.response_format = config.get("response_format", "wav")
|
||||||
self.audio_file_type = config.get("response_format", "mp3")
|
self.audio_file_type = config.get("response_format", "wav")
|
||||||
self.host = "api.coze.cn"
|
self.host = "api.coze.cn"
|
||||||
self.api_url = f"https://{self.host}/v1/audio/speech"
|
self.api_url = f"https://{self.host}/v1/audio/speech"
|
||||||
|
|
||||||
|
|||||||
@@ -9,26 +9,38 @@ def get_string_no_punctuation_or_emoji(s):
|
|||||||
end = len(chars) - 1
|
end = len(chars) - 1
|
||||||
while end >= start and is_punctuation_or_emoji(chars[end]):
|
while end >= start and is_punctuation_or_emoji(chars[end]):
|
||||||
end -= 1
|
end -= 1
|
||||||
return ''.join(chars[start:end + 1])
|
return "".join(chars[start : end + 1])
|
||||||
|
|
||||||
|
|
||||||
def is_punctuation_or_emoji(char):
|
def is_punctuation_or_emoji(char):
|
||||||
"""检查字符是否为空格、指定标点或表情符号"""
|
"""检查字符是否为空格、指定标点或表情符号"""
|
||||||
# 定义需要去除的中英文标点(包括全角/半角)
|
# 定义需要去除的中英文标点(包括全角/半角)
|
||||||
punctuation_set = {
|
punctuation_set = {
|
||||||
',', ',', # 中文逗号 + 英文逗号
|
",",
|
||||||
'。', '.', # 中文句号 + 英文句号
|
",", # 中文逗号 + 英文逗号
|
||||||
'!', '!', # 中文感叹号 + 英文感叹号
|
"。",
|
||||||
'-', '-', # 英文连字符 + 中文全角横线
|
".", # 中文句号 + 英文句号
|
||||||
'、' # 中文顿号
|
"!",
|
||||||
|
"!", # 中文感叹号 + 英文感叹号
|
||||||
|
"-",
|
||||||
|
"-", # 英文连字符 + 中文全角横线
|
||||||
|
"、", # 中文顿号
|
||||||
|
"[",
|
||||||
|
"]", # 方括号
|
||||||
|
"【",
|
||||||
|
"】", # 中文方括号
|
||||||
}
|
}
|
||||||
if char.isspace() or char in punctuation_set:
|
if char.isspace() or char in punctuation_set:
|
||||||
return True
|
return True
|
||||||
# 检查表情符号(保留原有逻辑)
|
# 检查表情符号(保留原有逻辑)
|
||||||
code_point = ord(char)
|
code_point = ord(char)
|
||||||
emoji_ranges = [
|
emoji_ranges = [
|
||||||
(0x1F600, 0x1F64F), (0x1F300, 0x1F5FF),
|
(0x1F600, 0x1F64F),
|
||||||
(0x1F680, 0x1F6FF), (0x1F900, 0x1F9FF),
|
(0x1F300, 0x1F5FF),
|
||||||
(0x1FA70, 0x1FAFF), (0x2600, 0x26FF),
|
(0x1F680, 0x1F6FF),
|
||||||
(0x2700, 0x27BF)
|
(0x1F900, 0x1F9FF),
|
||||||
|
(0x1FA70, 0x1FAFF),
|
||||||
|
(0x2600, 0x26FF),
|
||||||
|
(0x2700, 0x27BF),
|
||||||
]
|
]
|
||||||
return any(start <= code_point <= end for start, end in emoji_ranges)
|
return any(start <= code_point <= end for start, end in emoji_ranges)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import yaml
|
import yaml
|
||||||
import time
|
import time
|
||||||
import hashlib
|
import hashlib
|
||||||
@@ -88,33 +89,30 @@ class WakeupWordsConfig:
|
|||||||
voice = hashlib.md5(voice.encode()).hexdigest()
|
voice = hashlib.md5(voice.encode()).hexdigest()
|
||||||
"""获取唤醒词回复配置"""
|
"""获取唤醒词回复配置"""
|
||||||
config = self._load_config()
|
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:
|
if not config or voice not in config:
|
||||||
return default_response
|
return None
|
||||||
|
|
||||||
# 检查文件大小
|
# 检查文件大小
|
||||||
file_path = config[voice]["file_path"]
|
file_path = config[voice]["file_path"]
|
||||||
if not os.path.exists(file_path) or os.stat(file_path).st_size < (15 * 1024):
|
if not os.path.exists(file_path) or os.stat(file_path).st_size < (15 * 1024):
|
||||||
return default_response
|
return None
|
||||||
|
|
||||||
return config[voice]
|
return config[voice]
|
||||||
|
|
||||||
def update_wakeup_response(self, voice: str, file_path: str, text: str):
|
def update_wakeup_response(self, voice: str, file_path: str, text: str):
|
||||||
"""更新唤醒词回复配置"""
|
"""更新唤醒词回复配置"""
|
||||||
try:
|
try:
|
||||||
|
# 过滤表情符号
|
||||||
|
filtered_text = re.sub(r'[\U0001F600-\U0001F64F\U0001F900-\U0001F9FF]', '', text)
|
||||||
|
|
||||||
config = self._load_config()
|
config = self._load_config()
|
||||||
voice_hash = hashlib.md5(voice.encode()).hexdigest()
|
voice_hash = hashlib.md5(voice.encode()).hexdigest()
|
||||||
config[voice_hash] = {
|
config[voice_hash] = {
|
||||||
"voice": voice,
|
"voice": voice,
|
||||||
"file_path": file_path,
|
"file_path": file_path,
|
||||||
"time": time.time(),
|
"time": time.time(),
|
||||||
"text": text,
|
"text": filtered_text,
|
||||||
}
|
}
|
||||||
self._save_config(config)
|
self._save_config(config)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
Reference in New Issue
Block a user