From 8504c181c0b89f7876e9653a5012a4c29ef33584 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=84=E5=87=A4=E7=A7=91=E6=8A=80?= Date: Thu, 20 Mar 2025 08:59:45 +0800 Subject: [PATCH 01/39] =?UTF-8?q?mcp=E5=88=9D=E6=AD=A5=E8=B0=83=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main/xiaozhi-server/core/connection.py | 31 +++++- main/xiaozhi-server/core/mcp/MCPClient.py | 96 +++++++++++++++++ main/xiaozhi-server/core/mcp/manager.py | 102 ++++++++++++++++++ .../plugins_func/functions/get_time.py | 2 +- main/xiaozhi-server/plugins_func/register.py | 1 + 5 files changed, 228 insertions(+), 4 deletions(-) create mode 100644 main/xiaozhi-server/core/mcp/MCPClient.py create mode 100644 main/xiaozhi-server/core/mcp/manager.py diff --git a/main/xiaozhi-server/core/connection.py b/main/xiaozhi-server/core/connection.py index 718398c3..b5e7dcf2 100644 --- a/main/xiaozhi-server/core/connection.py +++ b/main/xiaozhi-server/core/connection.py @@ -22,6 +22,7 @@ from plugins_func.register import Action from config.private_config import PrivateConfig from core.auth import AuthMiddleware, AuthenticationError from core.utils.auth_code_gen import AuthCodeGenerator +from core.mcp.manager import MCPManager TAG = __name__ @@ -98,6 +99,8 @@ class ConnectionHandler: self.use_function_call_mode = False if self.config["selected_module"]["Intent"] == 'function_call': self.use_function_call_mode = True + + self.mcp_manager = MCPManager(self) async def handle_connection(self, ws): try: @@ -151,6 +154,8 @@ class ConnectionHandler: # 异步初始化 await self.loop.run_in_executor(None, self._initialize_components) + # 初始化MCP服务 + await self.mcp_manager.initialize_servers() # tts 消化线程 tts_priority = threading.Thread(target=self._tts_priority_thread, daemon=True) @@ -317,7 +322,9 @@ class ConnectionHandler: # Define intent functions functions = self.func_handler.get_functions() - + mcpfuncs = self.mcp_manager.get_all_tools() # MCP工具 + functions.extend(mcpfuncs) + print("functions with mcp", functions) response_message = [] processed_chars = 0 # 跟踪已处理的字符位置 @@ -429,8 +436,24 @@ class ConnectionHandler: "id": function_id, "arguments": function_arguments } - result = self.func_handler.handle_llm_function_call(self, function_call_data) - self._handle_function_result(result, function_call_data, text_index + 1) + #result = self.func_handler.handle_llm_function_call(self, function_call_data) + #self._handle_function_result(result, function_call_data, text_index+1) + + # 处理MCP工具调用 + if self.mcp_manager.is_mcp_tool(function_name): + try: + tool_result = asyncio.create_task(self.mcp_manager.execute_tool( + function_name, + function_arguments + )) + self._handle_mcp_tool_result(tool_result, function_call_data, text_index+1) + except Exception as e: + self.logger.bind(tag=TAG).error(f"MCP工具调用错误: {e}") + response_message.append(f"MCP工具调用失败: {str(e)}") + else: + # 处理系统函数 + result = self.func_handler.handle_llm_function_call(self, function_call_data) + self._handle_function_result(result, function_call_data, text_index+1) # 处理最后剩余的文本 full_text = "".join(response_message) @@ -565,6 +588,8 @@ class ConnectionHandler: async def close(self): """资源清理方法""" + # 清理MCP资源 + await self.mcp_manager.cleanup_all() # 清理其他资源 self.stop_event.set() diff --git a/main/xiaozhi-server/core/mcp/MCPClient.py b/main/xiaozhi-server/core/mcp/MCPClient.py new file mode 100644 index 00000000..903ce60b --- /dev/null +++ b/main/xiaozhi-server/core/mcp/MCPClient.py @@ -0,0 +1,96 @@ +import asyncio +from typing import Optional +from contextlib import AsyncExitStack +import os +from mcp import ClientSession, StdioServerParameters +from mcp.client.stdio import stdio_client + +from config.logger import setup_logging + +TAG = __name__ + +class MCPClient: + def __init__(self, config): + # Initialize session and client objects + self.session: Optional[ClientSession] = None + self.exit_stack = AsyncExitStack() + self.logger = setup_logging() + self.config = config + self.tolls = [] + + async def initialize(self): + main_command = self.config["command"] + args = self.config.get("args", []) + + server_script_path = main_command + if args: + # 如果第一个参数是路径,使用其目录作为工作目录 + possible_path = args[0] + if os.path.exists(possible_path): + server_script_path = possible_path + await self.connect_to_server(server_script_path) + + + async def connect_to_server(self, server_script_path: str): + """Connect to an MCP server + + Args: + server_script_path: Path to the server script (.py or .js) + """ + is_python = server_script_path.endswith('.py') + is_js = server_script_path.endswith('.js') + if not (is_python or is_js): + raise ValueError("Server script must be a .py or .js file") + + env = self.config.get("env", {}) + command = "python" if is_python else "node" + server_params = StdioServerParameters( + command=command, + args=[server_script_path], + env=env + ) + + stdio_transport = await self.exit_stack.enter_async_context(stdio_client(server_params)) + self.stdio, self.write = stdio_transport + self.session = await self.exit_stack.enter_async_context(ClientSession(self.stdio, self.write)) + + await self.session.initialize() + + # List available tools + response = await self.session.list_tools() + tools = response.tools + self.tools = tools + self.logger.bind(tag=TAG).info(f"Connected to server with tools:{[tool.name for tool in tools]}") + + def get_available_tools(self): + available_tools = [{"type": "function", "function":{ + "name": tool.name, + "description": tool.description, + "parameters": tool.inputSchema + } } for tool in self.tools] + + return available_tools + + async def call_tool(self, tool_name: str, tool_args: dict): + response = await self.session.call_tool(tool_name, tool_args) + return response + + async def cleanup(self): + """Clean up resources""" + await self.exit_stack.aclose() + +async def main(): + if len(sys.argv) < 2: + print("Usage: python client.py ") + sys.exit(1) + + client = MCPClient() + try: + await client.connect_to_server(sys.argv[1]) + await client.chat_loop() + finally: + await client.cleanup() + +if __name__ == "__main__": + import sys + asyncio.run(main()) \ No newline at end of file diff --git a/main/xiaozhi-server/core/mcp/manager.py b/main/xiaozhi-server/core/mcp/manager.py new file mode 100644 index 00000000..f9b4a742 --- /dev/null +++ b/main/xiaozhi-server/core/mcp/manager.py @@ -0,0 +1,102 @@ +"""MCP服务管理器""" +import json +from typing import Dict, Any, List +from .MCPClient import MCPClient +from config.logger import setup_logging +from core.utils.util import get_project_dir +from plugins_func.register import register_function, ActionResponse, Action, ToolType + +TAG = __name__ + +class MCPManager: + """管理多个MCP服务的集中管理器""" + + def __init__(self,conn) -> None: + """ + 初始化MCP管理器 + """ + self.conn = conn + self.config_path = get_project_dir() + 'data/.mcp_server_settings.json' + self.client: Dict[str, MCPClient] = {} + self.logger = setup_logging() + self.tools = [] + + def load_config(self) -> Dict[str, Any]: + """加载MCP服务配置 + Returns: + Dict[str, Any]: 服务配置字典 + """ + try: + with open(self.config_path, 'r', encoding='utf-8') as f: + config = json.load(f) + return config.get('mcpServers', {}) + except Exception as e: + self.logger.bind(tag=TAG).error(f"Error loading MCP config from {self.config_path}: {e}") + return {} + + async def initialize_servers(self) -> None: + """初始化所有MCP服务""" + config = self.load_config() + for name, srv_config in config.items(): + if not srv_config.get("command"): + self.logger.bind(tag=TAG).warning(f"Skipping server {name}: command not specified") + continue + + try: + client = MCPClient(srv_config) + await client.initialize() + self.client[name] = client + self.logger.bind(tag=TAG).info(f"Initialized MCP client: {name}") + client_tools = client.get_available_tools() + self.tools.extend(client_tools) + for tool in client_tools: + func_name = "mcp_"+tool["function"]["name"] + register_function(func_name, tool, ToolType.MCP_CLIENT)(self.execute_tool) + self.conn.func_handler.function_registry.register_function(func_name) + + except Exception as e: + self.logger.bind(tag=TAG).error(f"Failed to initialize MCP server {name}: {e}") + + def get_all_tools(self) -> List[Dict[str, Any]]: + """获取所有服务的工具function定义 + Returns: + List[Dict[str, Any]]: 所有工具的function定义列表 + """ + return self.tools + + def is_mcp_tool(self, tool_name: str) -> bool: + """检查是否是MCP工具 + Args: + tool_name: 工具名称 + Returns: + bool: 是否是MCP工具 + """ + for tool in self.tools: + if tool.get("function") != None and tool["function"].get("name") == tool_name: + return True + return False + + async def execute_tool(self, tool_name: str, arguments: Dict[str, Any]) -> Any: + """执行工具调用 + Args: + tool_name: 工具名称 + arguments: 工具参数 + Returns: + Any: 工具执行结果 + Raises: + ValueError: 工具未找到时抛出 + """ + for client in self.client.values(): + if any(tool_name == tool["name"] for tool in client.tools): + return await client.call_tool(tool_name, arguments) + + raise ValueError(f"Tool {tool_name} not found in any MCP server") + + async def cleanup_all(self) -> None: + for name, client in self.client.items(): + try: + await client.cleanup() + self.logger.bind(tag=TAG).info(f"Cleaned up MCP client: {name}") + except Exception as e: + self.logger.bind(tag=TAG).error(f"Error cleaning up MCP client {name}: {e}") + self.client.clear() diff --git a/main/xiaozhi-server/plugins_func/functions/get_time.py b/main/xiaozhi-server/plugins_func/functions/get_time.py index 1d141ed2..9a9aebfe 100644 --- a/main/xiaozhi-server/plugins_func/functions/get_time.py +++ b/main/xiaozhi-server/plugins_func/functions/get_time.py @@ -6,7 +6,7 @@ get_time_function_desc = { "function": { "name": "get_time", "description": "获取当前时间、日期、星期几", - "parameters": {} + 'parameters': {'type': 'object', 'properties': {}, 'required': []} } } diff --git a/main/xiaozhi-server/plugins_func/register.py b/main/xiaozhi-server/plugins_func/register.py index ccb03b44..fd990760 100644 --- a/main/xiaozhi-server/plugins_func/register.py +++ b/main/xiaozhi-server/plugins_func/register.py @@ -12,6 +12,7 @@ class ToolType(Enum): CHANGE_SYS_PROMPT = (3, "修改系统提示词,切换角色性格或职责") SYSTEM_CTL = (4, "系统控制,影响正常的对话流程,如退出、播放音乐等,需要传递conn参数") IOT_CTL = (5, "IOT设备控制,需要传递conn参数") + MCP_CLIENT = (6, "MCP客户端") def __init__(self, code, message): self.code = code From de9809ca5aa15ba25440e7a5282eef0f6b20325f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=84=E5=87=A4=E7=A7=91=E6=8A=80?= Date: Thu, 20 Mar 2025 09:06:36 +0800 Subject: [PATCH 02/39] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=97=B6=E9=97=B4?= =?UTF-8?q?=E6=8F=92=E4=BB=B6=E5=AF=BC=E8=87=B4chatglm=E5=8D=A1=E4=BD=8F?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main/xiaozhi-server/plugins_func/functions/get_time.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/xiaozhi-server/plugins_func/functions/get_time.py b/main/xiaozhi-server/plugins_func/functions/get_time.py index 1d141ed2..9a9aebfe 100644 --- a/main/xiaozhi-server/plugins_func/functions/get_time.py +++ b/main/xiaozhi-server/plugins_func/functions/get_time.py @@ -6,7 +6,7 @@ get_time_function_desc = { "function": { "name": "get_time", "description": "获取当前时间、日期、星期几", - "parameters": {} + 'parameters': {'type': 'object', 'properties': {}, 'required': []} } } From 2b81ebca8e3bbebef5c90c015f58d4ef355bb1b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=84=E5=87=A4=E7=A7=91=E6=8A=80?= Date: Thu, 20 Mar 2025 11:52:37 +0800 Subject: [PATCH 03/39] =?UTF-8?q?=E8=B0=83=E9=80=9Amcp=20tool?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main/xiaozhi-server/core/connection.py | 52 ++++++++++++++------- main/xiaozhi-server/core/mcp/MCPClient.py | 55 ++++++----------------- main/xiaozhi-server/core/mcp/manager.py | 5 ++- 3 files changed, 53 insertions(+), 59 deletions(-) diff --git a/main/xiaozhi-server/core/connection.py b/main/xiaozhi-server/core/connection.py index b5e7dcf2..088598e2 100644 --- a/main/xiaozhi-server/core/connection.py +++ b/main/xiaozhi-server/core/connection.py @@ -18,7 +18,7 @@ from concurrent.futures import ThreadPoolExecutor, TimeoutError from core.handle.sendAudioHandle import sendAudioMessage from core.handle.receiveAudioHandle import handleAudioMessage from core.handle.functionHandler import FunctionHandler -from plugins_func.register import Action +from plugins_func.register import Action, ActionResponse from config.private_config import PrivateConfig from core.auth import AuthMiddleware, AuthenticationError from core.utils.auth_code_gen import AuthCodeGenerator @@ -196,9 +196,9 @@ class ConnectionHandler: if self.private_config: self.prompt = self.private_config.private_config.get("prompt", self.prompt) - self.client_ip_info = get_ip_info(self.client_ip) - self.logger.bind(tag=TAG).info(f"Client ip info: {self.client_ip_info}") - self.prompt = self.prompt + f"\n我在:{self.client_ip_info}" + #self.client_ip_info = get_ip_info(self.client_ip) + #self.logger.bind(tag=TAG).info(f"Client ip info: {self.client_ip_info}") + #self.prompt = self.prompt + f"\n我在:{self.client_ip_info}" self.dialogue.put(Message(role="system", content=self.prompt)) self.func_handler = FunctionHandler(self.config) @@ -436,24 +436,14 @@ class ConnectionHandler: "id": function_id, "arguments": function_arguments } - #result = self.func_handler.handle_llm_function_call(self, function_call_data) - #self._handle_function_result(result, function_call_data, text_index+1) # 处理MCP工具调用 if self.mcp_manager.is_mcp_tool(function_name): - try: - tool_result = asyncio.create_task(self.mcp_manager.execute_tool( - function_name, - function_arguments - )) - self._handle_mcp_tool_result(tool_result, function_call_data, text_index+1) - except Exception as e: - self.logger.bind(tag=TAG).error(f"MCP工具调用错误: {e}") - response_message.append(f"MCP工具调用失败: {str(e)}") + result = self._handle_mcp_tool_call(function_call_data) else: # 处理系统函数 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) @@ -475,6 +465,36 @@ class ConnectionHandler: return True + def _handle_mcp_tool_call(self, function_call_data): + function_arguments = function_call_data["arguments"] + function_name = function_call_data["name"] + try: + args_dict = function_arguments + if isinstance(function_arguments, str): + try: + args_dict = json.loads(function_arguments) + except json.JSONDecodeError: + self.logger.bind(tag=TAG).error(f"无法解析 function_arguments: {function_arguments}") + return ActionResponse(action=Action.REQLLM, result="参数解析失败", response="") + + tool_result = asyncio.run_coroutine_threadsafe(self.mcp_manager.execute_tool( + function_name, + args_dict + ), self.loop).result() + # meta=None content=[TextContent(type='text', text='北京当前天气:\n温度: 21°C\n天气: 晴\n湿度: 6%\n风向: 西北 风\n风力等级: 5级', annotations=None)] isError=False + self.logger.bind(tag=TAG).info(f"tool_result:{tool_result}") + if tool_result is not None: + if tool_result.content is not None: + print(tool_result.content) + return ActionResponse(action=Action.REQLLM, result=tool_result.content[0].text, response="") + + except Exception as e: + self.logger.bind(tag=TAG).error(f"MCP工具调用错误: {e}") + return ActionResponse(action=Action.REQLLM, result="工具调用出错", response="") + + return ActionResponse(action=Action.REQLLM, result="工具调用出错", response="") + + def _handle_function_result(self, result, function_call_data, text_index): if result.action == Action.RESPONSE: # 直接回复前端 text = result.response diff --git a/main/xiaozhi-server/core/mcp/MCPClient.py b/main/xiaozhi-server/core/mcp/MCPClient.py index 903ce60b..a8be61e1 100644 --- a/main/xiaozhi-server/core/mcp/MCPClient.py +++ b/main/xiaozhi-server/core/mcp/MCPClient.py @@ -1,7 +1,7 @@ import asyncio from typing import Optional from contextlib import AsyncExitStack -import os +import os, shutil from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client @@ -19,34 +19,19 @@ class MCPClient: self.tolls = [] async def initialize(self): - main_command = self.config["command"] args = self.config.get("args", []) - server_script_path = main_command - if args: - # 如果第一个参数是路径,使用其目录作为工作目录 - possible_path = args[0] - if os.path.exists(possible_path): - server_script_path = possible_path - await self.connect_to_server(server_script_path) + command = ( + shutil.which("npx") + if self.config["command"] == "npx" + else self.config["command"] + ) - - async def connect_to_server(self, server_script_path: str): - """Connect to an MCP server + env={**os.environ, **self.config["env"]} - Args: - server_script_path: Path to the server script (.py or .js) - """ - is_python = server_script_path.endswith('.py') - is_js = server_script_path.endswith('.js') - if not (is_python or is_js): - raise ValueError("Server script must be a .py or .js file") - - env = self.config.get("env", {}) - command = "python" if is_python else "node" server_params = StdioServerParameters( command=command, - args=[server_script_path], + args=args, env=env ) @@ -61,7 +46,10 @@ class MCPClient: tools = response.tools self.tools = tools self.logger.bind(tag=TAG).info(f"Connected to server with tools:{[tool.name for tool in tools]}") - + + def has_tool(self, tool_name): + return any(tool.name == tool_name for tool in self.tools) + def get_available_tools(self): available_tools = [{"type": "function", "function":{ "name": tool.name, @@ -72,25 +60,10 @@ class MCPClient: return available_tools async def call_tool(self, tool_name: str, tool_args: dict): + self.logger.bind(tag=TAG).info(f"MCPClient Calling tool {tool_name} with args: {tool_args}") response = await self.session.call_tool(tool_name, tool_args) return response async def cleanup(self): """Clean up resources""" - await self.exit_stack.aclose() - -async def main(): - if len(sys.argv) < 2: - print("Usage: python client.py ") - sys.exit(1) - - client = MCPClient() - try: - await client.connect_to_server(sys.argv[1]) - await client.chat_loop() - finally: - await client.cleanup() - -if __name__ == "__main__": - import sys - asyncio.run(main()) \ No newline at end of file + await self.exit_stack.aclose() \ No newline at end of file diff --git a/main/xiaozhi-server/core/mcp/manager.py b/main/xiaozhi-server/core/mcp/manager.py index f9b4a742..cae3bcf2 100644 --- a/main/xiaozhi-server/core/mcp/manager.py +++ b/main/xiaozhi-server/core/mcp/manager.py @@ -80,14 +80,15 @@ class MCPManager: """执行工具调用 Args: tool_name: 工具名称 - arguments: 工具参数 + arguments: 工具参数 Returns: Any: 工具执行结果 Raises: ValueError: 工具未找到时抛出 """ + self.logger.bind(tag=TAG).info(f"Executing tool {tool_name} with arguments: {arguments}") for client in self.client.values(): - if any(tool_name == tool["name"] for tool in client.tools): + if client.has_tool(tool_name): return await client.call_tool(tool_name, arguments) raise ValueError(f"Tool {tool_name} not found in any MCP server") From 898fd36eca4e3e5b78b4325b8a03038627742f80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=84=E5=87=A4=E7=A7=91=E6=8A=80?= Date: Thu, 20 Mar 2025 18:20:09 +0800 Subject: [PATCH 04/39] =?UTF-8?q?=E5=A2=9E=E5=8A=A0mcp=E8=B0=83=E7=94=A8?= =?UTF-8?q?=E8=B6=85=E6=97=B6=E6=A3=80=E6=B5=8B=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main/xiaozhi-server/core/connection.py | 19 +++++++++------- main/xiaozhi-server/core/mcp/MCPClient.py | 27 ++++++++++++++++++----- main/xiaozhi-server/core/mcp/manager.py | 12 +++++++--- 3 files changed, 42 insertions(+), 16 deletions(-) diff --git a/main/xiaozhi-server/core/connection.py b/main/xiaozhi-server/core/connection.py index 088598e2..e536d00e 100644 --- a/main/xiaozhi-server/core/connection.py +++ b/main/xiaozhi-server/core/connection.py @@ -322,9 +322,6 @@ class ConnectionHandler: # Define intent functions functions = self.func_handler.get_functions() - mcpfuncs = self.mcp_manager.get_all_tools() # MCP工具 - functions.extend(mcpfuncs) - print("functions with mcp", functions) response_message = [] processed_chars = 0 # 跟踪已处理的字符位置 @@ -482,11 +479,17 @@ class ConnectionHandler: args_dict ), self.loop).result() # meta=None content=[TextContent(type='text', text='北京当前天气:\n温度: 21°C\n天气: 晴\n湿度: 6%\n风向: 西北 风\n风力等级: 5级', annotations=None)] isError=False - self.logger.bind(tag=TAG).info(f"tool_result:{tool_result}") - if tool_result is not None: - if tool_result.content is not None: - print(tool_result.content) - return ActionResponse(action=Action.REQLLM, result=tool_result.content[0].text, response="") + content_text = "" + if tool_result is not None and tool_result.content is not None: + for content in tool_result.content: + content_type = content.type + if content_type == "text": + content_text = content.text + elif content_type == "image": + pass + + if len(content_text) > 0: + return ActionResponse(action=Action.REQLLM, result=content_text, response="") except Exception as e: self.logger.bind(tag=TAG).error(f"MCP工具调用错误: {e}") diff --git a/main/xiaozhi-server/core/mcp/MCPClient.py b/main/xiaozhi-server/core/mcp/MCPClient.py index a8be61e1..3c32e077 100644 --- a/main/xiaozhi-server/core/mcp/MCPClient.py +++ b/main/xiaozhi-server/core/mcp/MCPClient.py @@ -1,4 +1,4 @@ -import asyncio +from datetime import timedelta from typing import Optional from contextlib import AsyncExitStack import os, shutil @@ -26,8 +26,10 @@ class MCPClient: if self.config["command"] == "npx" else self.config["command"] ) - - env={**os.environ, **self.config["env"]} + + env={**os.environ} + if self.config.get("env"): + env.update(self.config["env"]) server_params = StdioServerParameters( command=command, @@ -37,7 +39,8 @@ class MCPClient: stdio_transport = await self.exit_stack.enter_async_context(stdio_client(server_params)) self.stdio, self.write = stdio_transport - self.session = await self.exit_stack.enter_async_context(ClientSession(self.stdio, self.write)) + time_out_delta = timedelta(seconds=10) + self.session = await self.exit_stack.enter_async_context(ClientSession(read_stream=self.stdio, write_stream=self.write, read_timeout_seconds=time_out_delta)) await self.session.initialize() @@ -61,7 +64,21 @@ class MCPClient: async def call_tool(self, tool_name: str, tool_args: dict): self.logger.bind(tag=TAG).info(f"MCPClient Calling tool {tool_name} with args: {tool_args}") - response = await self.session.call_tool(tool_name, tool_args) + try: + response = await self.session.call_tool(tool_name, tool_args) + except Exception as e: + self.logger.bind(tag=TAG).error(f"Error calling tool {tool_name}: {e}") + from types import SimpleNamespace + error_content = SimpleNamespace( + type='text', + text=f"Error calling tool {tool_name}: {e}" + ) + error_response = SimpleNamespace( + content=[error_content], + isError=True + ) + return error_response + self.logger.bind(tag=TAG).info(f"MCPClient Response from tool {tool_name}: {response}") return response async def cleanup(self): diff --git a/main/xiaozhi-server/core/mcp/manager.py b/main/xiaozhi-server/core/mcp/manager.py index cae3bcf2..810554e7 100644 --- a/main/xiaozhi-server/core/mcp/manager.py +++ b/main/xiaozhi-server/core/mcp/manager.py @@ -1,5 +1,5 @@ """MCP服务管理器""" -import json +import os, json from typing import Dict, Any, List from .MCPClient import MCPClient from config.logger import setup_logging @@ -16,9 +16,12 @@ class MCPManager: 初始化MCP管理器 """ self.conn = conn - self.config_path = get_project_dir() + 'data/.mcp_server_settings.json' - self.client: Dict[str, MCPClient] = {} self.logger = setup_logging() + self.config_path = get_project_dir() + 'data/.mcp_server_settings.json' + if os.path.exists(self.config_path) == False: + self.config_path = "" + self.logger.bind(tag=TAG).warning(f"请检查mcp服务配置文件:data/.mcp_server_settings.json") + self.client: Dict[str, MCPClient] = {} self.tools = [] def load_config(self) -> Dict[str, Any]: @@ -26,6 +29,9 @@ class MCPManager: Returns: Dict[str, Any]: 服务配置字典 """ + if len(self.config_path) == 0: + return {} + try: with open(self.config_path, 'r', encoding='utf-8') as f: config = json.load(f) From 3ea95ffa48cf17d92471734b5b4c6585eb845d4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=84=E5=87=A4=E7=A7=91=E6=8A=80?= Date: Thu, 20 Mar 2025 18:20:50 +0800 Subject: [PATCH 05/39] =?UTF-8?q?mcp=E6=A8=A1=E6=9D=BF=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main/xiaozhi-server/mcp_server_settings.json | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 main/xiaozhi-server/mcp_server_settings.json diff --git a/main/xiaozhi-server/mcp_server_settings.json b/main/xiaozhi-server/mcp_server_settings.json new file mode 100644 index 00000000..1beb79a3 --- /dev/null +++ b/main/xiaozhi-server/mcp_server_settings.json @@ -0,0 +1,16 @@ +{ + "mcpServers": { + "filesystem": { + "command": "npx", + "args": [ + "-y", + "@modelcontextprotocol/server-filesystem", + "/path/to/other/allowed/dir" + ] + }, + "puppeteer": { + "command": "npx", + "args": ["-y", "@modelcontextprotocol/server-puppeteer"] + } + } +} From 4409f370006df98ccad7634fae46caa05c110cc4 Mon Sep 17 00:00:00 2001 From: Erlei Chen Date: Fri, 21 Mar 2025 01:39:23 +0800 Subject: [PATCH 06/39] =?UTF-8?q?=E5=90=88=E5=B9=B6=E6=A0=B7=E5=BC=8F?= =?UTF-8?q?=E5=8F=8A=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main/manager-web/src/apis/api.js | 6 +- main/manager-web/src/apis/module/agent.js | 84 ++++++ main/manager-web/src/apis/module/device.js | 51 ++++ main/manager-web/src/apis/module/user.js | 16 -- .../src/components/AddAgentDialog.vue | 75 ++++++ main/manager-web/src/components/AgentItem.vue | 77 ++++++ main/manager-web/src/components/Footer.vue | 30 +++ main/manager-web/src/components/HeaderBar.vue | 114 ++++---- main/manager-web/src/router/index.js | 7 + main/manager-web/src/views/device.vue | 251 ++++++++++++++++++ main/manager-web/src/views/home.vue | 115 +++++--- main/manager-web/src/views/roleConfig.vue | 129 ++++----- 12 files changed, 756 insertions(+), 199 deletions(-) create mode 100755 main/manager-web/src/apis/module/agent.js create mode 100755 main/manager-web/src/apis/module/device.js create mode 100644 main/manager-web/src/components/AddAgentDialog.vue create mode 100644 main/manager-web/src/components/AgentItem.vue create mode 100644 main/manager-web/src/components/Footer.vue create mode 100644 main/manager-web/src/views/device.vue diff --git a/main/manager-web/src/apis/api.js b/main/manager-web/src/apis/api.js index bb6ec96b..38c2f86b 100755 --- a/main/manager-web/src/apis/api.js +++ b/main/manager-web/src/apis/api.js @@ -1,4 +1,6 @@ // 引入各个模块的请求 +import agent from './module/agent.js' +import device from './module/device.js' import user from './module/user.js' /** @@ -7,7 +9,7 @@ import user from './module/user.js' * 如果你想调用8002端口,就用'/xiaozhi-esp32-api/api/v1',请与vue.config.js的devServer配置相结合,方便跨域请求 * */ -const DEV_API_SERVICE = 'https://apifoxmock.com/m1/5931378-5618560-default' +const DEV_API_SERVICE = 'https://m1.apifoxmock.com/m1/5931378-5618560-default' // 8002开发完成完成后使用这个 // const DEV_API_SERVICE = '/xiaozhi-esp32-api' @@ -24,4 +26,6 @@ export function getServiceUrl() { export default { getServiceUrl, user, + agent, + device } diff --git a/main/manager-web/src/apis/module/agent.js b/main/manager-web/src/apis/module/agent.js new file mode 100755 index 00000000..721f075c --- /dev/null +++ b/main/manager-web/src/apis/module/agent.js @@ -0,0 +1,84 @@ +import RequestService from '../httpRequest' +import {getServiceUrl} from '../api' + + +export default { + //智能体列表 + getAgentList(callback) { + RequestService.sendRequest().url(`${getServiceUrl()}/api/v1/user/agent`) + .method('GET') + .success((res) => { + RequestService.clearRequestTime() + callback(res) + }) + .fail((err) => { + console.error('获取智能体列表失败:', err); + }).send() + }, + //添加智能体 + addAgent(name, callback) { + RequestService.sendRequest() + .url(`${getServiceUrl()}/api/v1/user/agent`) + .method('POST') + .data({name}) + .success((res) => { + RequestService.clearRequestTime(); + callback(res); + }) + .fail((err) => { + console.error('添加智能体失败:', err); + RequestService.reAjaxFun(() => { + this.addAgent(name, callback); + }); + }).send(); + }, + //获取智能体配置 + getAgentConfig(agent_id, callback) { + RequestService.sendRequest() + .url(`${getServiceUrl()}/api/v1/user/agent/${agent_id}`) + .method('GET') + .success((res) => { + RequestService.clearRequestTime(); + callback(res); + }) + .fail((err) => { + console.error('获取智能体配置失败:', err); + RequestService.reAjaxFun(() => { + this.getAgentConfig(agent_id, callback); + }); + }).send(); + }, + //配置智能体 + saveAgentConfig(agent_id, configData, callback) { + RequestService.sendRequest() + .url(`${getServiceUrl()}/api/v1/user/agent/${agent_id}`) + .method('PUT') + .data(configData) + .success((res) => { + RequestService.clearRequestTime(); + callback(res); + }) + .fail((err) => { + console.error('保存智能体配置失败:', err); + RequestService.reAjaxFun(() => { + this.saveAgentConfig(device_id, configData, callback); + }); + }).send(); + }, + //删除智能体 + delAgent(agent_id, callback) { + RequestService.sendRequest() + .url(`${getServiceUrl()}/api/v1/user/agent/${agent_id}`) + .method('DELETE') + .success((res) => { + RequestService.clearRequestTime(); + callback(res); + }) + .fail((err) => { + console.error('删除智能体失败:', err); + RequestService.reAjaxFun(() => { + this.deleteAgent(agent_id, callback); + }); + }).send(); + }, +} diff --git a/main/manager-web/src/apis/module/device.js b/main/manager-web/src/apis/module/device.js new file mode 100755 index 00000000..cd568745 --- /dev/null +++ b/main/manager-web/src/apis/module/device.js @@ -0,0 +1,51 @@ +import RequestService from '../httpRequest' +import {getServiceUrl} from '../api' + + +export default { + //设备列表 + getDeviceList(agent_id, callback) { + RequestService.sendRequest().url(`${getServiceUrl()}/api/v1/user/agent/device/bind/${agent_id}`) + .method('GET') + .success((res) => { + RequestService.clearRequestTime() + callback(res) + }) + .fail((err) => { + console.error('获取设备列表失败:', err); + }).send() + }, + //绑定设备 + bindDevice(agent_id, code, callback) { + RequestService.sendRequest() + .url(`${getServiceUrl()}/api/v1/user/agent/device/bind/${agent_id}`) + .method('POST') + .data({code}) + .success((res) => { + RequestService.clearRequestTime(); + callback(res); + }) + .fail((err) => { + console.error('绑定设备失败:', err); + RequestService.reAjaxFun(() => { + this.addDevice(name, callback); + }); + }).send(); + }, + // 解绑设备 + unbindDevice(device_id, callback) { + RequestService.sendRequest() + .url(`${getServiceUrl()}/api/v1/user/device/unbind/${device_id}`) + .method('PUT') + .success((res) => { + RequestService.clearRequestTime(); + callback(res); + }) + .fail(() => { + RequestService.reAjaxFun(() => { + this.unbindDevice(device_id, callback); + }); + }).send() + }, + +} diff --git a/main/manager-web/src/apis/module/user.js b/main/manager-web/src/apis/module/user.js index bf3eea34..1879afaf 100755 --- a/main/manager-web/src/apis/module/user.js +++ b/main/manager-web/src/apis/module/user.js @@ -47,22 +47,6 @@ export default { }); }).send() }, - // 绑定设备 - bindDevice(deviceCode, callback) { - RequestService.sendRequest() - .url(`${getServiceUrl()}/api/v1/user/device/bind/${deviceCode}`) - .method('POST') - .success((res) => { - RequestService.clearRequestTime(); - callback(res); - }) - .fail((err) => { - console.error('绑定设备失败:', err); - RequestService.reAjaxFun(() => { - this.bindDevice(deviceCode, callback); - }); - }).send(); - }, // 获取验证码 getCaptcha(uuid, callback) { diff --git a/main/manager-web/src/components/AddAgentDialog.vue b/main/manager-web/src/components/AddAgentDialog.vue new file mode 100644 index 00000000..a7df2940 --- /dev/null +++ b/main/manager-web/src/components/AddAgentDialog.vue @@ -0,0 +1,75 @@ + + + + + \ No newline at end of file diff --git a/main/manager-web/src/components/AgentItem.vue b/main/manager-web/src/components/AgentItem.vue new file mode 100644 index 00000000..eb0b852d --- /dev/null +++ b/main/manager-web/src/components/AgentItem.vue @@ -0,0 +1,77 @@ + + + + \ No newline at end of file diff --git a/main/manager-web/src/components/Footer.vue b/main/manager-web/src/components/Footer.vue new file mode 100644 index 00000000..3ccb543c --- /dev/null +++ b/main/manager-web/src/components/Footer.vue @@ -0,0 +1,30 @@ + + + + + \ No newline at end of file diff --git a/main/manager-web/src/components/HeaderBar.vue b/main/manager-web/src/components/HeaderBar.vue index acc0e981..00400e30 100644 --- a/main/manager-web/src/components/HeaderBar.vue +++ b/main/manager-web/src/components/HeaderBar.vue @@ -1,32 +1,40 @@ From 7ead5537f622c10e5395fd2ee37f7cc71ea05601 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=84=E5=87=A4=E7=A7=91=E6=8A=80?= Date: Fri, 21 Mar 2025 17:15:54 +0800 Subject: [PATCH 07/39] =?UTF-8?q?=E6=8F=90=E4=BA=A4requirements.txt?= =?UTF-8?q?=E4=B8=ADmcp=E7=89=88=E6=9C=AC=E4=BE=9D=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main/xiaozhi-server/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/main/xiaozhi-server/requirements.txt b/main/xiaozhi-server/requirements.txt index 81d0cdfd..386d054c 100755 --- a/main/xiaozhi-server/requirements.txt +++ b/main/xiaozhi-server/requirements.txt @@ -20,3 +20,4 @@ requests==2.32.3 cozepy==0.12.0 mem0ai==0.1.62 bs4==0.0.2 +mcp==1.4.1 \ No newline at end of file From dc4968f9614c636eb12a234bf51356b6ee5e98c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=84=E5=87=A4=E7=A7=91=E6=8A=80?= Date: Fri, 21 Mar 2025 17:31:29 +0800 Subject: [PATCH 08/39] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=91=BD=E4=BB=A4?= =?UTF-8?q?=E8=A1=8C=E6=9C=8D=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main/xiaozhi-server/mcp_server_settings.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/main/xiaozhi-server/mcp_server_settings.json b/main/xiaozhi-server/mcp_server_settings.json index 1beb79a3..7e4dd5d2 100644 --- a/main/xiaozhi-server/mcp_server_settings.json +++ b/main/xiaozhi-server/mcp_server_settings.json @@ -11,6 +11,10 @@ "puppeteer": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-puppeteer"] + }, + "windows-cli": { + "command": "npx", + "args": ["-y", "@simonb97/server-win-cli"] } } } From a7730f6e9339ffb3773c9b6e8d9ce704db22d3fb Mon Sep 17 00:00:00 2001 From: Erlei Chen Date: Sat, 22 Mar 2025 08:33:24 +0800 Subject: [PATCH 09/39] =?UTF-8?q?update:=E4=BF=AE=E6=94=B9=E6=A0=B7?= =?UTF-8?q?=E5=BC=8F=E3=80=81=E8=B0=83=E6=95=B4=E9=A1=B5=E9=9D=A2=E5=91=BD?= =?UTF-8?q?=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/AddAgentDialog.vue | 57 ++-- .../src/components/AddDeviceDialog.vue | 7 - .../src/components/AddWisdomBodyDialog.vue | 97 ------ .../manager-web/src/components/DeviceItem.vue | 87 ------ main/manager-web/src/components/HeaderBar.vue | 9 +- main/manager-web/src/router/index.js | 27 +- .../src/views/DeviceManagement.vue | 169 ----------- main/manager-web/src/views/device.vue | 282 +++++++----------- main/manager-web/src/views/home.vue | 2 +- 9 files changed, 152 insertions(+), 585 deletions(-) delete mode 100644 main/manager-web/src/components/AddWisdomBodyDialog.vue delete mode 100644 main/manager-web/src/components/DeviceItem.vue delete mode 100644 main/manager-web/src/views/DeviceManagement.vue diff --git a/main/manager-web/src/components/AddAgentDialog.vue b/main/manager-web/src/components/AddAgentDialog.vue index a7df2940..026b646e 100644 --- a/main/manager-web/src/components/AddAgentDialog.vue +++ b/main/manager-web/src/components/AddAgentDialog.vue @@ -1,21 +1,21 @@ \ No newline at end of file diff --git a/main/manager-web/src/components/AddDeviceDialog.vue b/main/manager-web/src/components/AddDeviceDialog.vue index e7ba492e..f98ff7d9 100644 --- a/main/manager-web/src/components/AddDeviceDialog.vue +++ b/main/manager-web/src/components/AddDeviceDialog.vue @@ -53,13 +53,6 @@ export default { \ No newline at end of file diff --git a/main/manager-web/src/components/DeviceItem.vue b/main/manager-web/src/components/DeviceItem.vue deleted file mode 100644 index 912fd9d5..00000000 --- a/main/manager-web/src/components/DeviceItem.vue +++ /dev/null @@ -1,87 +0,0 @@ - - - - \ No newline at end of file diff --git a/main/manager-web/src/components/HeaderBar.vue b/main/manager-web/src/components/HeaderBar.vue index 00400e30..c3e6896e 100644 --- a/main/manager-web/src/components/HeaderBar.vue +++ b/main/manager-web/src/components/HeaderBar.vue @@ -5,17 +5,18 @@ -
@@ -109,7 +110,7 @@ export default { display: flex; align-items: center; justify-content: center; - gap: 10px; + gap: 4px; font-weight: 500; font-size: 14px; cursor: pointer; diff --git a/main/manager-web/src/router/index.js b/main/manager-web/src/router/index.js index 6ceb086f..f32c14ba 100644 --- a/main/manager-web/src/router/index.js +++ b/main/manager-web/src/router/index.js @@ -12,17 +12,10 @@ const routes = [ } }, { - path: '/device', - name: 'RoleConfig', + path: '/register', + name: 'Register', component: function () { - return import('../views/device.vue') - } - }, - { - path: '/role-config', - name: 'RoleConfig', - component: function () { - return import('../views/roleConfig.vue') + return import('../views/register.vue') } }, { @@ -40,20 +33,18 @@ const routes = [ } }, { - path: '/register', - name: 'Register', + path: '/role-config', + name: 'RoleConfig', component: function () { - return import('../views/register.vue') + return import('../views/roleConfig.vue') } }, - // 新增设备管理页面路由 { - path: '/device-management', - name: 'DeviceManagement', + path: '/device', + name: 'Device', component: function () { - return import('../views/DeviceManagement.vue') + return import('../views/device.vue') } - } ] diff --git a/main/manager-web/src/views/DeviceManagement.vue b/main/manager-web/src/views/DeviceManagement.vue deleted file mode 100644 index 06c4317c..00000000 --- a/main/manager-web/src/views/DeviceManagement.vue +++ /dev/null @@ -1,169 +0,0 @@ - - - - - \ No newline at end of file diff --git a/main/manager-web/src/views/device.vue b/main/manager-web/src/views/device.vue index 05daf0a1..bbfe41fc 100644 --- a/main/manager-web/src/views/device.vue +++ b/main/manager-web/src/views/device.vue @@ -1,104 +1,59 @@ \ No newline at end of file diff --git a/main/manager-web/src/views/home.vue b/main/manager-web/src/views/home.vue index 80cfee1a..0c55e9f6 100644 --- a/main/manager-web/src/views/home.vue +++ b/main/manager-web/src/views/home.vue @@ -205,7 +205,7 @@ export default { border-radius: 17px; background: #5778ff; color: #fff; - font-size: 10px; + font-size: 12px; font-weight: 500; text-align: center; line-height: 34px; From bdd18b384b87a1d13d8ed73a56dc3421b4313dd0 Mon Sep 17 00:00:00 2001 From: Erlei Chen Date: Mon, 24 Mar 2025 09:26:45 +0800 Subject: [PATCH 10/39] =?UTF-8?q?feature=EF=BC=9A=201.OTA=E5=9F=BA?= =?UTF-8?q?=E6=9C=AC=E8=83=BD=E5=8A=9B=EF=BC=88=E5=9B=BA=E5=AE=9A=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=EF=BC=89=EF=BC=8C=E5=BE=85=E4=BC=98=E5=8C=96=202.?= =?UTF-8?q?=E6=99=BA=E8=83=BD=E4=BD=93=E7=AE=A1=E7=90=86=E4=B8=8E=E8=AE=BE?= =?UTF-8?q?=E5=A4=87=E7=AE=A1=E7=90=86=E5=AF=B9=E6=8E=A5api=EF=BC=8C?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E6=B5=81=E7=A8=8B=E9=97=AD=E7=8E=AF=EF=BC=88?= =?UTF-8?q?=E5=B7=B2=E6=B5=8B=E8=AF=95=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main/manager-api/pom.xml | 6 + .../common/controller/BaseController.java | 46 +++++ .../agent/controller/UserAgentController.java | 195 ++++++++++++++++++ .../xiaozhi/modules/agent/domain/Agent.java | 116 +++++++++++ .../modules/agent/domain/AgentTemplate.java | 116 +++++++++++ .../modules/agent/mapper/AgentMapper.java | 20 ++ .../agent/mapper/AgentTemplateMapper.java | 20 ++ .../modules/agent/service/AgentService.java | 13 ++ .../agent/service/AgentTemplateService.java | 13 ++ .../agent/service/impl/AgentServiceImpl.java | 22 ++ .../impl/AgentTemplateServiceImpl.java | 22 ++ .../modules/agent/vo/AgentConfigVO.java | 17 ++ .../xiaozhi/modules/agent/vo/AgentVO.java | 19 ++ .../device/constant/DeviceConstant.java | 7 + .../device/controller/DeviceController.java | 82 -------- .../device/controller/OtaController.java | 81 ++++++++ .../controller/UserDeviceController.java | 127 ++++++++++++ .../xiaozhi/modules/device/dao/DeviceDao.java | 9 - .../xiaozhi/modules/device/domain/Device.java | 91 ++++++++ .../modules/device/dto/DeviceHeaderDTO.java | 19 -- .../modules/device/dto/DeviceUnBindDTO.java | 19 -- .../modules/device/entity/DeviceEntity.java | 54 ----- .../modules/device/mapper/DeviceMapper.java | 20 ++ .../modules/device/service/DeviceService.java | 31 ++- .../service/impl/DeviceServiceImpl.java | 79 ++----- .../device/utils/CodeGeneratorUtil.java | 68 ++++++ .../modules/device/vo/DeviceCodeVO.java | 10 + .../modules/device/vo/DeviceOtaVO.java | 55 +++++ .../modules/model/domain/ModelConfig.java | 91 ++++++++ .../modules/model/domain/TtsVoice.java | 81 ++++++++ .../model/mapper/ModelConfigMapper.java | 20 ++ .../modules/model/mapper/TtsVoiceMapper.java | 20 ++ .../model/service/ModelConfigService.java | 13 ++ .../model/service/TtsVoiceService.java | 13 ++ .../service/impl/ModelConfigServiceImpl.java | 22 ++ .../service/impl/TtsVoiceServiceImpl.java | 22 ++ .../modules/security/config/ShiroConfig.java | 2 + .../src/main/resources/application-dev.yml | 20 +- .../src/main/resources/application.yml | 17 +- .../resources/mapper/agent/AgentMapper.xml | 35 ++++ .../mapper/agent/AgentTemplateMapper.xml | 35 ++++ .../resources/mapper/device/DeviceMapper.xml | 29 +++ .../mapper/model/ModelConfigMapper.xml | 29 +++ .../resources/mapper/model/TtsVoiceMapper.xml | 27 +++ main/manager-web/src/apis/api.js | 4 +- main/manager-web/src/apis/module/agent.js | 7 +- main/manager-web/src/apis/module/device.js | 17 +- .../src/components/AddAgentDialog.vue | 9 +- .../src/components/AddDeviceDialog.vue | 6 +- main/manager-web/src/utils/date.js | 7 +- main/manager-web/src/views/device.vue | 64 ++++-- main/manager-web/src/views/home.vue | 6 +- main/manager-web/src/views/roleConfig.vue | 24 +-- main/xiaozhi-server/config.yaml | 5 + main/xiaozhi-server/config/private_config.py | 77 ++++++- 55 files changed, 1747 insertions(+), 332 deletions(-) create mode 100644 main/manager-api/src/main/java/xiaozhi/common/controller/BaseController.java create mode 100644 main/manager-api/src/main/java/xiaozhi/modules/agent/controller/UserAgentController.java create mode 100644 main/manager-api/src/main/java/xiaozhi/modules/agent/domain/Agent.java create mode 100644 main/manager-api/src/main/java/xiaozhi/modules/agent/domain/AgentTemplate.java create mode 100644 main/manager-api/src/main/java/xiaozhi/modules/agent/mapper/AgentMapper.java create mode 100644 main/manager-api/src/main/java/xiaozhi/modules/agent/mapper/AgentTemplateMapper.java create mode 100644 main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentService.java create mode 100644 main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentTemplateService.java create mode 100644 main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentServiceImpl.java create mode 100644 main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentTemplateServiceImpl.java create mode 100644 main/manager-api/src/main/java/xiaozhi/modules/agent/vo/AgentConfigVO.java create mode 100644 main/manager-api/src/main/java/xiaozhi/modules/agent/vo/AgentVO.java create mode 100644 main/manager-api/src/main/java/xiaozhi/modules/device/constant/DeviceConstant.java delete mode 100644 main/manager-api/src/main/java/xiaozhi/modules/device/controller/DeviceController.java create mode 100644 main/manager-api/src/main/java/xiaozhi/modules/device/controller/OtaController.java create mode 100644 main/manager-api/src/main/java/xiaozhi/modules/device/controller/UserDeviceController.java delete mode 100644 main/manager-api/src/main/java/xiaozhi/modules/device/dao/DeviceDao.java create mode 100644 main/manager-api/src/main/java/xiaozhi/modules/device/domain/Device.java delete mode 100644 main/manager-api/src/main/java/xiaozhi/modules/device/dto/DeviceHeaderDTO.java delete mode 100644 main/manager-api/src/main/java/xiaozhi/modules/device/dto/DeviceUnBindDTO.java delete mode 100644 main/manager-api/src/main/java/xiaozhi/modules/device/entity/DeviceEntity.java create mode 100644 main/manager-api/src/main/java/xiaozhi/modules/device/mapper/DeviceMapper.java create mode 100644 main/manager-api/src/main/java/xiaozhi/modules/device/utils/CodeGeneratorUtil.java create mode 100644 main/manager-api/src/main/java/xiaozhi/modules/device/vo/DeviceCodeVO.java create mode 100644 main/manager-api/src/main/java/xiaozhi/modules/device/vo/DeviceOtaVO.java create mode 100644 main/manager-api/src/main/java/xiaozhi/modules/model/domain/ModelConfig.java create mode 100644 main/manager-api/src/main/java/xiaozhi/modules/model/domain/TtsVoice.java create mode 100644 main/manager-api/src/main/java/xiaozhi/modules/model/mapper/ModelConfigMapper.java create mode 100644 main/manager-api/src/main/java/xiaozhi/modules/model/mapper/TtsVoiceMapper.java create mode 100644 main/manager-api/src/main/java/xiaozhi/modules/model/service/ModelConfigService.java create mode 100644 main/manager-api/src/main/java/xiaozhi/modules/model/service/TtsVoiceService.java create mode 100644 main/manager-api/src/main/java/xiaozhi/modules/model/service/impl/ModelConfigServiceImpl.java create mode 100644 main/manager-api/src/main/java/xiaozhi/modules/model/service/impl/TtsVoiceServiceImpl.java create mode 100644 main/manager-api/src/main/resources/mapper/agent/AgentMapper.xml create mode 100644 main/manager-api/src/main/resources/mapper/agent/AgentTemplateMapper.xml create mode 100644 main/manager-api/src/main/resources/mapper/device/DeviceMapper.xml create mode 100644 main/manager-api/src/main/resources/mapper/model/ModelConfigMapper.xml create mode 100644 main/manager-api/src/main/resources/mapper/model/TtsVoiceMapper.xml diff --git a/main/manager-api/pom.xml b/main/manager-api/pom.xml index eacbdff9..0857ec3c 100644 --- a/main/manager-api/pom.xml +++ b/main/manager-api/pom.xml @@ -168,6 +168,12 @@ lombok true + + + org.apache.logging.log4j + log4j-slf4j-impl + 2.20.0 + diff --git a/main/manager-api/src/main/java/xiaozhi/common/controller/BaseController.java b/main/manager-api/src/main/java/xiaozhi/common/controller/BaseController.java new file mode 100644 index 00000000..8cda64c0 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/common/controller/BaseController.java @@ -0,0 +1,46 @@ +package xiaozhi.common.controller; + +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.Enumeration; +import java.util.HashMap; + +public class BaseController { + + @Autowired + protected HttpServletRequest request; + + /** + * 获取请求头 + * + * @return + */ + protected HashMap getRequestHeaders() { + HashMap requestHeaders = new HashMap(); + Enumeration headerNames = request.getHeaderNames(); + while (headerNames.hasMoreElements()) { + String headerName = headerNames.nextElement(); + String headerValue = request.getHeader(headerName); + requestHeaders.put(headerName, headerValue); + } + return requestHeaders; + } + + /** + * 获取请求参数 + * + * @return + */ + protected HashMap getRequestParams() { + HashMap requestParams = new HashMap(); + Enumeration paramNames = request.getParameterNames(); + while (paramNames.hasMoreElements()) { + String paramName = paramNames.nextElement(); + String paramValue = request.getParameter(paramName); + requestParams.put(paramName, paramValue); + } + return requestParams; + } + +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/UserAgentController.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/UserAgentController.java new file mode 100644 index 00000000..0e13fac2 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/UserAgentController.java @@ -0,0 +1,195 @@ +package xiaozhi.modules.agent.controller; + +import cn.hutool.json.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.ObjectUtils; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.beanutils.BeanUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.web.bind.annotation.*; +import xiaozhi.common.controller.BaseController; +import xiaozhi.common.redis.RedisUtils; +import xiaozhi.common.user.UserDetail; +import xiaozhi.common.utils.Result; +import xiaozhi.modules.agent.domain.Agent; +import xiaozhi.modules.agent.domain.AgentTemplate; +import xiaozhi.modules.agent.service.AgentService; +import xiaozhi.modules.agent.service.AgentTemplateService; +import xiaozhi.modules.agent.vo.AgentConfigVO; +import xiaozhi.modules.agent.vo.AgentVO; +import xiaozhi.modules.device.domain.Device; +import xiaozhi.modules.device.service.DeviceService; +import xiaozhi.modules.model.domain.ModelConfig; +import xiaozhi.modules.model.domain.TtsVoice; +import xiaozhi.modules.model.service.ModelConfigService; +import xiaozhi.modules.model.service.TtsVoiceService; +import xiaozhi.modules.security.user.SecurityUser; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.UUID; + +@Slf4j +@Tag(name = "智能体管理") +@AllArgsConstructor +@RestController +@RequestMapping("/user/agent") +public class UserAgentController extends BaseController { + private final AgentService agentService; + private final AgentTemplateService agentTemplateService; + private final ModelConfigService modelConfigService; + private final TtsVoiceService ttsVoiceService; + private final DeviceService deviceService; + private final RedisUtils redisUtils; + + @PostMapping + @Operation(summary = "添加智能体") + public Result addAgent(@RequestBody Agent agent) { + UserDetail user = SecurityUser.getUser(); + if(StringUtils.isBlank(agent.getAgentName())){ + log.error("智能体名称不能为空"); + } + Agent oldAgent = agentService.getOne(new QueryWrapper().eq("agent_name", agent.getAgentName())); + if(ObjectUtils.isNull(oldAgent)){ + AgentTemplate agentTemplate = agentTemplateService.getOne(new QueryWrapper().eq("is_default", 1)); + if(ObjectUtils.isNull(agentTemplate)){ + + }else{ + try { + oldAgent = new Agent(); + BeanUtils.copyProperties(oldAgent, agentTemplate); + oldAgent.setId(UUID.randomUUID().toString().replace("-","")); + oldAgent.setAgentName(agent.getAgentName()); + oldAgent.setUserId(user.getId()); + oldAgent.setCreator(user.getId()); + oldAgent.setCreatedAt(new Date()); + agentService.save(oldAgent); + } catch (Exception e) { + log.error("对象赋值异常", e); + } + } + }else{ + + } + return new Result().ok(agent); + } + + @GetMapping + @Operation(summary = "智能体列表") + public Result> agentList() { + UserDetail user = SecurityUser.getUser(); + List agents = agentService.list(new QueryWrapper().eq("user_id",user.getId())); + List list = new ArrayList<>(); + this.convertAgetVOList(list, agents); + return new Result>().ok(list); + } + + @GetMapping("/loadAgentConfig/{deviceId}") + @Operation(summary = "下载智能体配置") +// public Result loadAgentConfig(@PathVariable String deviceId){ + public Result loadAgentConfig(@PathVariable String deviceId){ + Device device = deviceService.getOne(new QueryWrapper().eq("mac_address", deviceId.toUpperCase())); + if(ObjectUtils.isNull(device)){ + return new Result().error("设备不存在"); + } + Agent agent = agentService.getOne(new QueryWrapper().eq("id", device.getAgentId())); + if(ObjectUtils.isNull(agent)){ + return new Result().error("智能体不存在"); + } + AgentConfigVO agentConfigVO = new AgentConfigVO(); +// return new Result().ok(agentConfigVO); + String json = "{\n" + + " \"ASR\": {\n" + + " \"FunASR\": {\n" + + " \"model_dir\": \"models/SenseVoiceSmall\",\n" + + " \"output_dir\": \"tmp/\",\n" + + " \"type\": \"fun_local\"\n" + + " }\n" + + " },\n" + + " \"LLM\": {\n" + + " \"ChatGLMLLM\": {\n" + + " \"api_key\": \"0415dad4014847babc3e3f03024c50a3.qH7FgTy5Yawc85fl\",\n" + + " \"model_name\": \"glm-4-flash\",\n" + + " \"type\": \"openai\",\n" + + " \"url\": \"https://open.bigmodel.cn/api/paas/v4/\"\n" + + " }\n" + + " },\n" + + " \"TTS\": {\n" + + " \"DoubaoTTS\": {\n" + + " \"access_token\": \"hrnx22F9WutWBm7YJzE62r_Z1myUmHEL\",\n" + + " \"api_url\": \"https://openspeech.bytedance.com/api/v1/tts\",\n" + + " \"appid\": \"6295576095\",\n" + + " \"authorization\": \"Bearer;\",\n" + + " \"cluster\": \"volcano_tts\",\n" + + " \"output_dir\": \"tmp/\",\n" + + " \"type\": \"doubao\",\n" + + " \"voice\": \"BV034_streaming\"\n" + + " }\n" + + " },\n" + + " \"VAD\": {\n" + + " \"SileroVAD\": {\n" + + " \"min_silence_duration_ms\": 700,\n" + + " \"model_dir\": \"models/snakers4_silero-vad\",\n" + + " \"threshold\": 0.5\n" + + " }\n" + + " },\n" + + " \"auth_code\": \"642365\",\n" + + " \"prompt\": \"你是一个叫小优的女孩,来自优享生活公司的AI智能体,声音好听,习惯简短表达,爱用网络梗。\\n请注意,要像一个人一样说话,请不要回复表情符号、代码、和xml标签。\\n现在我正在和你进行语音聊天,我们开始吧。\\n如果用户希望结束对话,请在最后说“拜拜”或“再见”。\\n\",\n" + + " \"selected_module\": {\n" + + " \"ASR\": \"FunASR\",\n" + + " \"Intent\": \"function_call\",\n" + + " \"LLM\": \"ChatGLMLLM\",\n" + + " \"Memory\": \"mem0ai\",\n" + + " \"TTS\": \"DoubaoTTS\",\n" + + " \"VAD\": \"SileroVAD\"\n" + + " },\n" + + " \"owner\":\"18600806164\"\n" + + "}"; + + return new Result().ok(new JSONObject(json)); + } + + /** + * 将Agent对象列表转换为AgentVO对象列表 + * 此方法遍历Agent对象列表,将每个Agent对象转换为AgentVO对象,并添加到AgentVO列表中 + * + * @param agentVOList 转换后的AgentVO对象列表 + * @param agentList 原始的Agent对象列表 + */ + private void convertAgetVOList(List agentVOList, List agentList){ + // 遍历Agent对象列表 + for(Agent agent : agentList) { + // 创建一个新的AgentVO对象 + AgentVO agentVO = new AgentVO(); + // 将当前Agent对象的属性值转换并设置到AgentVO对象中 + this.convertAgentVO(agentVO, agent); + // 将转换后的AgentVO对象添加到AgentVO列表中 + agentVOList.add(agentVO); + } + } + private void convertAgentVO(AgentVO agentVO, Agent agent){ + try { + BeanUtils.copyProperties(agentVO, agent); + agentVO.setTtsModelName("未知"); + TtsVoice ttsVoice = ttsVoiceService.getOne(new QueryWrapper().eq("id", agent.getTtsVoiceId())); + if(ObjectUtils.isNotNull(ttsVoice)){ + agentVO.setTtsModelName(ttsVoice.getName()); + } + agentVO.setLlmModelName("未知"); + ModelConfig modelConfig = modelConfigService.getOne(new QueryWrapper().eq("id", agent.getLlmModelId())); + if(ObjectUtils.isNotNull(modelConfig)){ + agentVO.setLlmModelName(modelConfig.getModelName()); + } + agentVO.setLastConnectedAt("今天"); + agentVO.setDeviceCount(0L); + long deviceCount = deviceService.count(new QueryWrapper().eq("agent_id", agent.getId())); + agentVO.setDeviceCount(deviceCount); + } catch (Exception e) { + log.error("[convertAgentVO]对象转换报错",e); + } + } +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/domain/Agent.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/domain/Agent.java new file mode 100644 index 00000000..74f75491 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/domain/Agent.java @@ -0,0 +1,116 @@ +package xiaozhi.modules.agent.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import java.io.Serializable; +import java.util.Date; +import lombok.Data; + +/** + * 智能体配置表 + * @TableName ai_agent + */ +@TableName(value ="ai_agent") +@Data +public class Agent implements Serializable { + /** + * 智能体唯一标识 + */ + @TableId + private String id; + + /** + * 所属用户 ID + */ + private Long userId; + + /** + * 智能体编码 + */ + private String agentCode; + + /** + * 智能体名称 + */ + private String agentName; + + /** + * 语音识别模型标识 + */ + private String asrModelId; + + /** + * 语音活动检测标识 + */ + private String vadModelId; + + /** + * 大语言模型标识 + */ + private String llmModelId; + + /** + * 语音合成模型标识 + */ + private String ttsModelId; + + /** + * 音色标识 + */ + private String ttsVoiceId; + + /** + * 记忆模型标识 + */ + private String memModelId; + + /** + * 意图模型标识 + */ + private String intentModelId; + + /** + * 角色设定参数 + */ + private String systemPrompt; + + /** + * 语言编码 + */ + private String langCode; + + /** + * 交互语种 + */ + private String language; + + /** + * 排序权重 + */ + private Integer sort; + + /** + * 创建者 ID + */ + private Long creator; + + /** + * 创建时间 + */ + private Date createdAt; + + /** + * 更新者 ID + */ + private Long updater; + + /** + * 更新时间 + */ + private Date updatedAt; + + @TableField(exist = false) + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/domain/AgentTemplate.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/domain/AgentTemplate.java new file mode 100644 index 00000000..b25dc407 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/domain/AgentTemplate.java @@ -0,0 +1,116 @@ +package xiaozhi.modules.agent.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import java.io.Serializable; +import java.util.Date; +import lombok.Data; + +/** + * 智能体配置模板表 + * @TableName ai_agent_template + */ +@TableName(value ="ai_agent_template") +@Data +public class AgentTemplate implements Serializable { + /** + * 智能体唯一标识 + */ + @TableId + private String id; + + /** + * 智能体编码 + */ + private String agentCode; + + /** + * 智能体名称 + */ + private String agentName; + + /** + * 语音识别模型标识 + */ + private String asrModelId; + + /** + * 语音活动检测标识 + */ + private String vadModelId; + + /** + * 大语言模型标识 + */ + private String llmModelId; + + /** + * 语音合成模型标识 + */ + private String ttsModelId; + + /** + * 音色标识 + */ + private String ttsVoiceId; + + /** + * 记忆模型标识 + */ + private String memModelId; + + /** + * 意图模型标识 + */ + private String intentModelId; + + /** + * 角色设定参数 + */ + private String systemPrompt; + + /** + * 语言编码 + */ + private String langCode; + + /** + * 交互语种 + */ + private String language; + + /** + * 排序权重 + */ + private Integer sort; + + /** + * 是否默认模板:1:是,0:不是 + */ + private Integer isDefault; + + /** + * 创建者 ID + */ + private Long creator; + + /** + * 创建时间 + */ + private Date createdAt; + + /** + * 更新者 ID + */ + private Long updater; + + /** + * 更新时间 + */ + private Date updatedAt; + + @TableField(exist = false) + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/mapper/AgentMapper.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/mapper/AgentMapper.java new file mode 100644 index 00000000..7c849280 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/mapper/AgentMapper.java @@ -0,0 +1,20 @@ +package xiaozhi.modules.agent.mapper; + +import org.apache.ibatis.annotations.Mapper; +import xiaozhi.modules.agent.domain.Agent; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** +* @author chenerlei +* @description 针对表【ai_agent(智能体配置表)】的数据库操作Mapper +* @createDate 2025-03-22 11:48:18 +* @Entity xiaozhi.modules.agent.domain.Agent +*/ +@Mapper +public interface AgentMapper extends BaseMapper { + +} + + + + diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/mapper/AgentTemplateMapper.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/mapper/AgentTemplateMapper.java new file mode 100644 index 00000000..c3222cc4 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/mapper/AgentTemplateMapper.java @@ -0,0 +1,20 @@ +package xiaozhi.modules.agent.mapper; + +import org.apache.ibatis.annotations.Mapper; +import xiaozhi.modules.agent.domain.AgentTemplate; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** +* @author chenerlei +* @description 针对表【ai_agent_template(智能体配置模板表)】的数据库操作Mapper +* @createDate 2025-03-22 11:48:18 +* @Entity xiaozhi.modules.agent.domain.AgentTemplate +*/ +@Mapper +public interface AgentTemplateMapper extends BaseMapper { + +} + + + + diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentService.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentService.java new file mode 100644 index 00000000..b7a7705b --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentService.java @@ -0,0 +1,13 @@ +package xiaozhi.modules.agent.service; + +import xiaozhi.modules.agent.domain.Agent; +import com.baomidou.mybatisplus.extension.service.IService; + +/** +* @author chenerlei +* @description 针对表【ai_agent(智能体配置表)】的数据库操作Service +* @createDate 2025-03-22 11:48:18 +*/ +public interface AgentService extends IService { + +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentTemplateService.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentTemplateService.java new file mode 100644 index 00000000..18849526 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentTemplateService.java @@ -0,0 +1,13 @@ +package xiaozhi.modules.agent.service; + +import xiaozhi.modules.agent.domain.AgentTemplate; +import com.baomidou.mybatisplus.extension.service.IService; + +/** +* @author chenerlei +* @description 针对表【ai_agent_template(智能体配置模板表)】的数据库操作Service +* @createDate 2025-03-22 11:48:18 +*/ +public interface AgentTemplateService extends IService { + +} 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 new file mode 100644 index 00000000..dfa3400d --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentServiceImpl.java @@ -0,0 +1,22 @@ +package xiaozhi.modules.agent.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import xiaozhi.modules.agent.domain.Agent; +import xiaozhi.modules.agent.service.AgentService; +import xiaozhi.modules.agent.mapper.AgentMapper; +import org.springframework.stereotype.Service; + +/** +* @author chenerlei +* @description 针对表【ai_agent(智能体配置表)】的数据库操作Service实现 +* @createDate 2025-03-22 11:48:18 +*/ +@Service +public class AgentServiceImpl extends ServiceImpl + implements AgentService{ + +} + + + + diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentTemplateServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentTemplateServiceImpl.java new file mode 100644 index 00000000..112b65c0 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentTemplateServiceImpl.java @@ -0,0 +1,22 @@ +package xiaozhi.modules.agent.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import xiaozhi.modules.agent.domain.AgentTemplate; +import xiaozhi.modules.agent.service.AgentTemplateService; +import xiaozhi.modules.agent.mapper.AgentTemplateMapper; +import org.springframework.stereotype.Service; + +/** +* @author chenerlei +* @description 针对表【ai_agent_template(智能体配置模板表)】的数据库操作Service实现 +* @createDate 2025-03-22 11:48:18 +*/ +@Service +public class AgentTemplateServiceImpl extends ServiceImpl + implements AgentTemplateService{ + +} + + + + diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/vo/AgentConfigVO.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/vo/AgentConfigVO.java new file mode 100644 index 00000000..ba1ececc --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/vo/AgentConfigVO.java @@ -0,0 +1,17 @@ +package xiaozhi.modules.agent.vo; + +import cn.hutool.json.JSONObject; +import lombok.Data; + +@Data +public class AgentConfigVO { + private JSONObject selected_module; + private String prompt; + private JSONObject LLM; + private JSONObject TTS; + private JSONObject ASR; + private JSONObject VAD; + private JSONObject Memory; + private JSONObject Intent; + private String owner; +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/vo/AgentVO.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/vo/AgentVO.java new file mode 100644 index 00000000..3ffddbd7 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/vo/AgentVO.java @@ -0,0 +1,19 @@ +package xiaozhi.modules.agent.vo; + +import lombok.Data; +import xiaozhi.modules.agent.domain.Agent; + +@Data +public class AgentVO extends Agent { + //角色音色 + private String ttsModelName; + + //角色明星 + private String llmModelName; + + //最近对话 + private String lastConnectedAt; + + //设备数量 + private Long deviceCount; +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/constant/DeviceConstant.java b/main/manager-api/src/main/java/xiaozhi/modules/device/constant/DeviceConstant.java new file mode 100644 index 00000000..5d6fb39e --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/device/constant/DeviceConstant.java @@ -0,0 +1,7 @@ +package xiaozhi.modules.device.constant; + +public class DeviceConstant { + + public static final String REDIS_KEY_PREFIX_DEVICE_ACTIVATION_CODE = "device:activation:code"; + public static final String REDIS_KEY_PREFIX_DEVICE_ACTIVATION_MAC = "device:activation:mac"; +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/controller/DeviceController.java b/main/manager-api/src/main/java/xiaozhi/modules/device/controller/DeviceController.java deleted file mode 100644 index d458e9f9..00000000 --- a/main/manager-api/src/main/java/xiaozhi/modules/device/controller/DeviceController.java +++ /dev/null @@ -1,82 +0,0 @@ -package xiaozhi.modules.device.controller; - -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.Parameters; -import io.swagger.v3.oas.annotations.tags.Tag; -import lombok.AllArgsConstructor; -import org.apache.commons.lang3.StringUtils; -import org.apache.shiro.authz.annotation.RequiresPermissions; -import org.springframework.web.bind.annotation.*; -import xiaozhi.common.constant.Constant; -import xiaozhi.common.exception.ErrorCode; -import xiaozhi.common.page.PageData; -import xiaozhi.common.redis.RedisKeys; -import xiaozhi.common.redis.RedisUtils; -import xiaozhi.common.user.UserDetail; -import xiaozhi.common.utils.JsonUtils; -import xiaozhi.common.utils.Result; -import xiaozhi.modules.device.dto.DeviceHeaderDTO; -import xiaozhi.modules.device.dto.DeviceUnBindDTO; -import xiaozhi.modules.device.entity.DeviceEntity; -import xiaozhi.modules.device.service.DeviceService; -import xiaozhi.modules.security.user.SecurityUser; - -import java.util.List; -import java.util.Map; - -@Tag(name = "设备管理") -@AllArgsConstructor -@RestController -@RequestMapping("/device") -public class DeviceController { - private final DeviceService deviceService; - private final RedisUtils redisUtils; - - - @PostMapping("/bind/{deviceCode}") - @Operation(summary = "绑定设备") - @RequiresPermissions("sys:role:normal") - public Result bindDevice(@PathVariable String deviceCode) { - UserDetail user = SecurityUser.getUser(); - - String deviceHeaders = (String) redisUtils.get(RedisKeys.getDeviceCaptchaKey(deviceCode)); - if (StringUtils.isBlank(deviceHeaders)) { - return new Result().error(ErrorCode.DEVICE_CAPTCHA_ERROR); - } - DeviceHeaderDTO deviceHeader = JsonUtils.parseObject(deviceHeaders.getBytes(), DeviceHeaderDTO.class); - DeviceEntity device = deviceService.bindDevice(user.getId(), deviceHeader); - return new Result().ok(device); - } - - @GetMapping("/bind") - @Operation(summary = "获取已绑定设备") - @RequiresPermissions("sys:role:normal") - public Result> getUserDevices() { - UserDetail user = SecurityUser.getUser(); - List devices = deviceService.getUserDevices(user.getId()); - return new Result>().ok(devices); - } - - @PostMapping("/unbind") - @Operation(summary = "解绑设备") - @RequiresPermissions("sys:role:normal") - public Result unbindDevice(@RequestBody DeviceUnBindDTO unDeviveBind) { - UserDetail user = SecurityUser.getUser(); - deviceService.unbindDevice(user.getId(), unDeviveBind.getDeviceId()); - return new Result(); - } - - @GetMapping("/all") - @Operation(summary = "设备列表(管理员)") - @RequiresPermissions("sys:role:superAdmin") - @Parameters({ - @Parameter(name = Constant.PAGE, description = "当前页码,从1开始", required = true), - @Parameter(name = Constant.LIMIT, description = "每页显示记录数", required = true), - }) - public Result> adminDeviceList( - @Parameter(hidden = true) @RequestParam Map params) { - PageData page = deviceService.adminDeviceList(params); - return new Result>().ok(page); - } -} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/controller/OtaController.java b/main/manager-api/src/main/java/xiaozhi/modules/device/controller/OtaController.java new file mode 100644 index 00000000..c222a264 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/device/controller/OtaController.java @@ -0,0 +1,81 @@ +package xiaozhi.modules.device.controller; + +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; +import com.baomidou.mybatisplus.core.toolkit.ObjectUtils; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.http.HttpHeaders; +import org.springframework.web.bind.annotation.*; +import xiaozhi.common.controller.BaseController; +import xiaozhi.common.redis.RedisUtils; +import xiaozhi.modules.device.constant.DeviceConstant; +import xiaozhi.modules.device.domain.Device; +import xiaozhi.modules.device.service.DeviceService; +import xiaozhi.modules.device.utils.CodeGeneratorUtil; +import xiaozhi.modules.device.vo.DeviceOtaVO; +import xiaozhi.modules.sys.service.SysParamsService; + +import java.util.Date; + +@Slf4j +@Tag(name = "设备OTA管理") +@AllArgsConstructor +@RestController +@RequestMapping("/ota") +public class OtaController extends BaseController { + private final DeviceService deviceService; + private final SysParamsService sysParamsService; + @Resource + private RedisUtils redisUtils; + private final String OTA_PARAM_CODE = "OTA"; + + @CrossOrigin(originPatterns = "*", methods = {RequestMethod.GET, RequestMethod.POST}) + @RequestMapping(path = "/", method = {RequestMethod.POST, RequestMethod.GET}, produces = "application/json") + @Operation(summary = "设备OTA升级") + public String ota(@RequestHeader(required = false) HttpHeaders headers, @RequestBody(required = false) JSONObject jsonObject) { + log.info("OTA升级请求:header:{},body:{}", headers, jsonObject); + if (ObjectUtils.isNull(headers) || StringUtils.isBlank(headers.getFirst("device-id"))) { + log.error("设备ID不能为空"); + return "{\"error\": \"Device ID is required\"}"; + } + + DeviceOtaVO otaVO = new DeviceOtaVO(); + Device device = deviceService.getOne(new UpdateWrapper().eq("mac_address", headers.getFirst("device-id").toUpperCase()).eq("id", headers.getFirst("client-Id").replace("-", ""))); + if (ObjectUtils.isNull(device)) { + // 从 Redis 中获取设备信息 + Object redisValue = redisUtils.hGet(DeviceConstant.REDIS_KEY_PREFIX_DEVICE_ACTIVATION_MAC, headers.getFirst("device-id").toUpperCase()); + if (ObjectUtils.isNull(redisValue)) { + String code = CodeGeneratorUtil.generateCode(6); + log.info("[gen]授权码已广播:{}", code); + otaVO.setActivation(new DeviceOtaVO.Activation(code, "youxlife.com\n" + code)); + redisUtils.hSet(DeviceConstant.REDIS_KEY_PREFIX_DEVICE_ACTIVATION_MAC, headers.getFirst("device-id").toUpperCase(), code, RedisUtils.HOUR_ONE_EXPIRE); + redisUtils.hSet(DeviceConstant.REDIS_KEY_PREFIX_DEVICE_ACTIVATION_CODE, code, jsonObject, RedisUtils.HOUR_ONE_EXPIRE); + } else { + log.warn("[get]授权码已广播:{}", redisValue); + otaVO.setActivation(new DeviceOtaVO.Activation(redisValue.toString(), "youxlife.com\n" + redisValue)); + } + } + + + if (ObjectUtils.isNull(device) || device.getAutoUpdate() == 1) { + //{"version":"1.0.0","url":"http://https://youxlife.oss-cn-zhangjiakou.aliyuncs.com/v1.4.5.bin"} + String value = sysParamsService.getValue(OTA_PARAM_CODE); + if (StringUtils.isBlank(value)) { + log.warn("OTA升级信息未配置"); + } else { + JSONObject object = new JSONObject(value); + otaVO.setFirmware(new DeviceOtaVO.Firmware(object.getStr("version"), object.getStr("url"))); + } + } + + otaVO.setServer_time(new DeviceOtaVO.ServerTime(new Date().getTime(), 8 * 60)); + return JSONUtil.toJsonStr(otaVO); + } +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/controller/UserDeviceController.java b/main/manager-api/src/main/java/xiaozhi/modules/device/controller/UserDeviceController.java new file mode 100644 index 00000000..b5900faa --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/device/controller/UserDeviceController.java @@ -0,0 +1,127 @@ +package xiaozhi.modules.device.controller; + +import cn.hutool.json.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.ObjectUtils; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.web.bind.annotation.*; +import xiaozhi.common.controller.BaseController; +import xiaozhi.common.redis.RedisUtils; +import xiaozhi.common.user.UserDetail; +import xiaozhi.common.utils.Result; +import xiaozhi.modules.agent.domain.Agent; +import xiaozhi.modules.agent.service.AgentService; +import xiaozhi.modules.device.constant.DeviceConstant; +import xiaozhi.modules.device.domain.Device; +import xiaozhi.modules.device.service.DeviceService; +import xiaozhi.modules.device.vo.DeviceCodeVO; +import xiaozhi.modules.security.user.SecurityUser; + +import java.util.Date; + +@Slf4j +@Tag(name = "设备管理") +@AllArgsConstructor +@RestController +@RequestMapping("/user/agent/device") +public class UserDeviceController extends BaseController { + private final AgentService agentService; + private final DeviceService deviceService; + private final RedisUtils redisUtils; + + @GetMapping("/bind/{agentId}") + @Operation(summary = "已绑定设备列表") + public Result> bindDeviceList(@PathVariable String agentId, @RequestParam Integer pageNo, @RequestParam Integer pageSize) { + if (StringUtils.isBlank(agentId)) { + log.error("智能体ID不能为空"); + return new Result().error("智能体ID不能为空"); + } + + UserDetail user = SecurityUser.getUser(); + Agent agent = agentService.getById(agentId); + if (ObjectUtils.isNull(agent)) { + log.error("智能体不存在"); + return new Result().error("智能体不存在"); + } + Page page = new Page(pageNo, pageSize); + page = deviceService.page(page, new QueryWrapper().eq("user_id", user.getId()).eq("agent_id", agentId)); + return new Result>().ok(page); + } + + + @PostMapping("/bind/{agentId}") + @Operation(summary = "绑定设备") + public Result bindDevice(@PathVariable String agentId, @RequestBody DeviceCodeVO deviceCodeVO) { + if (ObjectUtils.isNull(deviceCodeVO) || StringUtils.isBlank(deviceCodeVO.getDeviceCode())) { + log.error("授权码不能为空"); + return new Result().error("授权码不能为空"); + } + if (StringUtils.isBlank(agentId)) { + log.error("智能体ID不能为空"); + return new Result().error("智能体ID不能为空"); + } + + UserDetail user = SecurityUser.getUser(); + + // 从 Redis 中获取设备信息 + Object redisValue = redisUtils.hGet(DeviceConstant.REDIS_KEY_PREFIX_DEVICE_ACTIVATION_CODE, deviceCodeVO.getDeviceCode()); + if (ObjectUtils.isNull(redisValue)) { + log.error("授权码不存在:{}", deviceCodeVO.getDeviceCode()); + return new Result().error("授权码不存在"); + } + JSONObject jsonObject = (JSONObject) redisValue; + try { + redisUtils.hDel(DeviceConstant.REDIS_KEY_PREFIX_DEVICE_ACTIVATION_CODE, deviceCodeVO.getDeviceCode()); + redisUtils.hDel(DeviceConstant.REDIS_KEY_PREFIX_DEVICE_ACTIVATION_MAC, jsonObject.getStr("mac_address").toUpperCase()); + } catch (Exception e) { + log.warn("授权缓存删除失败", e); + } + JSONObject board = jsonObject.getJSONObject("board"); + JSONObject application = jsonObject.getJSONObject("application"); + if (ObjectUtils.isNull(board) || ObjectUtils.isNull(application)) { + log.error("设备码绑定信息无效,{}:{}", jsonObject.getStr("mac_address").toUpperCase(), deviceCodeVO.getDeviceCode()); + return new Result().error("设备码绑定信息无效"); + } + + Agent agent = agentService.getById(agentId); + if (ObjectUtils.isNull(agent)) { + log.error("智能体不存在"); + return new Result().error("智能体不存在"); + } + Device device = new Device(); + device.setId(jsonObject.getStr("uuid").replace("-", "")); + device.setUserId(user.getId()); + device.setMacAddress(jsonObject.getStr("mac_address").toUpperCase()); + device.setBoard(board.getStr("type")); + device.setAppVersion(application.getStr("version")); + device.setAgentId(agentId); + device.setCreator(user.getId()); + device.setCreateDate(new Date()); + device.setAutoUpdate(1); + boolean bool = deviceService.save(device); + if (!bool) { + return new Result().error("绑定失败"); + } + return new Result().ok(device); + } + + @PutMapping("/unbind/{deviceId}") + @Operation(summary = "解绑设备") + public Result bindDevice(@PathVariable String deviceId) { + if (StringUtils.isBlank(deviceId)) { + log.error("设备ID不能为空"); + return new Result().error("设备ID不能为空"); + } + UserDetail user = SecurityUser.getUser(); + boolean bool = deviceService.removeById(deviceId); + if (!bool) { + return new Result().error("解绑失败"); + } + return new Result().ok(null); + } +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/dao/DeviceDao.java b/main/manager-api/src/main/java/xiaozhi/modules/device/dao/DeviceDao.java deleted file mode 100644 index 9e4a8cd4..00000000 --- a/main/manager-api/src/main/java/xiaozhi/modules/device/dao/DeviceDao.java +++ /dev/null @@ -1,9 +0,0 @@ -package xiaozhi.modules.device.dao; - -import com.baomidou.mybatisplus.core.mapper.BaseMapper; -import org.apache.ibatis.annotations.Mapper; -import xiaozhi.modules.device.entity.DeviceEntity; - -@Mapper -public interface DeviceDao extends BaseMapper { -} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/domain/Device.java b/main/manager-api/src/main/java/xiaozhi/modules/device/domain/Device.java new file mode 100644 index 00000000..ae662dde --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/device/domain/Device.java @@ -0,0 +1,91 @@ +package xiaozhi.modules.device.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import java.io.Serializable; +import java.util.Date; +import lombok.Data; + +/** + * 设备信息表 + * @TableName ai_device + */ +@TableName(value ="ai_device") +@Data +public class Device implements Serializable { + /** + * 设备唯一标识 + */ + @TableId + private String id; + + /** + * 关联用户 ID + */ + private Long userId; + + /** + * MAC 地址 + */ + private String macAddress; + + /** + * 最后连接时间 + */ + private Date lastConnectedAt; + + /** + * 自动更新开关(0 关闭/1 开启) + */ + private Integer autoUpdate; + + /** + * 设备硬件型号 + */ + private String board; + + /** + * 设备别名 + */ + private String alias; + + /** + * 智能体 ID + */ + private String agentId; + + /** + * 固件版本号 + */ + private String appVersion; + + /** + * 排序 + */ + private Integer sort; + + /** + * 创建者 + */ + private Long creator; + + /** + * 创建时间 + */ + private Date createDate; + + /** + * 更新者 + */ + private Long updater; + + /** + * 更新时间 + */ + private Date updateDate; + + @TableField(exist = false) + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/dto/DeviceHeaderDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/device/dto/DeviceHeaderDTO.java deleted file mode 100644 index 83327fef..00000000 --- a/main/manager-api/src/main/java/xiaozhi/modules/device/dto/DeviceHeaderDTO.java +++ /dev/null @@ -1,19 +0,0 @@ -package xiaozhi.modules.device.dto; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -@Data -@Schema(description = "设备连接头信息") -public class DeviceHeaderDTO { - - @Schema(description = "设备ID") - private String deviceId; - - @Schema(description = "协议版本号") - private Long protocolVersion; - - @Schema(description = "认证信息") - private String authorization; - -} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/dto/DeviceUnBindDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/device/dto/DeviceUnBindDTO.java deleted file mode 100644 index 830a51ae..00000000 --- a/main/manager-api/src/main/java/xiaozhi/modules/device/dto/DeviceUnBindDTO.java +++ /dev/null @@ -1,19 +0,0 @@ -package xiaozhi.modules.device.dto; -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; -import lombok.Data; - -import java.io.Serializable; - -/** - * 设备解绑表单 - */ -@Data -@Schema(description = "设备解绑表单") -public class DeviceUnBindDTO implements Serializable { - - @Schema(description = "设备ID") - @NotBlank(message = "设备ID不能为空") - private Long deviceId; - -} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/entity/DeviceEntity.java b/main/manager-api/src/main/java/xiaozhi/modules/device/entity/DeviceEntity.java deleted file mode 100644 index ccbe4683..00000000 --- a/main/manager-api/src/main/java/xiaozhi/modules/device/entity/DeviceEntity.java +++ /dev/null @@ -1,54 +0,0 @@ -package xiaozhi.modules.device.entity; - -import com.baomidou.mybatisplus.annotation.TableName; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import java.util.Date; - -@Data -@TableName("ai_device") -@Schema(description = "设备信息") -public class DeviceEntity { - @Schema(description = "设备ID") - private Long id; - - @Schema(description = "关联用户ID") - private Long userId; - - @Schema(description = "MAC地址") - private String macAddress; - - @Schema(description = "最后连接时间") - private Date lastConnectedAt; - - @Schema(description = "自动更新开关(0关闭/1开启)") - private Integer autoUpdate; - - @Schema(description = "设备硬件型号") - private String board; - - @Schema(description = "设备别名") - private String alias; - - @Schema(description = "智能体ID") - private String agentId; - - @Schema(description = "固件版本号") - private String appVersion; - - @Schema(description = "排序") - private Integer sort; - - @Schema(description = "创建者") - private Long creator; - - @Schema(description = "创建时间") - private Date createDate; - - @Schema(description = "更新者") - private Long updater; - - @Schema(description = "更新时间") - private Date updateDate; -} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/mapper/DeviceMapper.java b/main/manager-api/src/main/java/xiaozhi/modules/device/mapper/DeviceMapper.java new file mode 100644 index 00000000..b43847a5 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/device/mapper/DeviceMapper.java @@ -0,0 +1,20 @@ +package xiaozhi.modules.device.mapper; + +import org.apache.ibatis.annotations.Mapper; +import xiaozhi.modules.device.domain.Device; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** +* @author chenerlei +* @description 针对表【ai_device(设备信息表)】的数据库操作Mapper +* @createDate 2025-03-22 13:26:35 +* @Entity xiaozhi.modules.device.domain.Device +*/ +@Mapper +public interface DeviceMapper extends BaseMapper { + +} + + + + diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/service/DeviceService.java b/main/manager-api/src/main/java/xiaozhi/modules/device/service/DeviceService.java index 16ad44fe..ec3efe56 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/device/service/DeviceService.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/device/service/DeviceService.java @@ -1,18 +1,13 @@ -package xiaozhi.modules.device.service; - -import xiaozhi.common.page.PageData; -import xiaozhi.modules.device.dto.DeviceHeaderDTO; -import xiaozhi.modules.device.entity.DeviceEntity; - -import java.util.List; -import java.util.Map; - -public interface DeviceService { - DeviceEntity bindDevice(Long userId, DeviceHeaderDTO deviceHeader); - - List getUserDevices(Long userId); - - void unbindDevice(Long userId, Long deviceId); - - PageData adminDeviceList(Map params); -} \ No newline at end of file +package xiaozhi.modules.device.service; + +import xiaozhi.modules.device.domain.Device; +import com.baomidou.mybatisplus.extension.service.IService; + +/** +* @author chenerlei +* @description 针对表【ai_device(设备信息表)】的数据库操作Service +* @createDate 2025-03-22 13:26:35 +*/ +public interface DeviceService extends IService { + +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/service/impl/DeviceServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/device/service/impl/DeviceServiceImpl.java index 279a9f2e..4c317a64 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/device/service/impl/DeviceServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/device/service/impl/DeviceServiceImpl.java @@ -1,57 +1,22 @@ -package xiaozhi.modules.device.service.impl; - -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; -import com.baomidou.mybatisplus.core.metadata.IPage; -import org.springframework.stereotype.Service; -import xiaozhi.common.page.PageData; -import xiaozhi.common.service.impl.BaseServiceImpl; -import xiaozhi.modules.device.dao.DeviceDao; -import xiaozhi.modules.device.dto.DeviceHeaderDTO; -import xiaozhi.modules.device.entity.DeviceEntity; -import xiaozhi.modules.device.service.DeviceService; - -import java.util.Date; -import java.util.List; -import java.util.Map; - -@Service -public class DeviceServiceImpl extends BaseServiceImpl implements DeviceService { - private final DeviceDao deviceDao; - - // 添加构造函数来初始化 deviceMapper - public DeviceServiceImpl(DeviceDao deviceDao) { - this.deviceDao = deviceDao; - } - - @Override - public DeviceEntity bindDevice(Long userId, DeviceHeaderDTO deviceHeader) { - DeviceEntity device = new DeviceEntity(); - device.setUserId(userId); - device.setMacAddress(deviceHeader.getDeviceId()); - device.setCreateDate(new Date()); - deviceDao.insert(device); - return device; - } - - @Override - public List getUserDevices(Long userId) { - QueryWrapper wrapper = new QueryWrapper<>(); - wrapper.eq("user_id", userId); - return deviceDao.selectList(wrapper); - } - - @Override - public void unbindDevice(Long userId, Long deviceId) { - deviceDao.deleteById(deviceId); - } - - @Override - public PageData adminDeviceList(Map params) { - IPage page = deviceDao.selectPage( - getPage(params, "sort", true), - new QueryWrapper<>() - ); - return new PageData<>(page.getRecords(), page.getTotal()); - } - -} \ No newline at end of file +package xiaozhi.modules.device.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import xiaozhi.modules.device.domain.Device; +import xiaozhi.modules.device.service.DeviceService; +import xiaozhi.modules.device.mapper.DeviceMapper; +import org.springframework.stereotype.Service; + +/** +* @author chenerlei +* @description 针对表【ai_device(设备信息表)】的数据库操作Service实现 +* @createDate 2025-03-22 13:26:35 +*/ +@Service +public class DeviceServiceImpl extends ServiceImpl + implements DeviceService{ + +} + + + + diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/utils/CodeGeneratorUtil.java b/main/manager-api/src/main/java/xiaozhi/modules/device/utils/CodeGeneratorUtil.java new file mode 100644 index 00000000..3a9ca2e6 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/device/utils/CodeGeneratorUtil.java @@ -0,0 +1,68 @@ +package xiaozhi.modules.device.utils; + +import java.util.Random; + +public class CodeGeneratorUtil { + + private static long lastTime = 0; + private static int counter = 0; + + /** + * 生成6位数字码,首位不为0,短期内不会重复。 + * @return 6位数字码 + */ + public static synchronized String generateCode() { + long currentTime = System.currentTimeMillis(); + + // 如果当前时间与上次相同,增加计数器以避免重复 + if (currentTime == lastTime) { + counter++; + } else { + counter = 0; // 时间变化时重置计数器 + } + lastTime = currentTime; + + // 使用当前时间和计数器生成随机种子 + long seed = currentTime + counter; + Random random = new Random(seed); + + // 确保首位不为0 + int firstDigit = random.nextInt(9) + 1; + int remainingDigits = random.nextInt(100000); // 剩余5位 + + // 拼接生成的6位数字码 + return String.format("%d%05d", firstDigit, remainingDigits); + } + + /** + * 生成指定位数的数字码,首位不为0,短期内不会重复。 + * @param digits 生成的数字码位数 + * @return 指定位数的数字码 + */ + public static synchronized String generateCode(int digits) { + if (digits < 2) { + throw new IllegalArgumentException("数字码位数必须大于等于2"); + } + + long currentTime = System.currentTimeMillis(); + + // 如果当前时间与上次相同,增加计数器以避免重复 + if (currentTime == lastTime) { + counter++; + } else { + counter = 0; // 时间变化时重置计数器 + } + lastTime = currentTime; + + // 使用当前时间和计数器生成随机种子 + long seed = currentTime + counter; + Random random = new Random(seed); + + // 确保首位不为0 + int firstDigit = random.nextInt(9) + 1; + int remainingDigits = random.nextInt((int) Math.pow(10, digits - 1)); // 剩余位数 + + // 拼接生成的动态位数数字码 + return String.format("%d%0" + (digits - 1) + "d", firstDigit, remainingDigits); + } +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/vo/DeviceCodeVO.java b/main/manager-api/src/main/java/xiaozhi/modules/device/vo/DeviceCodeVO.java new file mode 100644 index 00000000..f5817dc1 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/device/vo/DeviceCodeVO.java @@ -0,0 +1,10 @@ +package xiaozhi.modules.device.vo; + +import lombok.Data; + +@Data +public class DeviceCodeVO { + private String deviceId; + private String deviceCode; + private String token; +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/vo/DeviceOtaVO.java b/main/manager-api/src/main/java/xiaozhi/modules/device/vo/DeviceOtaVO.java new file mode 100644 index 00000000..c5af75af --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/device/vo/DeviceOtaVO.java @@ -0,0 +1,55 @@ +package xiaozhi.modules.device.vo; + +import lombok.Data; + +@Data +public class DeviceOtaVO { + private Activation activation; + private Mqtt mqtt; + private ServerTime server_time; + private Firmware firmware; + private String error; + + @Data + public static class Activation { + private String code; + private String message; + + public Activation() { + } + public Activation(String code, String message) { + this.code = code; + this.message = message; + } + } + + @Data + public class Mqtt { + + } + + @Data + public static class ServerTime { + private Long timestamp; + private Integer timezone_offset; + + public ServerTime() { + } + public ServerTime(Long timestamp, Integer timezone_offset) { + this.timestamp = timestamp; + this.timezone_offset = timezone_offset; + } + } + + @Data + public static class Firmware { + private String version; + private String url; + public Firmware() { + } + public Firmware(String version, String url) { + this.version = version; + this.url = url; + } + } +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/model/domain/ModelConfig.java b/main/manager-api/src/main/java/xiaozhi/modules/model/domain/ModelConfig.java new file mode 100644 index 00000000..b9967900 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/model/domain/ModelConfig.java @@ -0,0 +1,91 @@ +package xiaozhi.modules.model.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import java.io.Serializable; +import java.util.Date; +import lombok.Data; + +/** + * 模型配置表 + * @TableName ai_model_config + */ +@TableName(value ="ai_model_config") +@Data +public class ModelConfig implements Serializable { + /** + * 主键 + */ + @TableId + private String id; + + /** + * 模型类型(Memory/ASR/VAD/LLM/TTS) + */ + private String modelType; + + /** + * 模型编码(如AliLLM、DoubaoTTS) + */ + private String modelCode; + + /** + * 模型名称 + */ + private String modelName; + + /** + * 是否默认配置(0否 1是) + */ + private Integer isDefault; + + /** + * 是否启用 + */ + private Integer isEnabled; + + /** + * 模型配置(JSON格式) + */ + private Object configJson; + + /** + * 官方文档链接 + */ + private String docLink; + + /** + * 备注 + */ + private String remark; + + /** + * 排序 + */ + private Integer sort; + + /** + * 创建者 + */ + private Long creator; + + /** + * 创建时间 + */ + private Date createDate; + + /** + * 更新者 + */ + private Long updater; + + /** + * 更新时间 + */ + private Date updateDate; + + @TableField(exist = false) + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/model/domain/TtsVoice.java b/main/manager-api/src/main/java/xiaozhi/modules/model/domain/TtsVoice.java new file mode 100644 index 00000000..744bd6e8 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/model/domain/TtsVoice.java @@ -0,0 +1,81 @@ +package xiaozhi.modules.model.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import java.io.Serializable; +import java.util.Date; +import lombok.Data; + +/** + * TTS 音色表 + * @TableName ai_tts_voice + */ +@TableName(value ="ai_tts_voice") +@Data +public class TtsVoice implements Serializable { + /** + * 主键 + */ + @TableId + private String id; + + /** + * 对应 TTS 模型主键 + */ + private String ttsModelId; + + /** + * 音色名称 + */ + private String name; + + /** + * 音色编码 + */ + private String ttsVoice; + + /** + * 语言 + */ + private String languages; + + /** + * 音色 Demo + */ + private String voiceDemo; + + /** + * 备注 + */ + private String remark; + + /** + * 排序 + */ + private Integer sort; + + /** + * 创建者 + */ + private Long creator; + + /** + * 创建时间 + */ + private Date createDate; + + /** + * 更新者 + */ + private Long updater; + + /** + * 更新时间 + */ + private Date updateDate; + + @TableField(exist = false) + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/model/mapper/ModelConfigMapper.java b/main/manager-api/src/main/java/xiaozhi/modules/model/mapper/ModelConfigMapper.java new file mode 100644 index 00000000..0bbf4ca8 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/model/mapper/ModelConfigMapper.java @@ -0,0 +1,20 @@ +package xiaozhi.modules.model.mapper; + +import org.apache.ibatis.annotations.Mapper; +import xiaozhi.modules.model.domain.ModelConfig; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** +* @author chenerlei +* @description 针对表【ai_model_config(模型配置表)】的数据库操作Mapper +* @createDate 2025-03-22 15:31:57 +* @Entity xiaozhi.modules.model.domain.ModelConfig +*/ +@Mapper +public interface ModelConfigMapper extends BaseMapper { + +} + + + + diff --git a/main/manager-api/src/main/java/xiaozhi/modules/model/mapper/TtsVoiceMapper.java b/main/manager-api/src/main/java/xiaozhi/modules/model/mapper/TtsVoiceMapper.java new file mode 100644 index 00000000..c4ec5089 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/model/mapper/TtsVoiceMapper.java @@ -0,0 +1,20 @@ +package xiaozhi.modules.model.mapper; + +import org.apache.ibatis.annotations.Mapper; +import xiaozhi.modules.model.domain.TtsVoice; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** +* @author chenerlei +* @description 针对表【ai_tts_voice(TTS 音色表)】的数据库操作Mapper +* @createDate 2025-03-22 15:31:57 +* @Entity xiaozhi.modules.model.domain.TtsVoice +*/ +@Mapper +public interface TtsVoiceMapper extends BaseMapper { + +} + + + + diff --git a/main/manager-api/src/main/java/xiaozhi/modules/model/service/ModelConfigService.java b/main/manager-api/src/main/java/xiaozhi/modules/model/service/ModelConfigService.java new file mode 100644 index 00000000..a82905eb --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/model/service/ModelConfigService.java @@ -0,0 +1,13 @@ +package xiaozhi.modules.model.service; + +import xiaozhi.modules.model.domain.ModelConfig; +import com.baomidou.mybatisplus.extension.service.IService; + +/** +* @author chenerlei +* @description 针对表【ai_model_config(模型配置表)】的数据库操作Service +* @createDate 2025-03-22 15:31:57 +*/ +public interface ModelConfigService extends IService { + +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/model/service/TtsVoiceService.java b/main/manager-api/src/main/java/xiaozhi/modules/model/service/TtsVoiceService.java new file mode 100644 index 00000000..5432d715 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/model/service/TtsVoiceService.java @@ -0,0 +1,13 @@ +package xiaozhi.modules.model.service; + +import xiaozhi.modules.model.domain.TtsVoice; +import com.baomidou.mybatisplus.extension.service.IService; + +/** +* @author chenerlei +* @description 针对表【ai_tts_voice(TTS 音色表)】的数据库操作Service +* @createDate 2025-03-22 15:31:57 +*/ +public interface TtsVoiceService extends IService { + +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/model/service/impl/ModelConfigServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/model/service/impl/ModelConfigServiceImpl.java new file mode 100644 index 00000000..f057e270 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/model/service/impl/ModelConfigServiceImpl.java @@ -0,0 +1,22 @@ +package xiaozhi.modules.model.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import xiaozhi.modules.model.domain.ModelConfig; +import xiaozhi.modules.model.service.ModelConfigService; +import xiaozhi.modules.model.mapper.ModelConfigMapper; +import org.springframework.stereotype.Service; + +/** +* @author chenerlei +* @description 针对表【ai_model_config(模型配置表)】的数据库操作Service实现 +* @createDate 2025-03-22 15:31:57 +*/ +@Service +public class ModelConfigServiceImpl extends ServiceImpl + implements ModelConfigService{ + +} + + + + diff --git a/main/manager-api/src/main/java/xiaozhi/modules/model/service/impl/TtsVoiceServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/model/service/impl/TtsVoiceServiceImpl.java new file mode 100644 index 00000000..cb27eaa4 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/model/service/impl/TtsVoiceServiceImpl.java @@ -0,0 +1,22 @@ +package xiaozhi.modules.model.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import xiaozhi.modules.model.domain.TtsVoice; +import xiaozhi.modules.model.service.TtsVoiceService; +import xiaozhi.modules.model.mapper.TtsVoiceMapper; +import org.springframework.stereotype.Service; + +/** +* @author chenerlei +* @description 针对表【ai_tts_voice(TTS 音色表)】的数据库操作Service实现 +* @createDate 2025-03-22 15:31:57 +*/ +@Service +public class TtsVoiceServiceImpl extends ServiceImpl + implements TtsVoiceService{ + +} + + + + diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/config/ShiroConfig.java b/main/manager-api/src/main/java/xiaozhi/modules/security/config/ShiroConfig.java index de25d80e..74b85933 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/security/config/ShiroConfig.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/security/config/ShiroConfig.java @@ -73,6 +73,8 @@ public class ShiroConfig { filterMap.put("/user/captcha", "anon"); filterMap.put("/user/login", "anon"); filterMap.put("/user/register", "anon"); + filterMap.put("/ota/**", "anon"); + filterMap.put("/user/agent/loadAgentConfig/**", "anon"); filterMap.put("/**", "oauth2"); shiroFilter.setFilterChainDefinitionMap(filterMap); diff --git a/main/manager-api/src/main/resources/application-dev.yml b/main/manager-api/src/main/resources/application-dev.yml index a8fdf21f..90a62b6a 100644 --- a/main/manager-api/src/main/resources/application-dev.yml +++ b/main/manager-api/src/main/resources/application-dev.yml @@ -41,4 +41,22 @@ spring: merge-sql: false wall: config: - multi-statement-allow: true \ No newline at end of file + multi-statement-allow: true + redis: + host: localhost # Redis服务器地址 + port: 6379 # Redis服务器连接端口 + password: # Redis服务器连接密码(默认为空) + database: 0 # Redis数据库索引(默认为0) +# jedis: +# pool: +# max-active: 8 # 连接池最大连接数(使用负值表示没有限制) +# max-wait: -1ms # 连接池最大阻塞等待时间(使用负值表示没有限制) +# max-idle: 8 # 连接池中的最大空闲连接 +# min-idle: 0 # 连接池中的最小空闲连接 + timeout: 10000ms # 连接超时时间(毫秒) + lettuce: + pool: + max-active: 8 # 连接池最大连接数(使用负值表示没有限制) + max-idle: 8 # 连接池中的最大空闲连接 + min-idle: 0 # 连接池中的最小空闲连接 + shutdown-timeout: 100ms # 客户端优雅关闭的等待时间 \ No newline at end of file diff --git a/main/manager-api/src/main/resources/application.yml b/main/manager-api/src/main/resources/application.yml index 967f610e..74dfb0e6 100644 --- a/main/manager-api/src/main/resources/application.yml +++ b/main/manager-api/src/main/resources/application.yml @@ -24,19 +24,6 @@ spring: max-file-size: 100MB max-request-size: 100MB enabled: true - data: - redis: - database: 0 - host: 127.0.0.1 - port: 6379 - password: # 密码(默认为空) - timeout: 6000ms # 连接超时时长(毫秒) - lettuce: - pool: - max-active: 1000 # 连接池最大连接数(使用负值表示没有限制) - max-wait: -1ms # 连接池最大阻塞等待时间(使用负值表示没有限制) - max-idle: 10 # 连接池中的最大空闲连接 - min-idle: 5 # 连接池中的最小空闲连接 main: allow-bean-definition-overriding: true @@ -51,7 +38,7 @@ knife4j: renren: redis: - open: false + open: true xss: enabled: true exclude-urls: @@ -60,7 +47,7 @@ renren: mybatis-plus: mapper-locations: classpath*:/mapper/**/*.xml #实体扫描,多个package用逗号或者分号分隔 - typeAliasesPackage: xiaozhi.modules.*.entity + typeAliasesPackage: xiaozhi.modules.*.entity;xiaozhi.modules.*.domain global-config: #数据库相关配置 db-config: diff --git a/main/manager-api/src/main/resources/mapper/agent/AgentMapper.xml b/main/manager-api/src/main/resources/mapper/agent/AgentMapper.xml new file mode 100644 index 00000000..7d8ddc74 --- /dev/null +++ b/main/manager-api/src/main/resources/mapper/agent/AgentMapper.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + id,user_id,agent_code,agent_name,asr_model_id,vad_model_id, + llm_model_id,tts_model_id,tts_voice_id,mem_model_id,intent_model_id, + system_prompt,lang_code,language,sort,creator, + created_at,updater,updated_at + + diff --git a/main/manager-api/src/main/resources/mapper/agent/AgentTemplateMapper.xml b/main/manager-api/src/main/resources/mapper/agent/AgentTemplateMapper.xml new file mode 100644 index 00000000..833bcd0e --- /dev/null +++ b/main/manager-api/src/main/resources/mapper/agent/AgentTemplateMapper.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + id,agent_code,agent_name,asr_model_id,vad_model_id,llm_model_id, + tts_model_id,tts_voice_id,mem_model_id,intent_model_id,system_prompt, + lang_code,language,sort,is_default,creator, + created_at,updater,updated_at + + diff --git a/main/manager-api/src/main/resources/mapper/device/DeviceMapper.xml b/main/manager-api/src/main/resources/mapper/device/DeviceMapper.xml new file mode 100644 index 00000000..743003f7 --- /dev/null +++ b/main/manager-api/src/main/resources/mapper/device/DeviceMapper.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + id,user_id,mac_address,last_connected_at,auto_update,board, + alias,agent_id,app_version,sort,creator, + create_date,updater,update_date + + diff --git a/main/manager-api/src/main/resources/mapper/model/ModelConfigMapper.xml b/main/manager-api/src/main/resources/mapper/model/ModelConfigMapper.xml new file mode 100644 index 00000000..90cf097b --- /dev/null +++ b/main/manager-api/src/main/resources/mapper/model/ModelConfigMapper.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + id,model_type,model_code,model_name,is_default,is_enabled, + config_json,doc_link,remark,sort,creator, + create_date,updater,update_date + + diff --git a/main/manager-api/src/main/resources/mapper/model/TtsVoiceMapper.xml b/main/manager-api/src/main/resources/mapper/model/TtsVoiceMapper.xml new file mode 100644 index 00000000..008cc4ed --- /dev/null +++ b/main/manager-api/src/main/resources/mapper/model/TtsVoiceMapper.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + id,tts_model_id,name,tts_voice,languages,voice_demo, + remark,sort,creator,create_date,updater, + update_date + + diff --git a/main/manager-web/src/apis/api.js b/main/manager-web/src/apis/api.js index 38c2f86b..03a5b5a2 100755 --- a/main/manager-web/src/apis/api.js +++ b/main/manager-web/src/apis/api.js @@ -9,9 +9,9 @@ import user from './module/user.js' * 如果你想调用8002端口,就用'/xiaozhi-esp32-api/api/v1',请与vue.config.js的devServer配置相结合,方便跨域请求 * */ -const DEV_API_SERVICE = 'https://m1.apifoxmock.com/m1/5931378-5618560-default' +// const DEV_API_SERVICE = 'https://m1.apifoxmock.com/m1/5931378-5618560-default' // 8002开发完成完成后使用这个 -// const DEV_API_SERVICE = '/xiaozhi-esp32-api' +const DEV_API_SERVICE = '/xiaozhi-esp32-api' /** * 根据开发环境返回接口url diff --git a/main/manager-web/src/apis/module/agent.js b/main/manager-web/src/apis/module/agent.js index 721f075c..e3c2c538 100755 --- a/main/manager-web/src/apis/module/agent.js +++ b/main/manager-web/src/apis/module/agent.js @@ -16,20 +16,17 @@ export default { }).send() }, //添加智能体 - addAgent(name, callback) { + addAgent(agentName, callback) { RequestService.sendRequest() .url(`${getServiceUrl()}/api/v1/user/agent`) .method('POST') - .data({name}) + .data({agentName}) .success((res) => { RequestService.clearRequestTime(); callback(res); }) .fail((err) => { console.error('添加智能体失败:', err); - RequestService.reAjaxFun(() => { - this.addAgent(name, callback); - }); }).send(); }, //获取智能体配置 diff --git a/main/manager-web/src/apis/module/device.js b/main/manager-web/src/apis/module/device.js index cd568745..e16bec25 100755 --- a/main/manager-web/src/apis/module/device.js +++ b/main/manager-web/src/apis/module/device.js @@ -4,9 +4,10 @@ import {getServiceUrl} from '../api' export default { //设备列表 - getDeviceList(agent_id, callback) { + getDeviceList(agent_id, page, callback) { RequestService.sendRequest().url(`${getServiceUrl()}/api/v1/user/agent/device/bind/${agent_id}`) .method('GET') + .data(page) .success((res) => { RequestService.clearRequestTime() callback(res) @@ -16,11 +17,11 @@ export default { }).send() }, //绑定设备 - bindDevice(agent_id, code, callback) { + bindDevice(agentId, deviceCode, callback) { RequestService.sendRequest() - .url(`${getServiceUrl()}/api/v1/user/agent/device/bind/${agent_id}`) + .url(`${getServiceUrl()}/api/v1/user/agent/device/bind/${agentId}`) .method('POST') - .data({code}) + .data({deviceCode}) .success((res) => { RequestService.clearRequestTime(); callback(res); @@ -28,14 +29,14 @@ export default { .fail((err) => { console.error('绑定设备失败:', err); RequestService.reAjaxFun(() => { - this.addDevice(name, callback); + this.addDevice(agentId, deviceCode, callback); }); }).send(); }, // 解绑设备 - unbindDevice(device_id, callback) { + unbindDevice(deviceId, callback) { RequestService.sendRequest() - .url(`${getServiceUrl()}/api/v1/user/device/unbind/${device_id}`) + .url(`${getServiceUrl()}/api/v1/user/agent/device/unbind/${deviceId}`) .method('PUT') .success((res) => { RequestService.clearRequestTime(); @@ -43,7 +44,7 @@ export default { }) .fail(() => { RequestService.reAjaxFun(() => { - this.unbindDevice(device_id, callback); + this.unbindDevice(deviceId, callback); }); }).send() }, diff --git a/main/manager-web/src/components/AddAgentDialog.vue b/main/manager-web/src/components/AddAgentDialog.vue index 026b646e..e805897f 100644 --- a/main/manager-web/src/components/AddAgentDialog.vue +++ b/main/manager-web/src/components/AddAgentDialog.vue @@ -46,12 +46,13 @@ export default { this.$message.error('请输入智能体名称'); return; } - userApi.addAgent(this.agentName, (res) => { - this.$message.success('添加成功'); - this.$emit('confirm', res); + this.$emit('confirm',this.agentName) + // userApi.addAgent(this.agentName, (res) => { + // this.$message.success('添加成功'); + // this.$emit('confirm', res); this.$emit('update:visible', false); this.agentName = ""; - }); + // }); }, cancel() { this.$emit('update:visible', false) diff --git a/main/manager-web/src/components/AddDeviceDialog.vue b/main/manager-web/src/components/AddDeviceDialog.vue index f98ff7d9..5b94090e 100644 --- a/main/manager-web/src/components/AddDeviceDialog.vue +++ b/main/manager-web/src/components/AddDeviceDialog.vue @@ -40,8 +40,12 @@ export default { }, methods: { confirm() { + if (!this.deviceCode.trim()) { + this.$message.error('请输入设备验证码'); + return; + } + this.$emit('confirm',this.deviceCode) this.$emit('update:visible', false) - this.$emit('added', this.deviceCode) this.deviceCode = "" }, cancel() { diff --git a/main/manager-web/src/utils/date.js b/main/manager-web/src/utils/date.js index c6210bc0..afc8a339 100755 --- a/main/manager-web/src/utils/date.js +++ b/main/manager-web/src/utils/date.js @@ -39,10 +39,13 @@ function formatDateTool(date, fmt) { const o = { 'M+': date.getMonth() + 1, 'd+': date.getDate(), - 'h+': date.getHours(), + 'H+': date.getHours(), + 'h+': date.getHours() > 12 ? date.getHours() - 12 : date.getHours(), 'm+': date.getMinutes(), 's+': date.getSeconds() } + // 12小时制 + const is_12Hours = fmt.indexOf('hh') > -1; for (const k in o) { if (new RegExp(`(${k})`).test(fmt)) { const str = o[k] + '' @@ -52,6 +55,8 @@ function formatDateTool(date, fmt) { ) } } + // 12小时制 + fmt = is_12Hours ? date.getHours() > 12 ? fmt + " PM" : fmt + " AM" : fmt return fmt } diff --git a/main/manager-web/src/views/device.vue b/main/manager-web/src/views/device.vue index bbfe41fc..4a60bdde 100644 --- a/main/manager-web/src/views/device.vue +++ b/main/manager-web/src/views/device.vue @@ -18,50 +18,53 @@ + 添加设备 - - - - + + + + + - +
- +
diff --git a/main/manager-web/src/views/home.vue b/main/manager-web/src/views/home.vue index 0c55e9f6..ee561668 100644 --- a/main/manager-web/src/views/home.vue +++ b/main/manager-web/src/views/home.vue @@ -1,7 +1,7 @@