From a971ae21686736540628861dace7840b183ac41b Mon Sep 17 00:00:00 2001 From: XL Date: Thu, 15 May 2025 10:23:40 +0800 Subject: [PATCH 01/17] =?UTF-8?q?=E8=87=AA=E5=AE=9A=E4=B9=89tts=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=E5=8F=82=E6=95=B0=E8=BD=ACdict?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main/xiaozhi-server/core/providers/tts/custom.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/main/xiaozhi-server/core/providers/tts/custom.py b/main/xiaozhi-server/core/providers/tts/custom.py index 5b8669da..1efd7d7f 100644 --- a/main/xiaozhi-server/core/providers/tts/custom.py +++ b/main/xiaozhi-server/core/providers/tts/custom.py @@ -1,4 +1,5 @@ import os +import json import uuid import requests from config.logger import setup_logging @@ -13,7 +14,7 @@ class TTSProvider(TTSProviderBase): super().__init__(config, delete_audio_file) self.url = config.get("url") self.headers = config.get("headers", {}) - self.params = config.get("params") + self.params = json.loads(config.get("params")) self.format = config.get("format", "wav") self.output_file = config.get("output_dir", "tmp/") From 1a0a0777a34bcd0970f2631394d3f1e451ef6fda Mon Sep 17 00:00:00 2001 From: XL Date: Thu, 15 May 2025 15:35:45 +0800 Subject: [PATCH 02/17] =?UTF-8?q?=E8=87=AA=E5=AE=9A=E4=B9=89tts=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=E5=8F=82=E6=95=B0=E8=BD=ACdict=20=E6=94=AF=E6=8C=81po?= =?UTF-8?q?st=E8=AF=B7=E6=B1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/db/changelog/202505151450.sql | 4 ++++ .../main/resources/db/changelog/db.changelog-master.yaml | 7 +++++++ main/xiaozhi-server/core/providers/tts/custom.py | 6 +++++- 3 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 main/manager-api/src/main/resources/db/changelog/202505151450.sql diff --git a/main/manager-api/src/main/resources/db/changelog/202505151450.sql b/main/manager-api/src/main/resources/db/changelog/202505151450.sql new file mode 100644 index 00000000..16c6a79b --- /dev/null +++ b/main/manager-api/src/main/resources/db/changelog/202505151450.sql @@ -0,0 +1,4 @@ +-- 修改自定义TTS接口请求定义 +update `ai_model_provider` set `fields` = +'[{"key":"url","label":"服务地址","type":"string"},{"key":"method","label":"请求方式","type":"string"},{"key":"params","label":"请求参数","type":"dict","dict_name":"params"},{"key":"headers","label":"请求头","type":"dict","dict_name":"headers"},{"key":"format","label":"音频格式","type":"string"},{"key":"output_dir","label":"输出目录","type":"string"}]' +where `id` = 'SYSTEM_TTS_custom'; \ No newline at end of file diff --git a/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml b/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml index 2dfd9831..ec9354bb 100755 --- a/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml +++ b/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml @@ -128,3 +128,10 @@ databaseChangeLog: - sqlFile: encoding: utf8 path: classpath:db/changelog/202505111914.sql + - changeSet: + id: 202505151450 + author: hrz + changes: + - sqlFile: + encoding: utf8 + path: classpath:db/changelog/202505151450.sql diff --git a/main/xiaozhi-server/core/providers/tts/custom.py b/main/xiaozhi-server/core/providers/tts/custom.py index 1efd7d7f..0b7dd04b 100644 --- a/main/xiaozhi-server/core/providers/tts/custom.py +++ b/main/xiaozhi-server/core/providers/tts/custom.py @@ -13,6 +13,7 @@ class TTSProvider(TTSProviderBase): def __init__(self, config, delete_audio_file): super().__init__(config, delete_audio_file) self.url = config.get("url") + self.method = config.get("method", "GET") self.headers = config.get("headers", {}) self.params = json.loads(config.get("params")) self.format = config.get("format", "wav") @@ -28,7 +29,10 @@ class TTSProvider(TTSProviderBase): v = v.replace("{prompt_text}", text) request_params[k] = v - resp = requests.get(self.url, params=request_params, headers=self.headers) + if self.method.upper() == "POST": + resp = requests.post(self.url, json=request_params, headers=self.headers) + else: + resp = requests.get(self.url, params=request_params, headers=self.headers) if resp.status_code == 200: with open(output_file, "wb") as file: file.write(resp.content) From 073b69f94a2780f4cc1a73c56e6f7934e95ffd6a Mon Sep 17 00:00:00 2001 From: XL Date: Thu, 15 May 2025 10:23:40 +0800 Subject: [PATCH 03/17] =?UTF-8?q?=E8=87=AA=E5=AE=9A=E4=B9=89tts=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=E5=8F=82=E6=95=B0=E8=BD=ACdict?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main/xiaozhi-server/core/providers/tts/custom.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/main/xiaozhi-server/core/providers/tts/custom.py b/main/xiaozhi-server/core/providers/tts/custom.py index 5b8669da..1efd7d7f 100644 --- a/main/xiaozhi-server/core/providers/tts/custom.py +++ b/main/xiaozhi-server/core/providers/tts/custom.py @@ -1,4 +1,5 @@ import os +import json import uuid import requests from config.logger import setup_logging @@ -13,7 +14,7 @@ class TTSProvider(TTSProviderBase): super().__init__(config, delete_audio_file) self.url = config.get("url") self.headers = config.get("headers", {}) - self.params = config.get("params") + self.params = json.loads(config.get("params")) self.format = config.get("format", "wav") self.output_file = config.get("output_dir", "tmp/") From 6dd66dbae99bc4fd01d0453c0473044f1b89ad1d Mon Sep 17 00:00:00 2001 From: XL Date: Thu, 15 May 2025 15:35:45 +0800 Subject: [PATCH 04/17] =?UTF-8?q?=E8=87=AA=E5=AE=9A=E4=B9=89tts=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=E5=8F=82=E6=95=B0=E8=BD=ACdict=20=E6=94=AF=E6=8C=81po?= =?UTF-8?q?st=E8=AF=B7=E6=B1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/db/changelog/202505151450.sql | 4 ++++ .../main/resources/db/changelog/db.changelog-master.yaml | 7 +++++++ main/xiaozhi-server/core/providers/tts/custom.py | 6 +++++- 3 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 main/manager-api/src/main/resources/db/changelog/202505151450.sql diff --git a/main/manager-api/src/main/resources/db/changelog/202505151450.sql b/main/manager-api/src/main/resources/db/changelog/202505151450.sql new file mode 100644 index 00000000..16c6a79b --- /dev/null +++ b/main/manager-api/src/main/resources/db/changelog/202505151450.sql @@ -0,0 +1,4 @@ +-- 修改自定义TTS接口请求定义 +update `ai_model_provider` set `fields` = +'[{"key":"url","label":"服务地址","type":"string"},{"key":"method","label":"请求方式","type":"string"},{"key":"params","label":"请求参数","type":"dict","dict_name":"params"},{"key":"headers","label":"请求头","type":"dict","dict_name":"headers"},{"key":"format","label":"音频格式","type":"string"},{"key":"output_dir","label":"输出目录","type":"string"}]' +where `id` = 'SYSTEM_TTS_custom'; \ No newline at end of file diff --git a/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml b/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml index ae81fd14..a350b76a 100755 --- a/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml +++ b/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml @@ -142,3 +142,10 @@ databaseChangeLog: - sqlFile: encoding: utf8 path: classpath:db/changelog/202505142037.sql + - changeSet: + id: 202505151450 + author: hrz + changes: + - sqlFile: + encoding: utf8 + path: classpath:db/changelog/202505151450.sql diff --git a/main/xiaozhi-server/core/providers/tts/custom.py b/main/xiaozhi-server/core/providers/tts/custom.py index 1efd7d7f..0b7dd04b 100644 --- a/main/xiaozhi-server/core/providers/tts/custom.py +++ b/main/xiaozhi-server/core/providers/tts/custom.py @@ -13,6 +13,7 @@ class TTSProvider(TTSProviderBase): def __init__(self, config, delete_audio_file): super().__init__(config, delete_audio_file) self.url = config.get("url") + self.method = config.get("method", "GET") self.headers = config.get("headers", {}) self.params = json.loads(config.get("params")) self.format = config.get("format", "wav") @@ -28,7 +29,10 @@ class TTSProvider(TTSProviderBase): v = v.replace("{prompt_text}", text) request_params[k] = v - resp = requests.get(self.url, params=request_params, headers=self.headers) + if self.method.upper() == "POST": + resp = requests.post(self.url, json=request_params, headers=self.headers) + else: + resp = requests.get(self.url, params=request_params, headers=self.headers) if resp.status_code == 200: with open(output_file, "wb") as file: file.write(resp.content) From eadb69bdc0e42fbacba5104d92e97a5694e7e0cd Mon Sep 17 00:00:00 2001 From: XL Date: Thu, 15 May 2025 15:46:41 +0800 Subject: [PATCH 05/17] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/resources/db/changelog/202505151450.sql | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/main/manager-api/src/main/resources/db/changelog/202505151450.sql b/main/manager-api/src/main/resources/db/changelog/202505151450.sql index 16c6a79b..0cdbcc8d 100644 --- a/main/manager-api/src/main/resources/db/changelog/202505151450.sql +++ b/main/manager-api/src/main/resources/db/changelog/202505151450.sql @@ -1,4 +1,18 @@ -- 修改自定义TTS接口请求定义 update `ai_model_provider` set `fields` = '[{"key":"url","label":"服务地址","type":"string"},{"key":"method","label":"请求方式","type":"string"},{"key":"params","label":"请求参数","type":"dict","dict_name":"params"},{"key":"headers","label":"请求头","type":"dict","dict_name":"headers"},{"key":"format","label":"音频格式","type":"string"},{"key":"output_dir","label":"输出目录","type":"string"}]' -where `id` = 'SYSTEM_TTS_custom'; \ No newline at end of file +where `id` = 'SYSTEM_TTS_custom'; + +-- 修改自定义TTS配置说明 +UPDATE `ai_model_config` SET +`doc_link` = NULL, +`remark` = '自定义TTS配置说明: +1. 支持自定义TTS接口服务 +2. 使用GET/POST方式请求 +3. 需要网络连接 +4. 输出文件保存在tmp/目录 +配置说明: +1. 在params中配置请求参数,使用JSON格式 + 例如:{"text": "{prompt_text}", "voice": "{voice}", "speed": "{speed}"} +2. 在headers中配置请求头 +3. 设置返回音频格式' WHERE `id` = 'TTS_CustomTTS'; \ No newline at end of file From d0694a0e7012877a9a602b61ab09a3ec85e80c38 Mon Sep 17 00:00:00 2001 From: XL Date: Fri, 16 May 2025 11:56:54 +0800 Subject: [PATCH 06/17] =?UTF-8?q?=E5=85=BC=E5=AE=B9yaml=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E5=92=8C=E7=AE=A1=E7=90=86=E7=AB=AF=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main/xiaozhi-server/core/providers/tts/custom.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/main/xiaozhi-server/core/providers/tts/custom.py b/main/xiaozhi-server/core/providers/tts/custom.py index 0b7dd04b..a5300a3e 100644 --- a/main/xiaozhi-server/core/providers/tts/custom.py +++ b/main/xiaozhi-server/core/providers/tts/custom.py @@ -15,10 +15,19 @@ class TTSProvider(TTSProviderBase): self.url = config.get("url") self.method = config.get("method", "GET") self.headers = config.get("headers", {}) - self.params = json.loads(config.get("params")) self.format = config.get("format", "wav") self.output_file = config.get("output_dir", "tmp/") + self.params = config.get("params") + + if isinstance(self.params, str): + try: + self.params = json.loads(self.params) + except json.JSONDecodeError: + raise ValueError("Custom TTS配置参数出错,无法将字符串解析为对象") + elif not isinstance(self.params, dict): + raise TypeError("Custom TTS配置参数出错, 请参考配置说明") + def generate_filename(self): return os.path.join(self.output_file, f"tts-{datetime.now().date()}@{uuid.uuid4().hex}.{self.format}") From a647f3c105230ee35a91fadf2cf321d5e1638fe4 Mon Sep 17 00:00:00 2001 From: CGD <3030332422@qq.com> Date: Mon, 19 May 2025 15:18:01 +0800 Subject: [PATCH 07/17] =?UTF-8?q?fix:=E8=A7=A3=E5=86=B3=E7=AC=A6=E5=8F=B7?= =?UTF-8?q?=E8=AF=AF=E8=AF=86=E5=88=AB=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main/xiaozhi-server/core/handle/helloHandle.py | 4 ++-- main/xiaozhi-server/core/handle/intentHandler.py | 5 +++-- .../xiaozhi-server/core/handle/receiveAudioHandle.py | 10 +++++----- main/xiaozhi-server/core/handle/textHandle.py | 12 ++++++------ 4 files changed, 16 insertions(+), 15 deletions(-) diff --git a/main/xiaozhi-server/core/handle/helloHandle.py b/main/xiaozhi-server/core/handle/helloHandle.py index f902a64f..e29ef450 100644 --- a/main/xiaozhi-server/core/handle/helloHandle.py +++ b/main/xiaozhi-server/core/handle/helloHandle.py @@ -40,8 +40,8 @@ async def checkWakeupWords(conn, text): if not enable_wakeup_words_response_cache: return False """检查是否是唤醒词""" - _, text = remove_punctuation_and_length(text) - if text in conn.config.get("wakeup_words"): + _, filtered_text = remove_punctuation_and_length(text) + if filtered_text in conn.config.get("wakeup_words"): await send_stt_message(conn, text) conn.tts_first_text_index = 0 conn.tts_last_text_index = 0 diff --git a/main/xiaozhi-server/core/handle/intentHandler.py b/main/xiaozhi-server/core/handle/intentHandler.py index 1cae8bab..495da96f 100644 --- a/main/xiaozhi-server/core/handle/intentHandler.py +++ b/main/xiaozhi-server/core/handle/intentHandler.py @@ -13,10 +13,11 @@ TAG = __name__ async def handle_user_intent(conn, text): # 检查是否有明确的退出命令 - if await check_direct_exit(conn, text): + filtered_text = remove_punctuation_and_length(text)[1] + if await check_direct_exit(conn, filtered_text): return True # 检查是否是唤醒词 - if await checkWakeupWords(conn, text): + if await checkWakeupWords(conn, filtered_text): return True if conn.intent_type == "function_call": diff --git a/main/xiaozhi-server/core/handle/receiveAudioHandle.py b/main/xiaozhi-server/core/handle/receiveAudioHandle.py index ee6a1d5c..9c693751 100644 --- a/main/xiaozhi-server/core/handle/receiveAudioHandle.py +++ b/main/xiaozhi-server/core/handle/receiveAudioHandle.py @@ -39,14 +39,14 @@ async def handleAudioMessage(conn, audio): if len(conn.asr_audio) < 15: conn.asr_server_receive = True else: - text, _ = await conn.asr.speech_to_text(conn.asr_audio, conn.session_id) - conn.logger.bind(tag=TAG).info(f"识别文本: {text}") - text_len, _ = remove_punctuation_and_length(text) + raw_text, _ = await conn.asr.speech_to_text(conn.asr_audio, conn.session_id) # 确保ASR模块返回原始文本 + conn.logger.bind(tag=TAG).info(f"识别文本: {raw_text}") + text_len, _ = remove_punctuation_and_length(raw_text) if text_len > 0: # 使用自定义模块进行上报 - enqueue_asr_report(conn, text, copy.deepcopy(conn.asr_audio)) + enqueue_asr_report(conn, raw_text, copy.deepcopy(conn.asr_audio)) - await startToChat(conn, text) + await startToChat(conn, raw_text) else: conn.asr_server_receive = True conn.asr_audio.clear() diff --git a/main/xiaozhi-server/core/handle/textHandle.py b/main/xiaozhi-server/core/handle/textHandle.py index 79874f1c..c7827409 100644 --- a/main/xiaozhi-server/core/handle/textHandle.py +++ b/main/xiaozhi-server/core/handle/textHandle.py @@ -42,17 +42,17 @@ async def handleTextMessage(conn, message): conn.client_have_voice = False conn.asr_audio.clear() if "text" in msg_json: - text = msg_json["text"] - _, text = remove_punctuation_and_length(text) + original_text = msg_json["text"] # 保留原始文本 + filtered_len, filtered_text = remove_punctuation_and_length(original_text) # 识别是否是唤醒词 - is_wakeup_words = text in conn.config.get("wakeup_words") + is_wakeup_words = filtered_text in conn.config.get("wakeup_words") # 是否开启唤醒词回复 enable_greeting = conn.config.get("enable_greeting", True) if is_wakeup_words and not enable_greeting: # 如果是唤醒词,且关闭了唤醒词回复,就不用回答 - await send_stt_message(conn, text) + await send_stt_message(conn, original_text) await send_tts_message(conn, "stop", None) elif is_wakeup_words: # 上报纯文字数据(复用ASR上报功能,但不提供音频数据) @@ -60,9 +60,9 @@ async def handleTextMessage(conn, message): await startToChat(conn, "嘿,你好呀") else: # 上报纯文字数据(复用ASR上报功能,但不提供音频数据) - enqueue_asr_report(conn, text, []) + enqueue_asr_report(conn, original_text, []) # 否则需要LLM对文字内容进行答复 - await startToChat(conn, text) + await startToChat(conn, original_text) elif msg_json["type"] == "iot": if "descriptors" in msg_json: asyncio.create_task(handleIotDescriptors(conn, msg_json["descriptors"])) From ebc16207f436d96d77e8d53e951135bd82f40e9d Mon Sep 17 00:00:00 2001 From: CGD <3030332422@qq.com> Date: Tue, 20 May 2025 10:54:48 +0800 Subject: [PATCH 08/17] =?UTF-8?q?update:intent=5Fllm=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E4=B8=8E=E9=80=80=E5=87=BA=E7=9B=B8=E5=85=B3=E7=9A=84=E6=8F=90?= =?UTF-8?q?=E7=A4=BA=E8=AF=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/providers/intent/intent_llm/intent_llm.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/main/xiaozhi-server/core/providers/intent/intent_llm/intent_llm.py b/main/xiaozhi-server/core/providers/intent/intent_llm/intent_llm.py index 2a3e023f..ee657420 100644 --- a/main/xiaozhi-server/core/providers/intent/intent_llm/intent_llm.py +++ b/main/xiaozhi-server/core/providers/intent/intent_llm/intent_llm.py @@ -53,6 +53,8 @@ class IntentProvider(IntentProviderBase): prompt = ( "你是一个意图识别助手。请分析用户的最后一句话,判断用户意图并调用相应的函数。\n\n" + "- 如果用户使用疑问词(如'怎么'、'为什么'、'如何')询问退出相关的问题(例如'怎么退出了?'),注意这不是让你退出,请返回 {'function_call': {'name': 'continue_chat'}\n" + "- 仅当用户明确使用'退出系统'、'结束对话'、'我不想和你说话了'等指令时,才触发 handle_exit_intent\n\n" f"{functions_desc}\n" "处理步骤:\n" "1. 分析用户输入,确定用户意图\n" From 595a338de73affa67cbaef80f1ed0ce9794482fe Mon Sep 17 00:00:00 2001 From: Sakura-RanChen <1908198662@qq.com> Date: Tue, 20 May 2025 16:57:48 +0800 Subject: [PATCH 09/17] =?UTF-8?q?=E5=A2=9E=E5=8A=A0fastgpt=E5=AD=97?= =?UTF-8?q?=E6=AE=B5=E5=A4=84=E7=90=86=EF=BC=8C=E6=90=9C=E7=B4=A2=E5=9B=BE?= =?UTF-8?q?=E6=A0=87=E4=B8=8B=E7=A7=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main/manager-web/src/components/HeaderBar.vue | 6 ++ .../src/components/ModelEditDialog.vue | 58 +++++++++++++++++-- 2 files changed, 59 insertions(+), 5 deletions(-) diff --git a/main/manager-web/src/components/HeaderBar.vue b/main/manager-web/src/components/HeaderBar.vue index 842441ce..b7b859dc 100644 --- a/main/manager-web/src/components/HeaderBar.vue +++ b/main/manager-web/src/components/HeaderBar.vue @@ -322,6 +322,12 @@ export default { line-height: 30px; } +.custom-search-input::v-deep .el-input__suffix-inner { + display: flex; + align-items: center; + height: 100%; +} + .avatar-img { width: 21px; height: 21px; diff --git a/main/manager-web/src/components/ModelEditDialog.vue b/main/manager-web/src/components/ModelEditDialog.vue index 8eebd8d0..35307d42 100644 --- a/main/manager-web/src/components/ModelEditDialog.vue +++ b/main/manager-web/src/components/ModelEditDialog.vue @@ -66,8 +66,12 @@
- + + + +
@@ -112,6 +116,7 @@ export default { pendingProviderType: null, pendingModelData: null, dynamicCallInfoFields: [], + variablesJson: '', form: { id: "", modelType: "", @@ -173,7 +178,8 @@ export default { docLink: "", remark: "", sort: 0, - configJson: {} + configJson: {}, + variablesJson: {}, }; this.dynamicCallInfoFields.forEach(field => { this.$set(this.form.configJson, field.prop, ''); @@ -203,6 +209,16 @@ export default { handleSave() { this.saving = true; // 开始保存加载 + // 确保 variables 是字典 + if (typeof this.form.configJson.variables === 'string') { + const parsed = this.validateVariablesJson(this.form.configJson.variables); + if (parsed === null) { + this.saving = false; + return; + } + this.form.configJson.variables = parsed; + } + const provideCode = this.form.configJson.type; const formData = { id: this.modelData.id, @@ -255,7 +271,7 @@ export default { this.dynamicCallInfoFields = JSON.parse(provider.fields || '[]').map(f => ({ label: f.label, prop: f.key, - type: f.type === 'password' ? 'password' : 'text', + type: f.key === 'variables' ? 'json-textarea' : (f.type === 'password' ? 'password' : 'text'), placeholder: `请输入${f.label}` })); @@ -272,7 +288,7 @@ export default { this.dynamicCallInfoFields.forEach(field => { if (!configJson.hasOwnProperty(field.prop)) { configJson[field.prop] = ''; - } else if (typeof configJson[field.prop] !== 'string') { + } else if (field.prop !== 'variables' && typeof configJson[field.prop] !== 'string') { configJson[field.prop] = String(configJson[field.prop]); } }); @@ -291,6 +307,38 @@ export default { ...configJson } }; + // 读出时的JSON转换 + if (this.form.configJson.variables) { + this.variablesJson = JSON.stringify(this.form.configJson.variables, null, 2) + } + }, + + // 输入监听 + handleVariablesChange(value) { + const parsed = this.validateVariablesJson(value); + if (parsed !== null) this.form.configJson.variables = parsed; + }, + + // 数据校验 + validateVariablesJson(value) { + try { + const parsed = JSON.parse(value); + // 校验是否为非空字典 + if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) { + return parsed; + } + this.$message.error({ + message: '必须输入字典格式(如 {"key":"value"}),保存则保留原数据', + showClose: true + }); + return null; + } catch (e) { + this.$message.error({ + message: 'JSON格式错误(如 {"key":"value"}),保存则保留原数据', + showClose: true + }); + return null; + } } } }; From 915f4cac5e166de4213c61ec1c418b6bff2e5cb2 Mon Sep 17 00:00:00 2001 From: Sakura-RanChen <1908198662@qq.com> Date: Wed, 21 May 2025 09:55:15 +0800 Subject: [PATCH 10/17] =?UTF-8?q?=E5=AF=B9=E9=80=9A=E7=94=A8dict=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=E7=9A=84=E5=AD=97=E6=AE=B5=E8=BF=9B=E8=A1=8C=E5=A4=84?= =?UTF-8?q?=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/ModelEditDialog.vue | 98 ++++++++----------- 1 file changed, 42 insertions(+), 56 deletions(-) diff --git a/main/manager-web/src/components/ModelEditDialog.vue b/main/manager-web/src/components/ModelEditDialog.vue index 35307d42..406284a5 100644 --- a/main/manager-web/src/components/ModelEditDialog.vue +++ b/main/manager-web/src/components/ModelEditDialog.vue @@ -66,13 +66,12 @@
- - - - - + +
@@ -116,7 +115,7 @@ export default { pendingProviderType: null, pendingModelData: null, dynamicCallInfoFields: [], - variablesJson: '', + fieldJsonMap: {}, // 用于存储JSON字段的字符串形式 form: { id: "", modelType: "", @@ -178,12 +177,9 @@ export default { docLink: "", remark: "", sort: 0, - configJson: {}, - variablesJson: {}, + configJson: {} }; - this.dynamicCallInfoFields.forEach(field => { - this.$set(this.form.configJson, field.prop, ''); - }); + this.fieldJsonMap = {}; }, resetProviders() { this.providers = []; @@ -209,17 +205,14 @@ export default { handleSave() { this.saving = true; // 开始保存加载 - // 确保 variables 是字典 - if (typeof this.form.configJson.variables === 'string') { - const parsed = this.validateVariablesJson(this.form.configJson.variables); - if (parsed === null) { - this.saving = false; - return; + // 处理所有JSON字段 + Object.keys(this.fieldJsonMap).forEach(key => { + const parsed = this.validateJson(this.fieldJsonMap[key]); + if (parsed !== null) { + this.form.configJson[key] = parsed; } - this.form.configJson.variables = parsed; - } + }); - const provideCode = this.form.configJson.type; const formData = { id: this.modelData.id, modelCode: this.form.modelCode, @@ -229,13 +222,11 @@ export default { docLink: this.form.docLink, remark: this.form.remark, sort: this.form.sort || 0, - configJson: { - ...this.form.configJson, - } + configJson: { ...this.form.configJson } }; this.$emit("save", { - provideCode, + provideCode: this.form.configJson.type, formData, done: () => { this.saving = false; // 保存完成后回调 @@ -256,7 +247,6 @@ export default { value: String(item.providerCode) })); this.providersLoaded = true; - this.allProvidersData = data; if (this.pendingProviderType) { @@ -271,7 +261,7 @@ export default { this.dynamicCallInfoFields = JSON.parse(provider.fields || '[]').map(f => ({ label: f.label, prop: f.key, - type: f.key === 'variables' ? 'json-textarea' : (f.type === 'password' ? 'password' : 'text'), + type: f.type === 'dict' ? 'json-textarea' : (f.type === 'password' ? 'password' : 'text'), placeholder: `请输入${f.label}` })); @@ -288,7 +278,10 @@ export default { this.dynamicCallInfoFields.forEach(field => { if (!configJson.hasOwnProperty(field.prop)) { configJson[field.prop] = ''; - } else if (field.prop !== 'variables' && typeof configJson[field.prop] !== 'string') { + } else if (field.type === 'json-textarea') { + this.$set(this.fieldJsonMap, field.prop, this.formatJson(configJson[field.prop])); + configJson[field.prop] = this.ensureObject(configJson[field.prop]); + } else if (typeof configJson[field.prop] !== 'string') { configJson[field.prop] = String(configJson[field.prop]); } }); @@ -303,42 +296,43 @@ export default { docLink: model.docLink, remark: model.remark, sort: Number(model.sort) || 0, - configJson: { - ...configJson - } + configJson: { ...configJson } }; - // 读出时的JSON转换 - if (this.form.configJson.variables) { - this.variablesJson = JSON.stringify(this.form.configJson.variables, null, 2) + }, + handleJsonChange(field, value) { + const parsed = this.validateJson(value); + if (parsed !== null) { + this.form.configJson[field] = parsed; } }, - - // 输入监听 - handleVariablesChange(value) { - const parsed = this.validateVariablesJson(value); - if (parsed !== null) this.form.configJson.variables = parsed; - }, - - // 数据校验 - validateVariablesJson(value) { + validateJson(value) { try { const parsed = JSON.parse(value); - // 校验是否为非空字典 if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) { return parsed; } this.$message.error({ - message: '必须输入字典格式(如 {"key":"value"}),保存则保留原数据', + message: '必须输入字典格式(如 {"key":"value"}),保存则使用原数据', showClose: true }); return null; } catch (e) { this.$message.error({ - message: 'JSON格式错误(如 {"key":"value"}),保存则保留原数据', + message: 'JSON格式错误(如 {"key":"value"}),保存则使用原数据', showClose: true }); return null; } + }, + formatJson(obj) { + try { + return JSON.stringify(obj, null, 2); + } catch { + return ''; + } + }, + ensureObject(value) { + return typeof value === 'object' ? value : {}; } } }; @@ -364,7 +358,6 @@ export default { justify-content: center; } - .custom-close-btn { position: absolute; top: 20px; @@ -500,21 +493,14 @@ export default { height: 32px; } - .custom-form .el-form-item { margin-bottom: 20px; } - -.custom-input-bg .el-input__inner { - height: 32px; -} - - .custom-form .el-form-item__label { color: #3d4566; font-weight: normal; text-align: right; padding-right: 20px; } - + \ No newline at end of file From 241d96f6ac02b87cb27aefb77c1b56479c46036c Mon Sep 17 00:00:00 2001 From: CGD <3030332422@qq.com> Date: Wed, 21 May 2025 18:17:08 +0800 Subject: [PATCH 11/17] =?UTF-8?q?update:=E4=BF=AE=E5=A4=8Dintent=5Fllm?= =?UTF-8?q?=E8=AF=86=E5=88=AB=E6=84=8F=E5=9B=BE=E4=B8=8D=E7=B2=BE=E5=87=86?= =?UTF-8?q?=E9=97=AE=E9=A2=98=EF=BC=8Chandle=5Fdevice=E6=94=B9=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/handle/functionHandler.py | 2 +- ..._device.py => handle_speaker_or_screen.py} | 27 ++++++++++++------- 2 files changed, 19 insertions(+), 10 deletions(-) rename main/xiaozhi-server/plugins_func/functions/{handle_device.py => handle_speaker_or_screen.py} (70%) diff --git a/main/xiaozhi-server/core/handle/functionHandler.py b/main/xiaozhi-server/core/handle/functionHandler.py index b09a369c..9f6bec26 100644 --- a/main/xiaozhi-server/core/handle/functionHandler.py +++ b/main/xiaozhi-server/core/handle/functionHandler.py @@ -54,7 +54,7 @@ class FunctionHandler: self.function_registry.register_function("plugin_loader") self.function_registry.register_function("get_time") self.function_registry.register_function("get_lunar") - self.function_registry.register_function("handle_device") + self.function_registry.register_function("handle_speaker_volume_or_screen_brightness") def register_config_functions(self): """注册配置中的函数,可以不同客户端使用不同的配置""" diff --git a/main/xiaozhi-server/plugins_func/functions/handle_device.py b/main/xiaozhi-server/plugins_func/functions/handle_speaker_or_screen.py similarity index 70% rename from main/xiaozhi-server/plugins_func/functions/handle_device.py rename to main/xiaozhi-server/plugins_func/functions/handle_speaker_or_screen.py index 70902c55..fa357ff5 100644 --- a/main/xiaozhi-server/plugins_func/functions/handle_device.py +++ b/main/xiaozhi-server/plugins_func/functions/handle_speaker_or_screen.py @@ -54,20 +54,29 @@ def _handle_device_action(conn, func, success_message, error_message, *args, **k handle_device_function_desc = { "type": "function", "function": { - "name": "handle_device", + "name": "handle_speaker_volume_or_screen_brightness", "description": ( - "用户想要获取或者设置设备的音量/亮度大小,或者用户觉得声音/亮度过高或过低,或者用户想提高或降低音量/亮度。" - "比如用户说现在亮度多少,参数为:device_type:Screen,action:get。" - "比如用户说设置音量为50,参数为:device_type:Speaker,action:set,value:50。" - "比如用户说亮度太高了,参数为:device_type:Screen,action:lower。" - "比如用户说调大音量,参数为:device_type:Speaker,action:raise。" + "用户想要获取或者设置设备的音量/亮度大小,或者用户觉得声音/亮度过高或过低,或者用户想提高或降低音量/亮度。\n" + "**严格限制**:仅当用户明确操作 **Speaker(音量)或Screen(亮度)** 时才能调用此函数!\n" + "对于其他设备(如AC、Battery、Switch等),请不要调用此函数,而是继续正常的对话。\n\n" + "示例:\n" + "- 用户说『现在亮度多少』 → 调用函数:device_type: Screen, action: get\n" + "- 用户说『设置音量为50』 → 调用函数:device_type: Speaker, action: set, value: 50\n" + "- 用户说『亮度太高了』 → 调用函数:device_type: Screen, action: lower\n" + "- 用户说『调大音量』 → 调用函数:device_type: Speaker, action: raise\n\n" + "**拒绝调用示例**(应继续对话而非调用本函数):\n" + "- 用户说『空调调低一度』 → 不调用(设备类型为AC)\n" + "- 用户说『开关灯』 → 不调用(设备类型为Switch)\n" + "- 用户说『电量多少』 → 不调用(设备类型为Battery)\n" ), "parameters": { "type": "object", "properties": { "device_type": { "type": "string", - "description": "设备类型,可选值:Speaker(音量),Screen(亮度)" + "description": "设备类型,**严格限定为Speaker(音量)或Screen(亮度)**,其他设备类型禁止调用此函数", + "enum": ["Speaker", "Screen"] + }, "action": { "type": "string", @@ -84,8 +93,8 @@ handle_device_function_desc = { } -@register_function('handle_device', handle_device_function_desc, ToolType.IOT_CTL) -def handle_device(conn, device_type: str, action: str, value: int = None): +@register_function('handle_speaker_volume_or_screen_brightness', handle_device_function_desc, ToolType.IOT_CTL) +def handle_speaker_volume_or_screen_brightness(conn, device_type: str, action: str, value: int = None): if device_type == "Speaker": method_name, property_name, device_name = "SetVolume", "volume", "音量" elif device_type == "Screen": From 76342b09ffd901fff8f0234139a69db6ac5f3036 Mon Sep 17 00:00:00 2001 From: hrz <1710360675@qq.com> Date: Wed, 21 May 2025 18:35:34 +0800 Subject: [PATCH 12/17] =?UTF-8?q?fix:=E4=BF=AE=E5=A4=8D=E5=85=A8=E5=B1=80?= =?UTF-8?q?=E5=8F=98=E9=87=8Fall=5Ffunction=5Fregistry=E5=AF=BC=E8=87=B4io?= =?UTF-8?q?t=E6=8F=8F=E8=BF=B0=E6=97=A0=E6=B3=95=E6=9B=B4=E6=96=B0bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main/xiaozhi-server/app.py | 2 +- .../core/handle/functionHandler.py | 9 ++++- main/xiaozhi-server/core/handle/iotHandle.py | 33 +++++++++++-------- main/xiaozhi-server/plugins_func/register.py | 21 ++++++++++-- 4 files changed, 46 insertions(+), 19 deletions(-) diff --git a/main/xiaozhi-server/app.py b/main/xiaozhi-server/app.py index 619fb5c7..63849c58 100644 --- a/main/xiaozhi-server/app.py +++ b/main/xiaozhi-server/app.py @@ -102,7 +102,7 @@ async def main(): await asyncio.wait( [stdin_task, ws_task, ota_task] if ota_task else [stdin_task, ws_task], timeout=3.0, - return_when=asyncio.ALL_COMPLETED + return_when=asyncio.ALL_COMPLETED, ) print("服务器已关闭,程序退出。") diff --git a/main/xiaozhi-server/core/handle/functionHandler.py b/main/xiaozhi-server/core/handle/functionHandler.py index b09a369c..33e950b4 100644 --- a/main/xiaozhi-server/core/handle/functionHandler.py +++ b/main/xiaozhi-server/core/handle/functionHandler.py @@ -1,6 +1,12 @@ from config.logger import setup_logging import json -from plugins_func.register import FunctionRegistry, ActionResponse, Action, ToolType +from plugins_func.register import ( + FunctionRegistry, + ActionResponse, + Action, + ToolType, + DeviceTypeRegistry, +) from plugins_func.functions.hass_init import append_devices_to_prompt TAG = __name__ @@ -10,6 +16,7 @@ class FunctionHandler: def __init__(self, conn): self.conn = conn self.config = conn.config + self.device_type_registry = DeviceTypeRegistry() self.function_registry = FunctionRegistry() self.register_nessary_functions() self.register_config_functions() diff --git a/main/xiaozhi-server/core/handle/iotHandle.py b/main/xiaozhi-server/core/handle/iotHandle.py index 5b1bb9bb..5cf2d204 100644 --- a/main/xiaozhi-server/core/handle/iotHandle.py +++ b/main/xiaozhi-server/core/handle/iotHandle.py @@ -1,9 +1,8 @@ import json import asyncio -from config.logger import setup_logging from plugins_func.register import ( - device_type_registry, - register_function, + FunctionItem, + register_device_function, ActionResponse, Action, ToolType, @@ -177,7 +176,7 @@ class IotDescriptor: self.methods.append(method) -def register_device_type(descriptor): +def register_device_type(descriptor, device_type_registry): """注册设备类型及其功能""" device_name = descriptor["name"] type_id = device_type_registry.generate_device_type_id(descriptor) @@ -213,10 +212,12 @@ def register_device_type(descriptor): }, } query_func = create_iot_query_function(device_name, prop_name, prop_info) - decorated_func = register_function(func_name, func_desc, ToolType.IOT_CTL)( - query_func + decorated_func = register_device_function( + func_name, func_desc, ToolType.IOT_CTL + )(query_func) + functions[func_name] = FunctionItem( + func_name, func_desc, decorated_func, ToolType.IOT_CTL ) - functions[func_name] = decorated_func # 为每个方法创建控制函数 for method_name, method_info in descriptor["methods"].items(): @@ -267,10 +268,12 @@ def register_device_type(descriptor): }, } control_func = create_iot_function(device_name, method_name, method_info) - decorated_func = register_function(func_name, func_desc, ToolType.IOT_CTL)( - control_func + decorated_func = register_device_function( + func_name, func_desc, ToolType.IOT_CTL + )(control_func) + functions[func_name] = FunctionItem( + func_name, func_desc, decorated_func, ToolType.IOT_CTL ) - functions[func_name] = decorated_func device_type_registry.register_device_type(type_id, functions) return type_id @@ -289,7 +292,6 @@ async def handleIotDescriptors(conn, descriptors): functions_changed = False for descriptor in descriptors: - # 如果descriptor没有properties和methods,则直接跳过 if "properties" not in descriptor and "methods" not in descriptor: continue @@ -319,13 +321,16 @@ async def handleIotDescriptors(conn, descriptors): if conn.load_function_plugin: # 注册或获取设备类型 - type_id = register_device_type(descriptor) + device_type_registry = conn.func_handler.device_type_registry + type_id = register_device_type(descriptor, device_type_registry) device_functions = device_type_registry.get_device_functions(type_id) # 在连接级注册设备函数 if hasattr(conn, "func_handler"): - for func_name in device_functions: - conn.func_handler.function_registry.register_function(func_name) + for func_name, func_item in device_functions.items(): + conn.func_handler.function_registry.register_function( + func_name, func_item + ) conn.logger.bind(tag=TAG).info( f"注册IOT函数到function handler: {func_name}" ) diff --git a/main/xiaozhi-server/plugins_func/register.py b/main/xiaozhi-server/plugins_func/register.py index b96f2f29..873c61e1 100644 --- a/main/xiaozhi-server/plugins_func/register.py +++ b/main/xiaozhi-server/plugins_func/register.py @@ -77,7 +77,6 @@ class DeviceTypeRegistry: # 初始化函数注册字典 all_function_registry = {} -device_type_registry = DeviceTypeRegistry() def register_function(name, desc, type=None): @@ -91,13 +90,29 @@ def register_function(name, desc, type=None): return decorator +def register_device_function(name, desc, type=None): + """注册设备级别的函数到函数注册字典的装饰器""" + + def decorator(func): + logger.bind(tag=TAG).debug(f"设备函数 '{name}' 已加载") + return func + + return decorator + + class FunctionRegistry: def __init__(self): self.function_registry = {} self.logger = setup_logging() - def register_function(self, name): - # 查找all_function_registry中是否有对应的函数 + def register_function(self, name, func_item=None): + # 如果提供了func_item,直接注册 + if func_item: + self.function_registry[name] = func_item + self.logger.bind(tag=TAG).debug(f"函数 '{name}' 直接注册成功") + return func_item + + # 否则从all_function_registry中查找 func = all_function_registry.get(name) if not func: self.logger.bind(tag=TAG).error(f"函数 '{name}' 未找到") From b78d337fd0f98d02561148cc60c1b39d42ac421a Mon Sep 17 00:00:00 2001 From: caixypromise Date: Thu, 22 May 2025 00:49:56 +0800 Subject: [PATCH 13/17] =?UTF-8?q?chore:=20=E6=9C=8D=E5=8A=A1=E7=AB=AF?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E6=8E=A5=E5=8F=A3=E4=BB=A3=E7=A0=81=E5=81=A5?= =?UTF-8?q?=E5=A3=AE=E6=80=A7=E4=BC=98=E5=8C=96=EF=BC=8C=E8=A1=A5=E5=85=85?= =?UTF-8?q?=E6=B3=A8=E9=87=8A=E4=B8=8E=E5=88=A0=E9=99=A4=E7=BD=B2=E5=90=8D?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ServerSideManageController.java | 15 ++++++++------- .../modules/sys/dto/EmitSeverActionDTO.java | 3 --- .../modules/sys/dto/ServerActionPayloadDTO.java | 3 --- .../modules/sys/enums/ServerActionEnum.java | 3 --- .../sys/enums/ServerActionResponseEnum.java | 3 --- 5 files changed, 8 insertions(+), 19 deletions(-) diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/controller/ServerSideManageController.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/controller/ServerSideManageController.java index a9664ced..0b6b6a5b 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/controller/ServerSideManageController.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/controller/ServerSideManageController.java @@ -1,9 +1,6 @@ package xiaozhi.modules.sys.controller; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.UUID; +import java.util.*; import java.util.concurrent.TimeUnit; import org.apache.commons.lang3.StringUtils; @@ -38,7 +35,7 @@ import xiaozhi.modules.sys.utils.WebSocketClientManager; * 服务端管理控制器 */ @RestController -@RequestMapping("admin/server") +@RequestMapping("/admin/server") @Tag(name = "服务端管理") @AllArgsConstructor public class ServerSideManageController { @@ -46,6 +43,7 @@ public class ServerSideManageController { private static final ObjectMapper objectMapper; static { objectMapper = new ObjectMapper(); + // 忽略json字符串中存在,但pojo中不存在对应字段的情况 objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); } @@ -53,8 +51,11 @@ public class ServerSideManageController { @GetMapping("/server-list") @RequiresPermissions("sys:role:superAdmin") public Result> getWsServerList() { - String cachedWs = sysParamsService.getValue(Constant.SERVER_WEBSOCKET, true); - return new Result>().ok(Arrays.asList(cachedWs.split(";"))); + String wsText = sysParamsService.getValue(Constant.SERVER_WEBSOCKET, true); + if (StringUtils.isBlank(wsText)) { + return new Result>().ok(Collections.emptyList()); + } + return new Result>().ok(Arrays.asList(wsText.split(";"))); } @Operation(summary = "通知python服务端更新配置") diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/EmitSeverActionDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/EmitSeverActionDTO.java index 888ef4ec..6de89aab 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/EmitSeverActionDTO.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/EmitSeverActionDTO.java @@ -10,9 +10,6 @@ import xiaozhi.modules.sys.enums.ServerActionEnum; /** * 发送python服务端操作DTO - * - * @Author CAIXYPROMISE - * @since 2025/5/15 4:55 */ @Data @NoArgsConstructor diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/ServerActionPayloadDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/ServerActionPayloadDTO.java index cb158d25..fb1427f9 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/ServerActionPayloadDTO.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/ServerActionPayloadDTO.java @@ -7,9 +7,6 @@ import java.util.Map; /** * 服务端动作DTO - * - * @Author CAIXYPROMISE - * @since 2025/5/15 5:10 */ @Data public class ServerActionPayloadDTO diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/enums/ServerActionEnum.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/enums/ServerActionEnum.java index 33babf45..94e2672f 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/enums/ServerActionEnum.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/enums/ServerActionEnum.java @@ -6,9 +6,6 @@ import xiaozhi.common.exception.RenException; /** * 服务端动作枚举 - * - * @Author CAIXYPROMISE - * @since 2025/5/15 4:57 */ public enum ServerActionEnum { diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/enums/ServerActionResponseEnum.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/enums/ServerActionResponseEnum.java index e1b54a66..9e4c8307 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/enums/ServerActionResponseEnum.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/enums/ServerActionResponseEnum.java @@ -7,9 +7,6 @@ import org.apache.commons.lang3.StringUtils; /** * 服务端调用响应枚举 - * - * @Author CAIXYPROMISE - * @since 2025/5/15 5:50 */ public enum ServerActionResponseEnum { From 46a15082d6085003ba469774f041105c0a37337e Mon Sep 17 00:00:00 2001 From: hrz <1710360675@qq.com> Date: Thu, 22 May 2025 10:41:56 +0800 Subject: [PATCH 14/17] =?UTF-8?q?update:=E4=BC=98=E5=8C=96=E6=8C=89?= =?UTF-8?q?=E9=92=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main/manager-web/src/components/DeviceItem.vue | 2 +- main/manager-web/src/components/HeaderBar.vue | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/main/manager-web/src/components/DeviceItem.vue b/main/manager-web/src/components/DeviceItem.vue index 0ecbcefa..eeab90a1 100644 --- a/main/manager-web/src/components/DeviceItem.vue +++ b/main/manager-web/src/components/DeviceItem.vue @@ -28,7 +28,7 @@
- + 聊天记录 聊天记录 diff --git a/main/manager-web/src/components/HeaderBar.vue b/main/manager-web/src/components/HeaderBar.vue index bb5baca5..70b64f61 100644 --- a/main/manager-web/src/components/HeaderBar.vue +++ b/main/manager-web/src/components/HeaderBar.vue @@ -35,10 +35,11 @@ OTA管理
+ :class="{ 'active-tab': $route.path === '/dict-management' || $route.path === '/params-management' || $route.path === '/provider-management' || $route.path === '/server-side-management' }" + @visible-change="handleParamDropdownVisibleChange"> + :style="{ filter: $route.path === '/dict-management' || $route.path === '/params-management' || $route.path === '/provider-management' || $route.path === '/server-side-management' ? 'brightness(0) invert(1)' : 'None' }" /> 参数字典 From 1ad8aeeaa711058d7658297a001a759172105943 Mon Sep 17 00:00:00 2001 From: hrz <1710360675@qq.com> Date: Thu, 22 May 2025 11:22:16 +0800 Subject: [PATCH 15/17] =?UTF-8?q?add=EF=BC=9ACustomTTS=E6=8F=90=E4=BE=9B?= =?UTF-8?q?=E4=B8=80=E4=B8=AA=E7=A4=BA=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main/xiaozhi-server/config.yaml | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/main/xiaozhi-server/config.yaml b/main/xiaozhi-server/config.yaml index 05756cd7..44845df8 100644 --- a/main/xiaozhi-server/config.yaml +++ b/main/xiaozhi-server/config.yaml @@ -675,17 +675,24 @@ TTS: speed: 1 output_dir: tmp/ CustomTTS: - # 自定义的TTS接口服务,请求参数可自定义 - # 要求接口使用GET方式请求,并返回音频文件 + # 自定义的TTS接口服务,请求参数可自定义,可接入众多TTS服务 + # 以本地部署的KokoroTTS为例 + # 如果只有cpu运行:docker run -p 8880:8880 ghcr.io/remsky/kokoro-fastapi-cpu:latest + # 如果只有gpu运行:docker run --gpus all -p 8880:8880 ghcr.io/remsky/kokoro-fastapi-gpu:latest + # 要求接口使用POST方式请求,并返回音频文件 type: custom - url: "http://127.0.0.1:9880/tts" + method: POST + url: "http://127.0.0.1:8880/v1/audio/speech" params: # 自定义请求参数 - # text: "{prompt_text}" # {prompt_text}会被替换为实际的提示词内容 - # speaker: jok老师 - # speed: 1 - # foo: bar - # testabc: 123456 + input: "{prompt_text}" + response_format: "mp3" + download_format: "mp3" + voice: "zf_xiaoxiao" + lang_code: "z" + return_download_link: true + speed: 1 + stream: false headers: # 自定义请求头 # Authorization: Bearer xxxx - format: wav # 接口返回的音频格式 + format: mp3 # 接口返回的音频格式 output_dir: tmp/ \ No newline at end of file From 2ecce6485cad764d7ed10fbcf4bd6230794ed65b Mon Sep 17 00:00:00 2001 From: hrz <1710360675@qq.com> Date: Thu, 22 May 2025 11:26:00 +0800 Subject: [PATCH 16/17] =?UTF-8?q?update:=E5=A2=9E=E5=8A=A0=E8=87=AA?= =?UTF-8?q?=E5=AE=9A=E4=B9=89=E7=9A=84TTS=E6=8E=A5=E5=8F=A3=E6=9C=8D?= =?UTF-8?q?=E5=8A=A1=E7=9A=84=E4=BD=BF=E7=94=A8=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../changelog/{202505151450.sql => 202505151451.sql} | 10 +++++----- .../resources/db/changelog/db.changelog-master.yaml | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) rename main/manager-api/src/main/resources/db/changelog/{202505151450.sql => 202505151451.sql} (60%) diff --git a/main/manager-api/src/main/resources/db/changelog/202505151450.sql b/main/manager-api/src/main/resources/db/changelog/202505151451.sql similarity index 60% rename from main/manager-api/src/main/resources/db/changelog/202505151450.sql rename to main/manager-api/src/main/resources/db/changelog/202505151451.sql index 0cdbcc8d..601de457 100644 --- a/main/manager-api/src/main/resources/db/changelog/202505151450.sql +++ b/main/manager-api/src/main/resources/db/changelog/202505151451.sql @@ -7,12 +7,12 @@ where `id` = 'SYSTEM_TTS_custom'; UPDATE `ai_model_config` SET `doc_link` = NULL, `remark` = '自定义TTS配置说明: -1. 支持自定义TTS接口服务 -2. 使用GET/POST方式请求 -3. 需要网络连接 -4. 输出文件保存在tmp/目录 +1. 自定义的TTS接口服务,请求参数可自定义,可接入众多TTS服务 +2. 以本地部署的KokoroTTS为例 +3. 如果只有cpu运行:docker run -p 8880:8880 ghcr.io/remsky/kokoro-fastapi-cpu:latest +4. 如果只有gpu运行:docker run --gpus all -p 8880:8880 ghcr.io/remsky/kokoro-fastapi-gpu:latest 配置说明: 1. 在params中配置请求参数,使用JSON格式 - 例如:{"text": "{prompt_text}", "voice": "{voice}", "speed": "{speed}"} + 例如KokoroTTS:{ "input": "{prompt_text}", "speed": 1, "voice": "zm_yunxi", "stream": true, "download_format": "mp3", "response_format": "mp3", "return_download_link": true } 2. 在headers中配置请求头 3. 设置返回音频格式' WHERE `id` = 'TTS_CustomTTS'; \ No newline at end of file diff --git a/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml b/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml index add92bc5..9d3808da 100755 --- a/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml +++ b/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml @@ -157,9 +157,9 @@ databaseChangeLog: encoding: utf8 path: classpath:db/changelog/202505201744.sql - changeSet: - id: 202505151450 + id: 202505151451 author: hsoftxl changes: - sqlFile: encoding: utf8 - path: classpath:db/changelog/202505151450.sql \ No newline at end of file + path: classpath:db/changelog/202505151451.sql \ No newline at end of file From a5f74d7767fc26d36d623605a7d773bcf9f187d0 Mon Sep 17 00:00:00 2001 From: hrz <1710360675@qq.com> Date: Thu, 22 May 2025 22:22:43 +0800 Subject: [PATCH 17/17] =?UTF-8?q?update:=E6=9B=B4=E6=96=B0=E8=A7=86?= =?UTF-8?q?=E9=A2=91=E6=95=99=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 4b3215f6..27c8c759 100644 --- a/README.md +++ b/README.md @@ -142,10 +142,11 @@ #### 🚀 部署方式选择 -| 部署方式 | 特点 | 适用场景 | Docker部署文档 | 源码部署文档 | -|---------|------|---------|---------|---------| -| **最简化安装** | 智能对话、IOT功能,数据存储在配置文件 | 低配置环境,无需数据库 | [Docker只运行Server](./docs/Deployment.md#%E6%96%B9%E5%BC%8F%E4%B8%80docker%E5%8F%AA%E8%BF%90%E8%A1%8Cserver) | [本地源码只运行Server](./docs/Deployment.md#%E6%96%B9%E5%BC%8F%E4%BA%8C%E6%9C%AC%E5%9C%B0%E6%BA%90%E7%A0%81%E5%8F%AA%E8%BF%90%E8%A1%8Cserver)| -| **全模块安装** | 智能对话、IOT、OTA、智控台,数据存储在数据库 | 完整功能体验 |[Docker运行全模块](./docs/Deployment_all.md#%E6%96%B9%E5%BC%8F%E4%B8%80docker%E8%BF%90%E8%A1%8C%E5%85%A8%E6%A8%A1%E5%9D%97) | [本地源码运行全模块](./docs/Deployment_all.md#%E6%96%B9%E5%BC%8F%E4%BA%8C%E6%9C%AC%E5%9C%B0%E6%BA%90%E7%A0%81%E8%BF%90%E8%A1%8C%E5%85%A8%E6%A8%A1%E5%9D%97) | +| 部署方式 | 特点 | 适用场景 | Docker部署文档 | 源码部署文档 | 视频教程 | +|---------|------|---------|---------|---------|---------| +| **最简化安装** | 智能对话、IOT功能,数据存储在配置文件 | 低配置环境,无需数据库 | [Docker只运行Server](./docs/Deployment.md#%E6%96%B9%E5%BC%8F%E4%B8%80docker%E5%8F%AA%E8%BF%90%E8%A1%8Cserver) | [本地源码只运行Server](./docs/Deployment.md#%E6%96%B9%E5%BC%8F%E4%BA%8C%E6%9C%AC%E5%9C%B0%E6%BA%90%E7%A0%81%E5%8F%AA%E8%BF%90%E8%A1%8Cserver)| - | +| **全模块安装** | 智能对话、IOT、OTA、智控台,数据存储在数据库 | 完整功能体验 |[Docker运行全模块](./docs/Deployment_all.md#%E6%96%B9%E5%BC%8F%E4%B8%80docker%E8%BF%90%E8%A1%8C%E5%85%A8%E6%A8%A1%E5%9D%97) | [本地源码运行全模块](./docs/Deployment_all.md#%E6%96%B9%E5%BC%8F%E4%BA%8C%E6%9C%AC%E5%9C%B0%E6%BA%90%E7%A0%81%E8%BF%90%E8%A1%8C%E5%85%A8%E6%A8%A1%E5%9D%97) | [本地源码视频教程](https://www.bilibili.com/video/BV1wBJhz4Ewe) | + > 💡 提示:以下是按最新代码部署后的测试平台,有需要可烧录测试,并发为6个,每天会清空数据