mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-23 15:43:54 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c796af7d07 | ||
|
|
bd556c33a0 | ||
|
|
878809ecb4 |
@@ -114,10 +114,10 @@ Memory:
|
|||||||
# 每月有1000次免费调用
|
# 每月有1000次免费调用
|
||||||
api_key: 你的mem0ai api key
|
api_key: 你的mem0ai api key
|
||||||
nomem:
|
nomem:
|
||||||
# 不想使用记忆功能,可以使用nomem
|
# 不想使用记忆功能,可以使用nomem
|
||||||
type: nomem
|
type: nomem
|
||||||
mem_local_short:
|
mem_local_short:
|
||||||
# 本地记忆功能,通过selected_module的llm总结,数据保存在本地,不会上传到服务器
|
# 本地记忆功能,通过selected_module的llm总结,数据保存在本地,不会上传到服务器
|
||||||
type: mem_local_short
|
type: mem_local_short
|
||||||
|
|
||||||
ASR:
|
ASR:
|
||||||
@@ -225,7 +225,7 @@ TTS:
|
|||||||
# 定义TTS API类型
|
# 定义TTS API类型
|
||||||
type: edge
|
type: edge
|
||||||
voice: zh-CN-XiaoxiaoNeural
|
voice: zh-CN-XiaoxiaoNeural
|
||||||
output_file: tmp/
|
output_dir: tmp/
|
||||||
DoubaoTTS:
|
DoubaoTTS:
|
||||||
# 定义TTS API类型
|
# 定义TTS API类型
|
||||||
type: doubao
|
type: doubao
|
||||||
@@ -235,7 +235,7 @@ TTS:
|
|||||||
# 地址:https://console.volcengine.com/speech/service/8
|
# 地址:https://console.volcengine.com/speech/service/8
|
||||||
api_url: https://openspeech.bytedance.com/api/v1/tts
|
api_url: https://openspeech.bytedance.com/api/v1/tts
|
||||||
voice: BV001_streaming
|
voice: BV001_streaming
|
||||||
output_file: tmp/
|
output_dir: tmp/
|
||||||
authorization: "Bearer;"
|
authorization: "Bearer;"
|
||||||
appid: 你的火山引擎语音合成服务appid
|
appid: 你的火山引擎语音合成服务appid
|
||||||
access_token: 你的火山引擎语音合成服务access_token
|
access_token: 你的火山引擎语音合成服务access_token
|
||||||
@@ -246,7 +246,7 @@ TTS:
|
|||||||
# token申请地址 https://cloud.siliconflow.cn/account/ak
|
# token申请地址 https://cloud.siliconflow.cn/account/ak
|
||||||
model: FunAudioLLM/CosyVoice2-0.5B
|
model: FunAudioLLM/CosyVoice2-0.5B
|
||||||
voice: FunAudioLLM/CosyVoice2-0.5B:alex
|
voice: FunAudioLLM/CosyVoice2-0.5B:alex
|
||||||
output_file: tmp/
|
output_dir: tmp/
|
||||||
access_token: 你的硅基流动API密钥
|
access_token: 你的硅基流动API密钥
|
||||||
response_format: wav
|
response_format: wav
|
||||||
CozeCnTTS:
|
CozeCnTTS:
|
||||||
@@ -254,7 +254,7 @@ TTS:
|
|||||||
# COZECN TTS
|
# COZECN TTS
|
||||||
# token申请地址 https://www.coze.cn/open/oauth/pats
|
# token申请地址 https://www.coze.cn/open/oauth/pats
|
||||||
voice: 7426720361733046281
|
voice: 7426720361733046281
|
||||||
output_file: tmp/
|
output_dir: tmp/
|
||||||
access_token: 你的coze web key
|
access_token: 你的coze web key
|
||||||
response_format: wav
|
response_format: wav
|
||||||
FishSpeech:
|
FishSpeech:
|
||||||
@@ -267,7 +267,7 @@ TTS:
|
|||||||
#--decoder-config-name firefly_gan_vq
|
#--decoder-config-name firefly_gan_vq
|
||||||
#--compile
|
#--compile
|
||||||
type: fishspeech
|
type: fishspeech
|
||||||
output_file: tmp/
|
output_dir: tmp/
|
||||||
response_format: wav
|
response_format: wav
|
||||||
reference_id: null
|
reference_id: null
|
||||||
reference_audio: ["/tmp/test.wav",]
|
reference_audio: ["/tmp/test.wav",]
|
||||||
@@ -291,7 +291,7 @@ TTS:
|
|||||||
#python api_v2.py -a 127.0.0.1 -p 9880 -c GPT_SoVITS/configs/caixukun.yaml
|
#python api_v2.py -a 127.0.0.1 -p 9880 -c GPT_SoVITS/configs/caixukun.yaml
|
||||||
type: gpt_sovits_v2
|
type: gpt_sovits_v2
|
||||||
url: "http://127.0.0.1:9880/tts"
|
url: "http://127.0.0.1:9880/tts"
|
||||||
output_file: tmp/
|
output_dir: tmp/
|
||||||
text_lang: "auto"
|
text_lang: "auto"
|
||||||
ref_audio_path: "caixukun.wav"
|
ref_audio_path: "caixukun.wav"
|
||||||
prompt_text: ""
|
prompt_text: ""
|
||||||
@@ -316,7 +316,7 @@ TTS:
|
|||||||
#python api.py
|
#python api.py
|
||||||
type: gpt_sovits_v3
|
type: gpt_sovits_v3
|
||||||
url: "http://127.0.0.1:9880"
|
url: "http://127.0.0.1:9880"
|
||||||
output_file: tmp/
|
output_dir: tmp/
|
||||||
text_language: "auto"
|
text_language: "auto"
|
||||||
refer_wav_path: "caixukun.wav"
|
refer_wav_path: "caixukun.wav"
|
||||||
prompt_language: "zh"
|
prompt_language: "zh"
|
||||||
@@ -337,7 +337,7 @@ TTS:
|
|||||||
# api_key地址:https://platform.minimaxi.com/user-center/basic-information/interface-key
|
# api_key地址:https://platform.minimaxi.com/user-center/basic-information/interface-key
|
||||||
# 定义TTS API类型
|
# 定义TTS API类型
|
||||||
type: minimax
|
type: minimax
|
||||||
output_file: tmp/
|
output_dir: tmp/
|
||||||
group_id: 你的minimax平台groupID
|
group_id: 你的minimax平台groupID
|
||||||
api_key: 你的minimax平台接口密钥
|
api_key: 你的minimax平台接口密钥
|
||||||
model: "speech-01-turbo"
|
model: "speech-01-turbo"
|
||||||
@@ -374,7 +374,7 @@ TTS:
|
|||||||
# token地址:https://nls-portal.console.aliyun.com/overview
|
# token地址:https://nls-portal.console.aliyun.com/overview
|
||||||
# 定义TTS API类型
|
# 定义TTS API类型
|
||||||
type: aliyun
|
type: aliyun
|
||||||
output_file: tmp/
|
output_dir: tmp/
|
||||||
appkey: 你的阿里云智能语音交互服务项目Appkey
|
appkey: 你的阿里云智能语音交互服务项目Appkey
|
||||||
token: 你的阿里云智能语音交互服务AccessToken,临时的24小时,要长期用下方的access_key_id,access_key_secret
|
token: 你的阿里云智能语音交互服务AccessToken,临时的24小时,要长期用下方的access_key_id,access_key_secret
|
||||||
voice: xiaoyun
|
voice: xiaoyun
|
||||||
@@ -397,7 +397,7 @@ TTS:
|
|||||||
api_url: https://api.302ai.cn/doubao/tts_hd
|
api_url: https://api.302ai.cn/doubao/tts_hd
|
||||||
authorization: "Bearer "
|
authorization: "Bearer "
|
||||||
voice: "zh_female_wanwanxiaohe_moon_bigtts"
|
voice: "zh_female_wanwanxiaohe_moon_bigtts"
|
||||||
output_file: tmp/
|
output_dir: tmp/
|
||||||
access_token: "你的302API密钥"
|
access_token: "你的302API密钥"
|
||||||
ACGNTTS:
|
ACGNTTS:
|
||||||
#在线网址:https://acgn.ttson.cn/
|
#在线网址:https://acgn.ttson.cn/
|
||||||
@@ -414,7 +414,7 @@ TTS:
|
|||||||
to_lang: ZH
|
to_lang: ZH
|
||||||
url: https://u95167-bd74-2aef8085.westx.seetacloud.com:8443/flashsummary/tts?token=
|
url: https://u95167-bd74-2aef8085.westx.seetacloud.com:8443/flashsummary/tts?token=
|
||||||
format: mp3
|
format: mp3
|
||||||
output_file: tmp/
|
output_dir: tmp/
|
||||||
emotion: 1
|
emotion: 1
|
||||||
OpenAITTS:
|
OpenAITTS:
|
||||||
# openai官方文本转语音服务,可支持全球大多数语种
|
# openai官方文本转语音服务,可支持全球大多数语种
|
||||||
@@ -428,7 +428,7 @@ TTS:
|
|||||||
voice: onyx
|
voice: onyx
|
||||||
# 语速范围0.25-4.0
|
# 语速范围0.25-4.0
|
||||||
speed: 1
|
speed: 1
|
||||||
output_file: tmp/
|
output_dir: tmp/
|
||||||
CustomTTS:
|
CustomTTS:
|
||||||
# 自定义的TTS接口服务,请求参数可自定义
|
# 自定义的TTS接口服务,请求参数可自定义
|
||||||
# 要求接口使用GET方式请求,并返回音频文件
|
# 要求接口使用GET方式请求,并返回音频文件
|
||||||
@@ -443,7 +443,7 @@ TTS:
|
|||||||
headers: # 自定义请求头
|
headers: # 自定义请求头
|
||||||
# Authorization: Bearer xxxx
|
# Authorization: Bearer xxxx
|
||||||
format: wav # 接口返回的音频格式
|
format: wav # 接口返回的音频格式
|
||||||
output_file: tmp/
|
output_dir: tmp/
|
||||||
# 模块测试配置
|
# 模块测试配置
|
||||||
module_test:
|
module_test:
|
||||||
test_sentences: # 自定义测试语句
|
test_sentences: # 自定义测试语句
|
||||||
@@ -466,4 +466,4 @@ manager:
|
|||||||
enabled: false
|
enabled: false
|
||||||
ip: 0.0.0.0
|
ip: 0.0.0.0
|
||||||
port: 8002
|
port: 8002
|
||||||
use_private_config: false
|
use_private_config: false
|
||||||
|
|||||||
@@ -7,9 +7,48 @@ from core.utils.util import read_config, get_project_dir
|
|||||||
default_config_file = "config.yaml"
|
default_config_file = "config.yaml"
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_directories(config):
|
||||||
|
"""确保所有配置路径存在"""
|
||||||
|
dirs_to_create = set()
|
||||||
|
project_dir = get_project_dir() # 获取项目根目录
|
||||||
|
# 日志文件目录
|
||||||
|
log_dir = config.get('log', {}).get('log_dir', 'tmp')
|
||||||
|
dirs_to_create.add(os.path.join(project_dir, log_dir))
|
||||||
|
|
||||||
|
# ASR/TTS模块输出目录
|
||||||
|
for module in ['ASR', 'TTS']:
|
||||||
|
for provider in config.get(module, {}).values():
|
||||||
|
output_dir = provider.get('output_dir', '')
|
||||||
|
if output_dir:
|
||||||
|
dirs_to_create.add(output_dir)
|
||||||
|
|
||||||
|
# 根据selected_module创建模型目录
|
||||||
|
selected_modules = config.get('selected_module', {})
|
||||||
|
for module_type in ['ASR', 'LLM', 'TTS']:
|
||||||
|
selected_provider = selected_modules.get(module_type)
|
||||||
|
if not selected_provider:
|
||||||
|
continue
|
||||||
|
provider_config = config.get(module_type, {}).get(selected_provider, {})
|
||||||
|
output_dir = provider_config.get('output_dir')
|
||||||
|
if output_dir:
|
||||||
|
full_model_dir = os.path.join(project_dir, output_dir)
|
||||||
|
dirs_to_create.add(full_model_dir)
|
||||||
|
|
||||||
|
# 统一创建目录(保留原data目录创建)
|
||||||
|
for dir_path in dirs_to_create:
|
||||||
|
try:
|
||||||
|
os.makedirs(dir_path, exist_ok=True)
|
||||||
|
except PermissionError:
|
||||||
|
print(f"警告:无法创建目录 {dir_path},请检查写入权限")
|
||||||
|
|
||||||
|
|
||||||
def get_config_file():
|
def get_config_file():
|
||||||
global default_config_file
|
global default_config_file
|
||||||
# 判断是否存在私有的配置文件
|
"""获取配置文件路径,优先使用私有配置文件(若存在)。
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: 配置文件路径(相对路径或默认路径)
|
||||||
|
"""
|
||||||
config_file = default_config_file
|
config_file = default_config_file
|
||||||
if os.path.exists(get_project_dir() + "data/." + default_config_file):
|
if os.path.exists(get_project_dir() + "data/." + default_config_file):
|
||||||
config_file = "data/." + default_config_file
|
config_file = "data/." + default_config_file
|
||||||
@@ -20,9 +59,13 @@ def load_config():
|
|||||||
"""加载配置文件"""
|
"""加载配置文件"""
|
||||||
parser = argparse.ArgumentParser(description="Server configuration")
|
parser = argparse.ArgumentParser(description="Server configuration")
|
||||||
config_file = get_config_file()
|
config_file = get_config_file()
|
||||||
|
|
||||||
parser.add_argument("--config_path", type=str, default=config_file)
|
parser.add_argument("--config_path", type=str, default=config_file)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
return read_config(args.config_path)
|
config = read_config(args.config_path)
|
||||||
|
# 初始化目录
|
||||||
|
ensure_directories(config)
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
def update_config(config):
|
def update_config(config):
|
||||||
@@ -67,7 +110,7 @@ def find_missing_keys(new_config, old_config, parent_key=''):
|
|||||||
def check_config_file():
|
def check_config_file():
|
||||||
old_config_file = get_config_file()
|
old_config_file = get_config_file()
|
||||||
global default_config_file
|
global default_config_file
|
||||||
if not old_config_file.startswith('data'):
|
if not 'data' in old_config_file:
|
||||||
return
|
return
|
||||||
old_config = read_config(get_project_dir() + old_config_file)
|
old_config = read_config(get_project_dir() + old_config_file)
|
||||||
new_config = read_config(get_project_dir() + default_config_file)
|
new_config = read_config(get_project_dir() + default_config_file)
|
||||||
|
|||||||
@@ -197,7 +197,7 @@ class ConnectionHandler:
|
|||||||
self.dialogue.put(Message(role="system", content=self.prompt))
|
self.dialogue.put(Message(role="system", content=self.prompt))
|
||||||
|
|
||||||
self.func_handler = FunctionHandler(self.config)
|
self.func_handler = FunctionHandler(self.config)
|
||||||
|
|
||||||
def change_system_prompt(self, prompt):
|
def change_system_prompt(self, prompt):
|
||||||
self.prompt = prompt
|
self.prompt = prompt
|
||||||
# 找到原来的role==system,替换原来的系统提示
|
# 找到原来的role==system,替换原来的系统提示
|
||||||
@@ -303,7 +303,7 @@ class ConnectionHandler:
|
|||||||
self.logger.bind(tag=TAG).debug(json.dumps(self.dialogue.get_llm_dialogue(), indent=4, ensure_ascii=False))
|
self.logger.bind(tag=TAG).debug(json.dumps(self.dialogue.get_llm_dialogue(), indent=4, ensure_ascii=False))
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def chat_with_function_calling(self, query, tool_call = False):
|
def chat_with_function_calling(self, query, tool_call=False):
|
||||||
self.logger.bind(tag=TAG).debug(f"Chat with function calling start: {query}")
|
self.logger.bind(tag=TAG).debug(f"Chat with function calling start: {query}")
|
||||||
"""Chat with function calling for intent detection using streaming"""
|
"""Chat with function calling for intent detection using streaming"""
|
||||||
if self.isNeedAuth():
|
if self.isNeedAuth():
|
||||||
@@ -311,7 +311,7 @@ class ConnectionHandler:
|
|||||||
future = asyncio.run_coroutine_threadsafe(self._check_and_broadcast_auth_code(), self.loop)
|
future = asyncio.run_coroutine_threadsafe(self._check_and_broadcast_auth_code(), self.loop)
|
||||||
future.result()
|
future.result()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if not tool_call:
|
if not tool_call:
|
||||||
self.dialogue.put(Message(role="user", content=query))
|
self.dialogue.put(Message(role="user", content=query))
|
||||||
|
|
||||||
@@ -320,7 +320,7 @@ class ConnectionHandler:
|
|||||||
|
|
||||||
response_message = []
|
response_message = []
|
||||||
processed_chars = 0 # 跟踪已处理的字符位置
|
processed_chars = 0 # 跟踪已处理的字符位置
|
||||||
|
|
||||||
try:
|
try:
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
|
|
||||||
@@ -328,7 +328,7 @@ class ConnectionHandler:
|
|||||||
future = asyncio.run_coroutine_threadsafe(self.memory.query_memory(query), self.loop)
|
future = asyncio.run_coroutine_threadsafe(self.memory.query_memory(query), self.loop)
|
||||||
memory_str = future.result()
|
memory_str = future.result()
|
||||||
|
|
||||||
#self.logger.bind(tag=TAG).info(f"对话记录: {self.dialogue.get_llm_dialogue_with_memory(memory_str)}")
|
# self.logger.bind(tag=TAG).info(f"对话记录: {self.dialogue.get_llm_dialogue_with_memory(memory_str)}")
|
||||||
|
|
||||||
# 使用支持functions的streaming接口
|
# 使用支持functions的streaming接口
|
||||||
llm_responses = self.llm.response_with_functions(
|
llm_responses = self.llm.response_with_functions(
|
||||||
@@ -351,8 +351,8 @@ class ConnectionHandler:
|
|||||||
content_arguments = ""
|
content_arguments = ""
|
||||||
for response in llm_responses:
|
for response in llm_responses:
|
||||||
content, tools_call = response
|
content, tools_call = response
|
||||||
if content is not None and len(content)>0:
|
if content is not None and len(content) > 0:
|
||||||
if len(response_message)<=0 and (content=="```" or "<tool_call>" in content):
|
if len(response_message) <= 0 and (content == "```" or "<tool_call>" in content):
|
||||||
tool_call_flag = True
|
tool_call_flag = True
|
||||||
|
|
||||||
if tools_call is not None:
|
if tools_call is not None:
|
||||||
@@ -366,7 +366,7 @@ class ConnectionHandler:
|
|||||||
|
|
||||||
if content is not None and len(content) > 0:
|
if content is not None and len(content) > 0:
|
||||||
if tool_call_flag:
|
if tool_call_flag:
|
||||||
content_arguments+=content
|
content_arguments += content
|
||||||
else:
|
else:
|
||||||
response_message.append(content)
|
response_message.append(content)
|
||||||
|
|
||||||
@@ -422,16 +422,17 @@ class ConnectionHandler:
|
|||||||
else:
|
else:
|
||||||
function_arguments = json.loads(function_arguments)
|
function_arguments = json.loads(function_arguments)
|
||||||
if not bHasError:
|
if not bHasError:
|
||||||
self.logger.bind(tag=TAG).info(f"function_name={function_name}, function_id={function_id}, function_arguments={function_arguments}")
|
self.logger.bind(tag=TAG).info(
|
||||||
|
f"function_name={function_name}, function_id={function_id}, function_arguments={function_arguments}")
|
||||||
function_call_data = {
|
function_call_data = {
|
||||||
"name": function_name,
|
"name": function_name,
|
||||||
"id": function_id,
|
"id": function_id,
|
||||||
"arguments": function_arguments
|
"arguments": function_arguments
|
||||||
}
|
}
|
||||||
result = self.func_handler.handle_llm_function_call(self, function_call_data)
|
result = self.func_handler.handle_llm_function_call(self, function_call_data)
|
||||||
self._handle_function_result(result, function_call_data, text_index+1)
|
self._handle_function_result(result, function_call_data, text_index + 1)
|
||||||
|
|
||||||
# 处理最后剩余的文本
|
# 处理最后剩余的文本
|
||||||
full_text = "".join(response_message)
|
full_text = "".join(response_message)
|
||||||
remaining_text = full_text[processed_chars:]
|
remaining_text = full_text[processed_chars:]
|
||||||
if remaining_text:
|
if remaining_text:
|
||||||
@@ -443,7 +444,7 @@ class ConnectionHandler:
|
|||||||
self.tts_queue.put(future)
|
self.tts_queue.put(future)
|
||||||
|
|
||||||
# 存储对话内容
|
# 存储对话内容
|
||||||
if len(response_message)>0:
|
if len(response_message) > 0:
|
||||||
self.dialogue.put(Message(role="assistant", content="".join(response_message)))
|
self.dialogue.put(Message(role="assistant", content="".join(response_message)))
|
||||||
|
|
||||||
self.llm_finish_task = True
|
self.llm_finish_task = True
|
||||||
@@ -452,32 +453,40 @@ class ConnectionHandler:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def _handle_function_result(self, result, function_call_data, text_index):
|
def _handle_function_result(self, result, function_call_data, text_index):
|
||||||
if result.action == Action.RESPONSE: # 直接回复前端
|
if result.action == Action.RESPONSE: # 直接回复前端
|
||||||
text = result.response
|
text = result.response
|
||||||
self.recode_first_last_text(text, text_index)
|
self.recode_first_last_text(text, text_index)
|
||||||
future = self.executor.submit(self.speak_and_play, text, text_index)
|
future = self.executor.submit(self.speak_and_play, text, text_index)
|
||||||
self.tts_queue.put(future)
|
self.tts_queue.put(future)
|
||||||
self.dialogue.put(Message(role="assistant", content=text))
|
self.dialogue.put(Message(role="assistant", content=text))
|
||||||
elif result.action == Action.REQLLM: # 调用函数后再请求llm生成回复
|
elif result.action == Action.REQLLM: # 调用函数后再请求llm生成回复
|
||||||
|
|
||||||
text = result.result
|
text = result.result
|
||||||
if text is not None and len(text) > 0:
|
if text is not None and len(text) > 0:
|
||||||
function_id = function_call_data["id"]
|
function_id = function_call_data["id"]
|
||||||
function_name = function_call_data["name"]
|
function_name = function_call_data["name"]
|
||||||
function_arguments = function_call_data["arguments"]
|
function_arguments = function_call_data["arguments"]
|
||||||
self.dialogue.put(Message(role='assistant',
|
self.dialogue.put(Message(role='assistant',
|
||||||
tool_calls=[{"id": function_id,
|
tool_calls=[{"id": function_id,
|
||||||
"function": {"arguments": function_arguments,"name": function_name},
|
"function": {"arguments": function_arguments,
|
||||||
"type": 'function',
|
"name": function_name},
|
||||||
"index": 0}]))
|
"type": 'function',
|
||||||
|
"index": 0}]))
|
||||||
|
|
||||||
self.dialogue.put(Message(role="tool", tool_call_id=function_id, content=text))
|
self.dialogue.put(Message(role="tool", tool_call_id=function_id, content=text))
|
||||||
self.chat_with_function_calling(text, tool_call=True)
|
self.chat_with_function_calling(text, tool_call=True)
|
||||||
elif result.action == Action.NOTFOUND:
|
elif result.action == Action.NOTFOUND:
|
||||||
text = result.response
|
text = result.result
|
||||||
|
self.recode_first_last_text(text, text_index)
|
||||||
|
future = self.executor.submit(self.speak_and_play, text, text_index)
|
||||||
|
self.tts_queue.put(future)
|
||||||
|
self.dialogue.put(Message(role="assistant", content=text))
|
||||||
else:
|
else:
|
||||||
text = result.response
|
text = result.result
|
||||||
|
self.recode_first_last_text(text, text_index)
|
||||||
|
future = self.executor.submit(self.speak_and_play, text, text_index)
|
||||||
|
self.tts_queue.put(future)
|
||||||
|
self.dialogue.put(Message(role="assistant", content=text))
|
||||||
|
|
||||||
def _tts_priority_thread(self):
|
def _tts_priority_thread(self):
|
||||||
while not self.stop_event.is_set():
|
while not self.stop_event.is_set():
|
||||||
|
|||||||
@@ -212,7 +212,7 @@ def register_device_type(descriptor):
|
|||||||
},
|
},
|
||||||
"response_failure": {
|
"response_failure": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "查询失败时的友好回复,例如:'无法获取{device_name}的{prop_info['description']}'"
|
"description": f"查询失败时的友好回复,例如:'无法获取{device_name}的{prop_info['description']}'"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": ["response_success", "response_failure"]
|
"required": ["response_success", "response_failure"]
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from config.logger import setup_logging
|
|||||||
import json
|
import json
|
||||||
from core.handle.abortHandle import handleAbortMessage
|
from core.handle.abortHandle import handleAbortMessage
|
||||||
from core.handle.helloHandle import handleHelloMessage
|
from core.handle.helloHandle import handleHelloMessage
|
||||||
from core.handle.receiveAudioHandle import startToChat
|
from core.handle.receiveAudioHandle import startToChat, handleAudioMessage
|
||||||
from core.handle.iotHandle import handleIotDescriptors, handleIotStatus
|
from core.handle.iotHandle import handleIotDescriptors, handleIotStatus
|
||||||
|
|
||||||
TAG = __name__
|
TAG = __name__
|
||||||
@@ -31,6 +31,8 @@ async def handleTextMessage(conn, message):
|
|||||||
elif msg_json["state"] == "stop":
|
elif msg_json["state"] == "stop":
|
||||||
conn.client_have_voice = True
|
conn.client_have_voice = True
|
||||||
conn.client_voice_stop = True
|
conn.client_voice_stop = True
|
||||||
|
if len(conn.asr_audio) > 0:
|
||||||
|
await handleAudioMessage(conn, b'')
|
||||||
elif msg_json["state"] == "detect":
|
elif msg_json["state"] == "detect":
|
||||||
conn.asr_server_receive = False
|
conn.asr_server_receive = False
|
||||||
conn.client_have_voice = False
|
conn.client_have_voice = False
|
||||||
@@ -41,6 +43,6 @@ async def handleTextMessage(conn, message):
|
|||||||
if "descriptors" in msg_json:
|
if "descriptors" in msg_json:
|
||||||
await handleIotDescriptors(conn, msg_json["descriptors"])
|
await handleIotDescriptors(conn, msg_json["descriptors"])
|
||||||
if "states" in msg_json:
|
if "states" in msg_json:
|
||||||
await handleIotStatus(conn, msg_json["states"])
|
await handleIotStatus(conn, msg_json["states"])
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
await conn.websocket.send(message)
|
await conn.websocket.send(message)
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ logger = setup_logging()
|
|||||||
class TTSProviderBase(ABC):
|
class TTSProviderBase(ABC):
|
||||||
def __init__(self, config, delete_audio_file):
|
def __init__(self, config, delete_audio_file):
|
||||||
self.delete_audio_file = delete_audio_file
|
self.delete_audio_file = delete_audio_file
|
||||||
self.output_file = config.get("output_file")
|
self.output_file = config.get("output_dir")
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def generate_filename(self):
|
def generate_filename(self):
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ class TTSProvider(TTSProviderBase):
|
|||||||
self.headers = config.get("headers", {})
|
self.headers = config.get("headers", {})
|
||||||
self.params = config.get("params")
|
self.params = config.get("params")
|
||||||
self.format = config.get("format", "wav")
|
self.format = config.get("format", "wav")
|
||||||
self.output_file = config.get("output_file", "tmp/")
|
self.output_file = config.get("output_dir", "tmp/")
|
||||||
|
|
||||||
def generate_filename(self):
|
def generate_filename(self):
|
||||||
return os.path.join(self.output_file, f"tts-{datetime.now().date()}@{uuid.uuid4().hex}.{self.format}")
|
return os.path.join(self.output_file, f"tts-{datetime.now().date()}@{uuid.uuid4().hex}.{self.format}")
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ class TTSProvider(TTSProviderBase):
|
|||||||
self.voice = config.get("voice", "alloy")
|
self.voice = config.get("voice", "alloy")
|
||||||
self.response_format = "wav"
|
self.response_format = "wav"
|
||||||
self.speed = config.get("speed", 1.0)
|
self.speed = config.get("speed", 1.0)
|
||||||
self.output_file = config.get("output_file", "tmp/")
|
self.output_file = config.get("output_dir", "tmp/")
|
||||||
check_model_key("TTS", self.api_key)
|
check_model_key("TTS", self.api_key)
|
||||||
|
|
||||||
def generate_filename(self, extension=".wav"):
|
def generate_filename(self, extension=".wav"):
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ class TTSProvider(TTSProviderBase):
|
|||||||
self.volume_change_dB = config.get("volume_change_dB", 0)
|
self.volume_change_dB = config.get("volume_change_dB", 0)
|
||||||
self.speed_factor = config.get("speed_factor", 1)
|
self.speed_factor = config.get("speed_factor", 1)
|
||||||
self.stream = config.get("stream", False)
|
self.stream = config.get("stream", False)
|
||||||
self.output_file = config.get("output_file")
|
self.output_file = config.get("output_dir")
|
||||||
self.pitch_factor = config.get("pitch_factor", 0)
|
self.pitch_factor = config.get("pitch_factor", 0)
|
||||||
self.format = config.get("format", "mp3")
|
self.format = config.get("format", "mp3")
|
||||||
self.emotion = config.get("emotion", 1)
|
self.emotion = config.get("emotion", 1)
|
||||||
|
|||||||
Executable → Regular
+1
@@ -21,6 +21,7 @@ services:
|
|||||||
- ./data:/opt/xiaozhi-esp32-server/data
|
- ./data:/opt/xiaozhi-esp32-server/data
|
||||||
# 模型文件挂接,很重要
|
# 模型文件挂接,很重要
|
||||||
- ./models/SenseVoiceSmall/model.pt:/opt/xiaozhi-esp32-server/models/SenseVoiceSmall/model.pt
|
- ./models/SenseVoiceSmall/model.pt:/opt/xiaozhi-esp32-server/models/SenseVoiceSmall/model.pt
|
||||||
|
|
||||||
# #智控台还没开发好,还不能完全使用,会报很多错误,如果是非技术人员,请不要启用智控台服务
|
# #智控台还没开发好,还不能完全使用,会报很多错误,如果是非技术人员,请不要启用智控台服务
|
||||||
# xiaozhi-esp32-server-web:
|
# xiaozhi-esp32-server-web:
|
||||||
# image: ghcr.nju.edu.cn/xinnan-tech/xiaozhi-esp32-server:web_latest
|
# image: ghcr.nju.edu.cn/xinnan-tech/xiaozhi-esp32-server:web_latest
|
||||||
|
|||||||
@@ -49,6 +49,8 @@ def raise_and_lower_the_volume(conn, action: str):
|
|||||||
|
|
||||||
async def _raise_and_lower_the_volume(conn, action):
|
async def _raise_and_lower_the_volume(conn, action):
|
||||||
volume = await get_iot_status(conn, "Speaker", "volume")
|
volume = await get_iot_status(conn, "Speaker", "volume")
|
||||||
|
if volume is None:
|
||||||
|
raise Exception("你的设备不支持音量控制")
|
||||||
if action == 'raise':
|
if action == 'raise':
|
||||||
volume += 10
|
volume += 10
|
||||||
elif action == 'lower':
|
elif action == 'lower':
|
||||||
|
|||||||
@@ -19,4 +19,4 @@ loguru==0.7.3
|
|||||||
requests==2.32.3
|
requests==2.32.3
|
||||||
cozepy==0.12.0
|
cozepy==0.12.0
|
||||||
mem0ai==0.1.62
|
mem0ai==0.1.62
|
||||||
bs4==0.0.2
|
bs4==0.0.2
|
||||||
|
|||||||
Reference in New Issue
Block a user