From 0c0386f4bad4188c84af4d967eaad8fe68e34791 Mon Sep 17 00:00:00 2001 From: caixypromise Date: Sun, 4 May 2025 22:53:30 +0800 Subject: [PATCH 01/12] =?UTF-8?q?feature:=20=E6=96=B0=E5=A2=9EGemini?= =?UTF-8?q?=E6=A8=A1=E5=9E=8B=E6=94=AF=E6=8C=81function=5Fcall=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=EF=BC=8C=E6=94=AF=E6=8C=81=E5=9C=A8=E4=BB=A3=E7=90=86?= =?UTF-8?q?=E6=A8=A1=E5=BC=8F=E4=B8=8B=E4=BD=BF=E7=94=A8=E6=B5=81=E5=BC=8F?= =?UTF-8?q?stream=E5=AF=B9=E8=AF=9D=E6=A8=A1=E5=BC=8F=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/providers/llm/gemini/gemini.py | 243 +++++++++--------- 1 file changed, 118 insertions(+), 125 deletions(-) diff --git a/main/xiaozhi-server/core/providers/llm/gemini/gemini.py b/main/xiaozhi-server/core/providers/llm/gemini/gemini.py index a91a6bf0..16f8d11a 100644 --- a/main/xiaozhi-server/core/providers/llm/gemini/gemini.py +++ b/main/xiaozhi-server/core/providers/llm/gemini/gemini.py @@ -1,140 +1,133 @@ -import google.generativeai as genai -from core.utils.util import check_model_key -from core.providers.llm.base import LLMProviderBase -from config.logger import setup_logging -import requests -import json +# core/providers/llm/gemini_sdk.py +import os, json, uuid +from types import SimpleNamespace +from typing import Any, Dict, List +from google import generativeai as genai +from google.generativeai import types, GenerationConfig + +from core.providers.llm.base import LLMProviderBase +from core.utils.util import check_model_key +from config.logger import setup_logging +from google.generativeai.types import GenerateContentResponse + +log = setup_logging() TAG = __name__ -logger = setup_logging() class LLMProvider(LLMProviderBase): - def __init__(self, config): - """初始化Gemini LLM Provider""" - self.model_name = config.get("model_name", "gemini-1.5-pro") - self.api_key = config.get("api_key") - self.http_proxy = config.get("http_proxy") - self.https_proxy = config.get("https_proxy") - have_key = check_model_key("LLM", self.api_key) + def __init__(self, cfg: Dict[str, Any]): + self.model_name = cfg.get("model_name", "gemini-2.0-flash") + self.api_key = cfg["api_key"] + proxy = cfg.get("https_proxy") or cfg.get("http_proxy") - if not have_key: - return + if not check_model_key("LLM", self.api_key): + raise ValueError("无效的Gemini API Key,请检查是否配置正确") - try: - # 初始化Gemini客户端 - # 配置代理(如果提供了代理配置) - self.proxies = None - if self.http_proxy is not "" or self.https_proxy is not "": + if proxy: + os.environ["HTTPS_PROXY"] = os.environ["HTTP_PROXY"] = proxy + log.bind(tag=TAG).info(f"Gemini 代理地址: {proxy}") - self.proxies = { - "http": self.http_proxy, - "https": self.https_proxy, - } - logger.bind(tag=TAG).info(f"Gemini set proxys:{self.proxies}") - # 使用猴子补丁修改 google-generativeai 库的请求会话 + genai.configure(api_key=self.api_key) + self.model = genai.GenerativeModel(self.model_name) - # 使用 session 对象配置 genai + self.gen_cfg = GenerationConfig( + temperature=0.7, + top_p=0.9, + top_k=40, + max_output_tokens=2048, + ) - genai.configure(api_key=self.api_key) - self.model = genai.GenerativeModel(self.model_name) - # 设置生成参数 - self.generation_config = { - "temperature": 0.7, - "top_p": 0.9, - "top_k": 40, - "max_output_tokens": 2048, - } - self.chat = None - except Exception as e: - logger.bind(tag=TAG).error(f"Gemini初始化失败: {e}") - self.model = None + @staticmethod + def _build_tools(funcs: List[Dict[str, Any]] | None): + if not funcs: + return None + return [types.Tool(function_declarations=[ + types.FunctionDeclaration( + name=f["function"]["name"], + description=f["function"]["description"], + parameters=f["function"]["parameters"], + ) + for f in funcs + ])] + # Gemini文档提到,无需维护session-id,直接用dialogue拼接而成 def response(self, session_id, dialogue): - """生成Gemini对话响应""" - if not self.model: - yield "【Gemini服务未正确初始化】" - return - - try: - # 处理对话历史 - chat_history = [] - for msg in dialogue[:-1]: # 历史对话 - role = "model" if msg["role"] == "assistant" else "user" - content = msg["content"].strip() - if content: - chat_history.append({"role": role, "parts": [{"text": content}]}) - - # 获取当前消息 - current_msg = dialogue[-1]["content"] - - # 构建请求体 - request_body = { - "contents": chat_history - + [{"role": "user", "parts": [{"text": current_msg}]}], - "generationConfig": self.generation_config, - } - - # 构建请求URL - url = f"https://generativelanguage.googleapis.com/v1beta/models/{self.model_name}:generateContent?key={self.api_key}" - - # 构建请求头 - headers = { - "Content-Type": "application/json", - } - - # 发送POST请求,经测试手动 request 无法使用 stream 模式 - if self.proxies: - response = requests.post( - url, - headers=headers, - json=request_body, - stream=False, - proxies=self.proxies, - ) - try: - data = response.json() # 直接解析JSON - if "candidates" in data and data["candidates"]: - yield data["candidates"][0]["content"]["parts"][0]["text"] - else: - yield "未找到候选回复。" - except json.JSONDecodeError as e: - yield f"JSON解码错误:{e}" - except Exception as e: - yield f"发生错误:{e}" - else: - logger.bind(tag=TAG).info(f"Gemini stream mode ") - chat = self.model.start_chat(history=chat_history) - - # 发送消息并获取流式响应 - response = chat.send_message( - current_msg, stream=True, generation_config=self.generation_config - ) - # 处理流式响应 - for chunk in response: - if hasattr(chunk, "text") and chunk.text: - yield chunk.text - - except Exception as e: - error_msg = str(e) - logger.bind(tag=TAG).error(f"Gemini响应生成错误: {error_msg}") - - # 针对不同错误返回友好提示 - if "Rate limit" in error_msg: - yield "【Gemini服务请求太频繁,请稍后再试】" - elif "Invalid API key" in error_msg: - yield "【Gemini API key无效】" - else: - yield f"【Gemini服务响应异常: {error_msg}】" - - except requests.exceptions.RequestException as e: - yield f"请求失败:{e}" - except json.JSONDecodeError as e: - yield f"JSON解码错误:{e}" - except Exception as e: - yield f"发生错误:{e}" + yield from self._generate(dialogue, None) def response_with_functions(self, session_id, dialogue, functions=None): - logger.bind(tag=TAG).info(f"gemini暂未实现完整的工具调用(function call)") - return self.response(session_id, dialogue) + yield from self._generate(dialogue, self._build_tools(functions)) + + def _generate(self, dialogue, tools): + role_map = {"assistant": "model", "user": "user"} + contents: list = [] + # 拼接对话 + for m in dialogue: + r = m["role"] + + if r == "assistant" and "tool_calls" in m: + tc = m["tool_calls"][0] + contents.append({ + "role": "model", + "parts": [{"function_call": { + "name": tc["function"]["name"], + "args": json.loads(tc["function"]["arguments"]), + }}], + }) + continue + + if r == "tool": + contents.append({ + "role": "model", + "parts": [{"text": str(m.get("content", ""))}], + }) + continue + + contents.append({ + "role": role_map.get(r, "user"), + "parts": [{"text": str(m.get("content", ""))}], + }) + + stream: GenerateContentResponse = self.model.generate_content( + contents=contents, + generation_config=self.gen_cfg, + tools=tools, + stream=True, + ) + + try: + for chunk in stream: + cand = chunk.candidates[0] + for part in cand.content.parts: + # a) 函数调用-通常是最后一段话才是函数调用 + if getattr(part, "function_call", None): + fc = part.function_call + yield None, [SimpleNamespace( + id=uuid.uuid4().hex, + type="function", + function=SimpleNamespace( + name=fc.name, + arguments=json.dumps(dict(fc.args), + ensure_ascii=False), + ), + )] + return + # b) 普通文本 + if getattr(part, "text", None): + yield part.text if tools is None else (part.text, None) + + finally: + if tools is not None: + yield None, None # function‑mode 结束,返回哑包 + + # 关闭stream,预留后续打断对话功能的功能方法,官方文档推荐打断对话要关闭上一个流,可以有效减少配额计费和资源占用 + @staticmethod + def _safe_finish_stream(stream: GenerateContentResponse): + if hasattr(stream, "resolve"): + stream.resolve() # Gemini SDK version ≥ 0.5.0 + elif hasattr(stream, "close"): + stream.close() # Gemini SDK version < 0.5.0 + else: + for _ in stream: # 兜底耗尽 + pass From 7e712c5003f66b91ae9055e058f2c831fe3518a8 Mon Sep 17 00:00:00 2001 From: whosmyqueen Date: Fri, 9 May 2025 16:01:09 +0800 Subject: [PATCH 02/12] =?UTF-8?q?build(db):=20=E6=9B=B4=E6=96=B0=E6=A8=A1?= =?UTF-8?q?=E5=9E=8B=E4=BE=9B=E5=BA=94=E5=99=A8=E5=92=8C=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 更新 ai_model_provider表中的字段配置 - 更新 ai_model_config 表中的配置 JSON- 添加新的数据库变更脚本202505091552.sql - 在 db.changelog-master.yaml 中引用新脚本 --- .../src/main/resources/db/changelog/202505091552.sql | 5 +++++ .../main/resources/db/changelog/db.changelog-master.yaml | 9 ++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 main/manager-api/src/main/resources/db/changelog/202505091552.sql diff --git a/main/manager-api/src/main/resources/db/changelog/202505091552.sql b/main/manager-api/src/main/resources/db/changelog/202505091552.sql new file mode 100644 index 00000000..7d6a40b7 --- /dev/null +++ b/main/manager-api/src/main/resources/db/changelog/202505091552.sql @@ -0,0 +1,5 @@ +-- 更新模型供应器表 +UPDATE `ai_model_provider` SET fields = '[{"key": "host", "type": "string", "label": "服务地址"}, {"key": "port", "type": "number", "label": "端口号"}, {"key": "api_key", "type": "string", "label": "API密钥"}]' WHERE id = 'SYSTEM_ASR_FunASRServer'; + +-- 更新模型配置表 +UPDATE `ai_model_config` SET config_json = '{"host": "127.0.0.1", "port": 10096, "type": "fun_server", "api_key": "none"}' WHERE id = 'ASR_FunASRServer'; \ 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 53f4609d..6b969776 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 @@ -113,4 +113,11 @@ databaseChangeLog: changes: - sqlFile: encoding: utf8 - path: classpath:db/changelog/202505091409.sql \ No newline at end of file + path: classpath:db/changelog/202505091409.sql + - changeSet: + id: 202505091552 + author: whosmyqueen + changes: + - sqlFile: + encoding: utf8 + path: classpath:db/changelog/202505091552.sql \ No newline at end of file From 164dada46a6aac5fff32328c30b882ea0c035bad Mon Sep 17 00:00:00 2001 From: whosmyqueen Date: Mon, 12 May 2025 14:38:58 +0800 Subject: [PATCH 03/12] =?UTF-8?q?feat(core):=20=E4=B8=BA=20FunASR=20?= =?UTF-8?q?=E6=9C=8D=E5=8A=A1=E6=B7=BB=E5=8A=A0=20API=20=E5=AF=86=E9=92=A5?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=B9=B6=E4=BC=98=E5=8C=96=E7=BB=93=E6=9E=9C?= =?UTF-8?q?=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 FunASRWebSocket 类中添加 API密钥配置 - 在 WebSocket连接时添加认证头信息- 使用正则表达式处理返回结果,提取有用信息 --- main/xiaozhi-server/core/providers/asr/fun_server.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/main/xiaozhi-server/core/providers/asr/fun_server.py b/main/xiaozhi-server/core/providers/asr/fun_server.py index 53eb4e7f..a446c6ac 100644 --- a/main/xiaozhi-server/core/providers/asr/fun_server.py +++ b/main/xiaozhi-server/core/providers/asr/fun_server.py @@ -9,6 +9,7 @@ import wave import websockets from config.logger import setup_logging import asyncio +import re TAG = __name__ logger = setup_logging() @@ -24,6 +25,7 @@ class ASRProvider(ASRProviderBase): super().__init__() self.host = config.get("host", "localhost") self.port = config.get("port", 10095) + self.api_key = config.get('api_key', 'none') self.is_ssl = config.get("is_ssl", True) self.output_dir = config.get("output_dir") self.delete_audio_file = delete_audio_file @@ -130,9 +132,9 @@ class ASRProvider(ASRProviderBase): pass else: file_path = self.save_audio_to_file(pcm_data, session_id) - + auth_header = {'Authorization': 'Bearer; {}'.format(self.api_key)} async with websockets.connect( - self.uri, subprotocols=["binary"], ping_interval=None, ssl=self.ssl_context + self.uri, additional_headers=auth_header, subprotocols=["binary"], ping_interval=None, ssl=self.ssl_context ) as ws: try: # Use asyncio to handle WebSocket communication @@ -157,6 +159,9 @@ class ASRProvider(ASRProviderBase): # Get the result from the receive task result = receive_task.result() + match = re.match(r'<\|(.*?)\|><\|(.*?)\|><\|(.*?)\|>(.*)', result) + if match: + result = match.group(4).strip() return ( result, file_path, From d619d7ac7677493c86f06251445cf2d57b4f7970 Mon Sep 17 00:00:00 2001 From: GoodyHao <865700600@qq.com> Date: Mon, 12 May 2025 17:55:54 +0800 Subject: [PATCH 04/12] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E4=B8=8A?= =?UTF-8?q?=E6=8A=A5=E5=91=BD=E5=90=8D=E5=90=AB=E4=B9=89=EF=BC=8C=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E4=B8=8D=E5=90=8C=E4=B8=8A=E6=8A=A5=E5=BC=80=E5=85=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main/xiaozhi-server/core/connection.py | 40 ++++++++++--------- .../core/handle/receiveAudioHandle.py | 4 +- .../{ttsReportHandle.py => reportHandle.py} | 37 +++++++++++++---- main/xiaozhi-server/core/handle/textHandle.py | 6 +-- 4 files changed, 55 insertions(+), 32 deletions(-) rename main/xiaozhi-server/core/handle/{ttsReportHandle.py => reportHandle.py} (73%) diff --git a/main/xiaozhi-server/core/connection.py b/main/xiaozhi-server/core/connection.py index 2a85c0ad..0b285fc6 100644 --- a/main/xiaozhi-server/core/connection.py +++ b/main/xiaozhi-server/core/connection.py @@ -33,7 +33,7 @@ from core.mcp.manager import MCPManager from config.config_loader import get_private_config_from_api from config.manage_api_client import DeviceNotFoundException, DeviceBindException from core.utils.output_counter import add_device_output -from core.handle.ttsReportHandle import enqueue_tts_report, report_tts +from core.handle.reportHandle import enqueue_tts_report, report TAG = __name__ @@ -88,8 +88,11 @@ class ConnectionHandler: self.executor = ThreadPoolExecutor(max_workers=10) # 上报线程 - self.tts_report_queue = queue.Queue() - self.tts_report_thread = None + self.report_queue = queue.Queue() + self.report_thread = None + # TODO(haotian): 2025/5/12 可以通过修改此处,调节asr的上报和tts的上报 + self.report_asr_enable = self.read_config_from_api + self.report_tts_enable = self.read_config_from_api # 依赖的组件 self.vad = None @@ -306,11 +309,11 @@ class ConnectionHandler: """初始化ASR和TTS上报线程""" if not self.read_config_from_api or self.need_bind: return - if self.tts_report_thread is None or not self.tts_report_thread.is_alive(): - self.tts_report_thread = threading.Thread( - target=self._tts_report_worker, daemon=True + if self.report_thread is None or not self.report_thread.is_alive(): + self.report_thread = threading.Thread( + target=self._report_worker, daemon=True ) - self.tts_report_thread.start() + self.report_thread.start() self.logger.bind(tag=TAG).info("TTS上报线程已启动") def _initialize_private_config(self): @@ -860,8 +863,8 @@ class ConnectionHandler: audio_datas, _ = self.tts.audio_to_pcm_data(tts_file) else: audio_datas, _ = self.tts.audio_to_opus_data(tts_file) - # 在这里上报TTS数据(使用文件路径) - enqueue_tts_report(self, 2, text, audio_datas) + # 在这里上报TTS数据 + enqueue_tts_report(self, text, audio_datas) else: self.logger.bind(tag=TAG).error( f"TTS出错:文件不存在{tts_file}" @@ -917,13 +920,12 @@ class ConnectionHandler: f"audio_play_priority priority_thread: {text} {e}" ) - def _tts_report_worker(self): - """TTS上报工作线程""" - + def _report_worker(self): + """聊天记录上报工作线程""" while not self.stop_event.is_set(): try: # 从队列获取数据,设置超时以便定期检查停止事件 - item = self.tts_report_queue.get(timeout=1) + item = self.report_queue.get(timeout=1) if item is None: # 检测毒丸对象 break @@ -931,18 +933,18 @@ class ConnectionHandler: try: # 执行上报(传入二进制数据) - report_tts(self, type, text, audio_data) + report(self, type, text, audio_data) except Exception as e: - self.logger.bind(tag=TAG).error(f"TTS上报线程异常: {e}") + self.logger.bind(tag=TAG).error(f"聊天记录上报线程异常: {e}") finally: # 标记任务完成 - self.tts_report_queue.task_done() + self.report_queue.task_done() except queue.Empty: continue except Exception as e: - self.logger.bind(tag=TAG).error(f"TTS上报工作线程异常: {e}") + self.logger.bind(tag=TAG).error(f"聊天记录上报工作线程异常: {e}") - self.logger.bind(tag=TAG).info("TTS上报线程已退出") + self.logger.bind(tag=TAG).info("聊天记录上报线程已退出") def speak_and_play(self, text, text_index=0): if text is None or len(text) <= 0: @@ -990,7 +992,7 @@ class ConnectionHandler: self.executor = None # 添加毒丸对象到上报队列确保线程退出 - self.tts_report_queue.put(None) + self.report_queue.put(None) # 清空任务队列 self.clear_queues() diff --git a/main/xiaozhi-server/core/handle/receiveAudioHandle.py b/main/xiaozhi-server/core/handle/receiveAudioHandle.py index 352d0656..96df9f23 100644 --- a/main/xiaozhi-server/core/handle/receiveAudioHandle.py +++ b/main/xiaozhi-server/core/handle/receiveAudioHandle.py @@ -4,7 +4,7 @@ from core.utils.util import remove_punctuation_and_length from core.handle.sendAudioHandle import send_stt_message from core.handle.intentHandler import handle_user_intent from core.utils.output_counter import check_device_output_limit -from core.handle.ttsReportHandle import enqueue_tts_report +from core.handle.reportHandle import enqueue_asr_report from core.utils.util import audio_to_data TAG = __name__ @@ -44,7 +44,7 @@ async def handleAudioMessage(conn, audio): text_len, _ = remove_punctuation_and_length(text) if text_len > 0: # 使用自定义模块进行上报 - enqueue_tts_report(conn, 1, text, copy.deepcopy(conn.asr_audio)) + enqueue_asr_report(conn, text, copy.deepcopy(conn.asr_audio)) await startToChat(conn, text) else: diff --git a/main/xiaozhi-server/core/handle/ttsReportHandle.py b/main/xiaozhi-server/core/handle/reportHandle.py similarity index 73% rename from main/xiaozhi-server/core/handle/ttsReportHandle.py rename to main/xiaozhi-server/core/handle/reportHandle.py index ae8928ce..f8287fdc 100644 --- a/main/xiaozhi-server/core/handle/ttsReportHandle.py +++ b/main/xiaozhi-server/core/handle/reportHandle.py @@ -11,13 +11,13 @@ TTS上报功能已集成到ConnectionHandler类中。 import opuslib_next -from config.manage_api_client import report +from config.manage_api_client import report as manage_report TAG = __name__ -def report_tts(conn, type, text, opus_data): - """执行TTS上报操作 +def report(conn, type, text, opus_data): + """执行聊天记录上报操作 Args: conn: 连接对象 @@ -31,7 +31,7 @@ def report_tts(conn, type, text, opus_data): else: audio_data = None # 执行上报 - report( + manage_report( mac_address=conn.device_id, session_id=conn.session_id, chat_type=type, @@ -39,7 +39,7 @@ def report_tts(conn, type, text, opus_data): audio=audio_data, ) except Exception as e: - conn.logger.bind(tag=TAG).error(f"TTS上报失败: {e}") + conn.logger.bind(tag=TAG).error(f"聊天记录上报失败: {e}") def opus_to_wav(conn, opus_data): @@ -89,8 +89,8 @@ def opus_to_wav(conn, opus_data): return bytes(wav_header) + pcm_data_bytes -def enqueue_tts_report(conn, type, text, opus_data): - if not conn.read_config_from_api or conn.need_bind: +def enqueue_tts_report(conn, text, opus_data): + if not conn.read_config_from_api or conn.need_bind or not conn.report_tts_enable: return """将TTS数据加入上报队列 @@ -101,10 +101,31 @@ def enqueue_tts_report(conn, type, text, opus_data): """ try: # 使用连接对象的队列,传入文本和二进制数据而非文件路径 - conn.tts_report_queue.put((type, text, opus_data)) + conn.report_queue.put((2, text, opus_data)) conn.logger.bind(tag=TAG).debug( f"TTS数据已加入上报队列: {conn.device_id}, 音频大小: {len(opus_data)} " ) except Exception as e: conn.logger.bind(tag=TAG).error(f"加入TTS上报队列失败: {text}, {e}") + + +def enqueue_asr_report(conn, text, opus_data): + if not conn.read_config_from_api or conn.need_bind or not conn.report_asr_enable: + return + """将ASR数据加入上报队列 + + Args: + conn: 连接对象 + text: 合成文本 + opus_data: opus音频数据 + """ + try: + # 使用连接对象的队列,传入文本和二进制数据而非文件路径 + conn.report_queue.put((1, text, opus_data)) + + conn.logger.bind(tag=TAG).debug( + f"ASR数据已加入上报队列: {conn.device_id}, 音频大小: {len(opus_data)} " + ) + except Exception as e: + conn.logger.bind(tag=TAG).error(f"加入ASR上报队列失败: {text}, {e}") diff --git a/main/xiaozhi-server/core/handle/textHandle.py b/main/xiaozhi-server/core/handle/textHandle.py index a3acd3f0..79874f1c 100644 --- a/main/xiaozhi-server/core/handle/textHandle.py +++ b/main/xiaozhi-server/core/handle/textHandle.py @@ -5,7 +5,7 @@ from core.utils.util import remove_punctuation_and_length from core.handle.receiveAudioHandle import startToChat, handleAudioMessage from core.handle.sendAudioHandle import send_stt_message, send_tts_message from core.handle.iotHandle import handleIotDescriptors, handleIotStatus -from core.handle.ttsReportHandle import enqueue_tts_report +from core.handle.reportHandle import enqueue_asr_report import asyncio TAG = __name__ @@ -56,11 +56,11 @@ async def handleTextMessage(conn, message): await send_tts_message(conn, "stop", None) elif is_wakeup_words: # 上报纯文字数据(复用ASR上报功能,但不提供音频数据) - enqueue_tts_report(conn, 1, "嘿,你好呀", []) + enqueue_asr_report(conn, "嘿,你好呀", []) await startToChat(conn, "嘿,你好呀") else: # 上报纯文字数据(复用ASR上报功能,但不提供音频数据) - enqueue_tts_report(conn, 1, text, []) + enqueue_asr_report(conn, text, []) # 否则需要LLM对文字内容进行答复 await startToChat(conn, text) elif msg_json["type"] == "iot": From 0b11953f6f93b54aacf5bb425155b66701eea34d Mon Sep 17 00:00:00 2001 From: goodyhao <865700600@qq.com> Date: Mon, 12 May 2025 17:55:54 +0800 Subject: [PATCH 05/12] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E4=B8=8A?= =?UTF-8?q?=E6=8A=A5=E5=91=BD=E5=90=8D=E5=90=AB=E4=B9=89=EF=BC=8C=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E4=B8=8D=E5=90=8C=E4=B8=8A=E6=8A=A5=E5=BC=80=E5=85=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main/xiaozhi-server/core/connection.py | 40 ++++++++-------- .../core/handle/receiveAudioHandle.py | 4 +- .../{ttsReportHandle.py => reportHandle.py} | 46 +++++++++++++++---- main/xiaozhi-server/core/handle/textHandle.py | 6 +-- 4 files changed, 63 insertions(+), 33 deletions(-) rename main/xiaozhi-server/core/handle/{ttsReportHandle.py => reportHandle.py} (69%) diff --git a/main/xiaozhi-server/core/connection.py b/main/xiaozhi-server/core/connection.py index 5f2286db..63b79736 100644 --- a/main/xiaozhi-server/core/connection.py +++ b/main/xiaozhi-server/core/connection.py @@ -33,7 +33,7 @@ from core.mcp.manager import MCPManager from config.config_loader import get_private_config_from_api from config.manage_api_client import DeviceNotFoundException, DeviceBindException from core.utils.output_counter import add_device_output -from core.handle.ttsReportHandle import enqueue_tts_report, report_tts +from core.handle.reportHandle import enqueue_tts_report, report TAG = __name__ @@ -89,8 +89,11 @@ class ConnectionHandler: self.executor = ThreadPoolExecutor(max_workers=10) # 上报线程 - self.tts_report_queue = queue.Queue() - self.tts_report_thread = None + self.report_queue = queue.Queue() + self.report_thread = None + # TODO(haotian): 2025/5/12 可以通过修改此处,调节asr的上报和tts的上报 + self.report_asr_enable = self.read_config_from_api + self.report_tts_enable = self.read_config_from_api # 依赖的组件 self.vad = None @@ -317,11 +320,11 @@ class ConnectionHandler: return if self.chat_history_conf == 0: return - if self.tts_report_thread is None or not self.tts_report_thread.is_alive(): - self.tts_report_thread = threading.Thread( - target=self._tts_report_worker, daemon=True + if self.report_thread is None or not self.report_thread.is_alive(): + self.report_thread = threading.Thread( + target=self._report_worker, daemon=True ) - self.tts_report_thread.start() + self.report_thread.start() self.logger.bind(tag=TAG).info("TTS上报线程已启动") def _initialize_private_config(self): @@ -872,8 +875,8 @@ class ConnectionHandler: audio_datas, _ = self.tts.audio_to_pcm_data(tts_file) else: audio_datas, _ = self.tts.audio_to_opus_data(tts_file) - # 在这里上报TTS数据(使用文件路径) - enqueue_tts_report(self, 2, text, audio_datas) + # 在这里上报TTS数据 + enqueue_tts_report(self, text, audio_datas) else: self.logger.bind(tag=TAG).error( f"TTS出错:文件不存在{tts_file}" @@ -929,13 +932,12 @@ class ConnectionHandler: f"audio_play_priority priority_thread: {text} {e}" ) - def _tts_report_worker(self): - """TTS上报工作线程""" - + def _report_worker(self): + """聊天记录上报工作线程""" while not self.stop_event.is_set(): try: # 从队列获取数据,设置超时以便定期检查停止事件 - item = self.tts_report_queue.get(timeout=1) + item = self.report_queue.get(timeout=1) if item is None: # 检测毒丸对象 break @@ -943,18 +945,18 @@ class ConnectionHandler: try: # 执行上报(传入二进制数据) - report_tts(self, type, text, audio_data) + report(self, type, text, audio_data) except Exception as e: - self.logger.bind(tag=TAG).error(f"TTS上报线程异常: {e}") + self.logger.bind(tag=TAG).error(f"聊天记录上报线程异常: {e}") finally: # 标记任务完成 - self.tts_report_queue.task_done() + self.report_queue.task_done() except queue.Empty: continue except Exception as e: - self.logger.bind(tag=TAG).error(f"TTS上报工作线程异常: {e}") + self.logger.bind(tag=TAG).error(f"聊天记录上报工作线程异常: {e}") - self.logger.bind(tag=TAG).info("TTS上报线程已退出") + self.logger.bind(tag=TAG).info("聊天记录上报线程已退出") def speak_and_play(self, text, text_index=0): if text is None or len(text) <= 0: @@ -1002,7 +1004,7 @@ class ConnectionHandler: self.executor = None # 添加毒丸对象到上报队列确保线程退出 - self.tts_report_queue.put(None) + self.report_queue.put(None) # 清空任务队列 self.clear_queues() diff --git a/main/xiaozhi-server/core/handle/receiveAudioHandle.py b/main/xiaozhi-server/core/handle/receiveAudioHandle.py index 352d0656..96df9f23 100644 --- a/main/xiaozhi-server/core/handle/receiveAudioHandle.py +++ b/main/xiaozhi-server/core/handle/receiveAudioHandle.py @@ -4,7 +4,7 @@ from core.utils.util import remove_punctuation_and_length from core.handle.sendAudioHandle import send_stt_message from core.handle.intentHandler import handle_user_intent from core.utils.output_counter import check_device_output_limit -from core.handle.ttsReportHandle import enqueue_tts_report +from core.handle.reportHandle import enqueue_asr_report from core.utils.util import audio_to_data TAG = __name__ @@ -44,7 +44,7 @@ async def handleAudioMessage(conn, audio): text_len, _ = remove_punctuation_and_length(text) if text_len > 0: # 使用自定义模块进行上报 - enqueue_tts_report(conn, 1, text, copy.deepcopy(conn.asr_audio)) + enqueue_asr_report(conn, text, copy.deepcopy(conn.asr_audio)) await startToChat(conn, text) else: diff --git a/main/xiaozhi-server/core/handle/ttsReportHandle.py b/main/xiaozhi-server/core/handle/reportHandle.py similarity index 69% rename from main/xiaozhi-server/core/handle/ttsReportHandle.py rename to main/xiaozhi-server/core/handle/reportHandle.py index a6605e29..bb1ea066 100644 --- a/main/xiaozhi-server/core/handle/ttsReportHandle.py +++ b/main/xiaozhi-server/core/handle/reportHandle.py @@ -11,13 +11,13 @@ TTS上报功能已集成到ConnectionHandler类中。 import opuslib_next -from config.manage_api_client import report +from config.manage_api_client import report as manage_report TAG = __name__ -def report_tts(conn, type, text, opus_data): - """执行TTS上报操作 +def report(conn, type, text, opus_data): + """执行聊天记录上报操作 Args: conn: 连接对象 @@ -31,7 +31,7 @@ def report_tts(conn, type, text, opus_data): else: audio_data = None # 执行上报 - report( + manage_report( mac_address=conn.device_id, session_id=conn.session_id, chat_type=type, @@ -39,7 +39,7 @@ def report_tts(conn, type, text, opus_data): audio=audio_data, ) except Exception as e: - conn.logger.bind(tag=TAG).error(f"TTS上报失败: {e}") + conn.logger.bind(tag=TAG).error(f"聊天记录上报失败: {e}") def opus_to_wav(conn, opus_data): @@ -89,8 +89,8 @@ def opus_to_wav(conn, opus_data): return bytes(wav_header) + pcm_data_bytes -def enqueue_tts_report(conn, type, text, opus_data): - if not conn.read_config_from_api or conn.need_bind: +def enqueue_tts_report(conn, text, opus_data): + if not conn.read_config_from_api or conn.need_bind or not conn.report_tts_enable: return if conn.chat_history_conf == 0: return @@ -104,14 +104,42 @@ def enqueue_tts_report(conn, type, text, opus_data): try: # 使用连接对象的队列,传入文本和二进制数据而非文件路径 if conn.chat_history_conf == 2: - conn.tts_report_queue.put((type, text, opus_data)) + conn.report_queue.put((2, text, opus_data)) conn.logger.bind(tag=TAG).debug( f"TTS数据已加入上报队列: {conn.device_id}, 音频大小: {len(opus_data)} " ) else: - conn.tts_report_queue.put((type, text, None)) + conn.report_queue.put((2, text, None)) conn.logger.bind(tag=TAG).debug( f"TTS数据已加入上报队列: {conn.device_id}, 不上报音频" ) except Exception as e: conn.logger.bind(tag=TAG).error(f"加入TTS上报队列失败: {text}, {e}") + + +def enqueue_asr_report(conn, text, opus_data): + if not conn.read_config_from_api or conn.need_bind or not conn.report_asr_enable: + return + if conn.chat_history_conf == 0: + return + """将ASR数据加入上报队列 + + Args: + conn: 连接对象 + text: 合成文本 + opus_data: opus音频数据 + """ + try: + # 使用连接对象的队列,传入文本和二进制数据而非文件路径 + if conn.chat_history_conf == 2: + conn.report_queue.put((1, text, opus_data)) + conn.logger.bind(tag=TAG).debug( + f"ASR数据已加入上报队列: {conn.device_id}, 音频大小: {len(opus_data)} " + ) + else: + conn.report_queue.put((1, text, None)) + conn.logger.bind(tag=TAG).debug( + f"ASR数据已加入上报队列: {conn.device_id}, 不上报音频" + ) + except Exception as e: + conn.logger.bind(tag=TAG).error(f"加入ASR上报队列失败: {text}, {e}") diff --git a/main/xiaozhi-server/core/handle/textHandle.py b/main/xiaozhi-server/core/handle/textHandle.py index a3acd3f0..79874f1c 100644 --- a/main/xiaozhi-server/core/handle/textHandle.py +++ b/main/xiaozhi-server/core/handle/textHandle.py @@ -5,7 +5,7 @@ from core.utils.util import remove_punctuation_and_length from core.handle.receiveAudioHandle import startToChat, handleAudioMessage from core.handle.sendAudioHandle import send_stt_message, send_tts_message from core.handle.iotHandle import handleIotDescriptors, handleIotStatus -from core.handle.ttsReportHandle import enqueue_tts_report +from core.handle.reportHandle import enqueue_asr_report import asyncio TAG = __name__ @@ -56,11 +56,11 @@ async def handleTextMessage(conn, message): await send_tts_message(conn, "stop", None) elif is_wakeup_words: # 上报纯文字数据(复用ASR上报功能,但不提供音频数据) - enqueue_tts_report(conn, 1, "嘿,你好呀", []) + enqueue_asr_report(conn, "嘿,你好呀", []) await startToChat(conn, "嘿,你好呀") else: # 上报纯文字数据(复用ASR上报功能,但不提供音频数据) - enqueue_tts_report(conn, 1, text, []) + enqueue_asr_report(conn, text, []) # 否则需要LLM对文字内容进行答复 await startToChat(conn, text) elif msg_json["type"] == "iot": From 168bc9ef453d471530827eb252ffa603f68e7e54 Mon Sep 17 00:00:00 2001 From: caixypromise Date: Mon, 12 May 2025 21:33:43 +0800 Subject: [PATCH 06/12] =?UTF-8?q?update:=20=E6=94=B9=E8=BF=9Bgemini?= =?UTF-8?q?=E8=BF=9E=E9=80=9A=E4=BB=A3=E7=90=86=E8=83=BD=E5=8A=9B=EF=BC=8C?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E6=B5=8B=E8=AF=95=E4=B8=8E=E8=A3=85=E9=85=8D?= =?UTF-8?q?=E5=90=88=E9=80=82=E7=9A=84=E4=BB=A3=E7=90=86=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E4=BF=A1=E6=81=AF=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/providers/llm/gemini/gemini.py | 62 +++++++++++++++++-- 1 file changed, 56 insertions(+), 6 deletions(-) diff --git a/main/xiaozhi-server/core/providers/llm/gemini/gemini.py b/main/xiaozhi-server/core/providers/llm/gemini/gemini.py index 16f8d11a..a0b80558 100644 --- a/main/xiaozhi-server/core/providers/llm/gemini/gemini.py +++ b/main/xiaozhi-server/core/providers/llm/gemini/gemini.py @@ -1,8 +1,11 @@ # core/providers/llm/gemini_sdk.py import os, json, uuid +import urllib from types import SimpleNamespace from typing import Any, Dict, List +from urllib.parse import urlparse +import requests from google import generativeai as genai from google.generativeai import types, GenerationConfig @@ -10,24 +13,72 @@ from core.providers.llm.base import LLMProviderBase from core.utils.util import check_model_key from config.logger import setup_logging from google.generativeai.types import GenerateContentResponse +from requests import RequestException log = setup_logging() TAG = __name__ +def test_proxy(proxy_url: str, test_url: str) -> bool: + try: + resp = requests.get(test_url, proxies={"http": proxy_url, "https": proxy_url}) + return 200 <= resp.status_code < 400 + except RequestException: + return False + + +def setup_proxy_env(http_proxy: str | None, https_proxy: str | None): + """ + 分别测试 HTTP 和 HTTPS 代理是否可用,并设置环境变量。 + 如果 HTTPS 代理不可用但 HTTP 可用,会将 HTTPS_PROXY 也指向 HTTP。 + """ + test_http_url = "http://www.google.com" + test_https_url = "https://www.google.com" + + ok_http = ok_https = False + + if http_proxy: + ok_http = test_proxy(http_proxy, test_http_url) + if ok_http: + os.environ["HTTP_PROXY"] = http_proxy + log.bind(tag=TAG).info(f"配置提供的Gemini HTTPS代理连通成功: {http_proxy}") + else: + log.bind(tag=TAG).warn(f"配置提供的Gemini HTTP代理不可用: {http_proxy}") + + if https_proxy: + ok_https = test_proxy(https_proxy, test_https_url) + if ok_https: + os.environ["HTTPS_PROXY"] = https_proxy + log.bind(tag=TAG).info(f"配置提供的Gemini HTTPS代理连通成功: {https_proxy}") + else: + log.bind(tag=TAG).warning(f"配置提供的Gemini HTTPS代理不可用: {https_proxy}") + + # 如果https_proxy不可用,但http_proxy可用且能走通https,则复用http_proxy作为https_proxy + if ok_http and not ok_https: + if test_proxy(http_proxy, test_https_url): + os.environ["HTTPS_PROXY"] = http_proxy + ok_https = True + log.bind(tag=TAG).info(f"复用HTTP代理作为HTTPS代理: {http_proxy}") + + if not ok_http and not ok_https: + log.bind(tag=TAG).error(f"Gemini 代理设置失败: HTTP 和 HTTPS 代理都不可用,请检查配置") + raise RuntimeError("HTTP 和 HTTPS 代理都不可用,请检查配置") + + class LLMProvider(LLMProviderBase): def __init__(self, cfg: Dict[str, Any]): self.model_name = cfg.get("model_name", "gemini-2.0-flash") self.api_key = cfg["api_key"] - proxy = cfg.get("https_proxy") or cfg.get("http_proxy") + http_proxy = cfg.get("http_proxy") + https_proxy = cfg.get("https_proxy") if not check_model_key("LLM", self.api_key): raise ValueError("无效的Gemini API Key,请检查是否配置正确") - if proxy: - os.environ["HTTPS_PROXY"] = os.environ["HTTP_PROXY"] = proxy - log.bind(tag=TAG).info(f"Gemini 代理地址: {proxy}") - + if http_proxy or https_proxy: + log.bind(tag=TAG).info(f"检测到Gemini代理配置,开始测试代理连通性和设置代理环境...") + setup_proxy_env(http_proxy, https_proxy) + log.bind(tag=TAG).info(f"Gemini 代理设置成功 - HTTP: {http_proxy}, HTTPS: {https_proxy}") genai.configure(api_key=self.api_key) self.model = genai.GenerativeModel(self.model_name) @@ -38,7 +89,6 @@ class LLMProvider(LLMProviderBase): max_output_tokens=2048, ) - @staticmethod def _build_tools(funcs: List[Dict[str, Any]] | None): if not funcs: From 3ce204338a6e9c885b5ca05579556c26f26d5c6a Mon Sep 17 00:00:00 2001 From: caixypromise Date: Mon, 12 May 2025 21:38:23 +0800 Subject: [PATCH 07/12] =?UTF-8?q?chore:=20=E5=88=A0=E9=99=A4=E6=97=A0?= =?UTF-8?q?=E7=94=A8=E7=9A=84=E5=8C=85=E5=BC=95=E5=85=A5=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main/xiaozhi-server/core/providers/llm/gemini/gemini.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/main/xiaozhi-server/core/providers/llm/gemini/gemini.py b/main/xiaozhi-server/core/providers/llm/gemini/gemini.py index a0b80558..3935b68d 100644 --- a/main/xiaozhi-server/core/providers/llm/gemini/gemini.py +++ b/main/xiaozhi-server/core/providers/llm/gemini/gemini.py @@ -1,9 +1,7 @@ # core/providers/llm/gemini_sdk.py import os, json, uuid -import urllib from types import SimpleNamespace from typing import Any, Dict, List -from urllib.parse import urlparse import requests from google import generativeai as genai From f77215567433e2f0a6c20a755b03c39ec79044a7 Mon Sep 17 00:00:00 2001 From: caixypromise Date: Mon, 12 May 2025 21:39:13 +0800 Subject: [PATCH 08/12] =?UTF-8?q?chore:=20=E5=88=A0=E9=99=A4=E6=97=A0?= =?UTF-8?q?=E7=94=A8=E7=9A=84=E5=8C=85=E5=BC=95=E5=85=A5=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main/xiaozhi-server/core/providers/llm/gemini/gemini.py | 1 - 1 file changed, 1 deletion(-) diff --git a/main/xiaozhi-server/core/providers/llm/gemini/gemini.py b/main/xiaozhi-server/core/providers/llm/gemini/gemini.py index 3935b68d..2bdea14a 100644 --- a/main/xiaozhi-server/core/providers/llm/gemini/gemini.py +++ b/main/xiaozhi-server/core/providers/llm/gemini/gemini.py @@ -1,4 +1,3 @@ -# core/providers/llm/gemini_sdk.py import os, json, uuid from types import SimpleNamespace from typing import Any, Dict, List From 8930ab0af6bcb2c4a4e9c31a3bf1251531379297 Mon Sep 17 00:00:00 2001 From: hrz <1710360675@qq.com> Date: Tue, 13 May 2025 10:14:15 +0800 Subject: [PATCH 09/12] =?UTF-8?q?update=EF=BC=9A=E4=BF=AE=E5=A4=8Dfishspee?= =?UTF-8?q?ch=E7=9A=84normalize=E8=8E=B7=E5=8F=96=E5=80=BC=E8=BD=AC?= =?UTF-8?q?=E6=8D=A2bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main/xiaozhi-server/core/providers/tts/fishspeech.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/main/xiaozhi-server/core/providers/tts/fishspeech.py b/main/xiaozhi-server/core/providers/tts/fishspeech.py index 627c6e99..687e27ef 100644 --- a/main/xiaozhi-server/core/providers/tts/fishspeech.py +++ b/main/xiaozhi-server/core/providers/tts/fishspeech.py @@ -88,13 +88,17 @@ class TTSProvider(TTSProviderBase): self.reference_id = config.get("reference_id") self.reference_audio = parse_string_to_list(config.get("reference_audio")) self.reference_text = parse_string_to_list(config.get("reference_text")) - self.format = config.get("format", "wav") + self.format = config.get("response_format", "wav") self.api_key = config.get("api_key", "YOUR_API_KEY") have_key = check_model_key("FishSpeech TTS", self.api_key) if not have_key: return - self.normalize = config.get("normalize", True) + self.normalize = str(config.get("normalize", True)).lower() in ( + "true", + "1", + "yes", + ) # 处理空字符串的情况 channels = config.get("channels", "1") From 363dcef81ab7e39fc9650722c2487807202c70e0 Mon Sep 17 00:00:00 2001 From: hrz <1710360675@qq.com> Date: Tue, 13 May 2025 11:18:03 +0800 Subject: [PATCH 10/12] =?UTF-8?q?update:=E4=BC=98=E5=8C=96=E6=9C=AA?= =?UTF-8?q?=E5=BC=80=E5=90=AF=E8=AE=B0=E5=BF=86=E7=9A=84=E8=81=8A=E5=A4=A9?= =?UTF-8?q?=E8=AE=B0=E5=BD=95=E6=8C=89=E9=92=AE=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../xiaozhi/modules/agent/dto/AgentDTO.java | 3 +++ .../agent/service/impl/AgentServiceImpl.java | 3 +++ main/manager-web/src/components/DeviceItem.vue | 17 +++++++++++++++-- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentDTO.java index a63c96e7..11c9b021 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentDTO.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentDTO.java @@ -27,6 +27,9 @@ public class AgentDTO { @Schema(description = "大语言模型名称", example = "llm_model_01") private String llmModelName; + @Schema(description = "记忆模型ID", example = "mem_model_01") + private String memModelId; + @Schema(description = "角色设定参数", example = "你是一个专业的客服助手,负责回答用户问题并提供帮助") private String systemPrompt; diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentServiceImpl.java index 067a8f1e..37bb7d93 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentServiceImpl.java @@ -102,6 +102,9 @@ public class AgentServiceImpl extends BaseServiceImpl imp // 获取 LLM 模型名称 dto.setLlmModelName(modelConfigService.getModelNameById(agent.getLlmModelId())); + // 获取记忆模型名称 + dto.setMemModelId(agent.getMemModelId()); + // 获取 TTS 音色名称 dto.setTtsVoiceName(timbreModelService.getTimbreNameById(agent.getTtsVoiceId())); diff --git a/main/manager-web/src/components/DeviceItem.vue b/main/manager-web/src/components/DeviceItem.vue index 60f54b9d..0ecbcefa 100644 --- a/main/manager-web/src/components/DeviceItem.vue +++ b/main/manager-web/src/components/DeviceItem.vue @@ -26,8 +26,12 @@
设备管理({{ device.deviceCount }})
-
- 聊天记录 +
+ + 聊天记录 + + 聊天记录
@@ -77,6 +81,9 @@ export default { this.$router.push({ path: '/device-management', query: { agentId: this.device.agentId } }); }, handleChatHistory() { + if (this.device.memModelId === 'Memory_nomem') { + return + } this.$emit('chat-history', { agentId: this.device.agentId, agentName: this.device.agentName }) } } @@ -120,6 +127,12 @@ export default { color: #979db1; font-weight: 400; } + +.disabled-btn { + background: #e6e6e6; + color: #999; + cursor: not-allowed; +}