diff --git a/docs/firmware-setting.md b/docs/firmware-setting.md index ae66b6e9..0ed350a0 100644 --- a/docs/firmware-setting.md +++ b/docs/firmware-setting.md @@ -1,7 +1,7 @@ # 基于虾哥编译好的固件配置自定义服务器 ## 第1步 确认版本 -烧录虾哥已经编译好的1.6.1版本固件 +烧录虾哥已经编译好的[1.6.1版本以上固件](https://github.com/78/xiaozhi-esp32/releases) ## 第2步 准备你的ota地址 如果你按照教程使用的是全模块部署,就应该会有ota地址。 @@ -29,6 +29,7 @@ wss://2662r3426b.vicp.fun/xiaozhi/v1/ ## 第3步 进入配网模式 进入机器的配网模式,在页面顶部,点击“高级选项”,在里面输入你服务器的`ota`地址,点击保存。重启设备 +![请参考-OTA地址设置](../docs/images/firmware-setting-ota.png) ## 第4步 唤醒小智,查看日志输出 diff --git a/docs/images/firmware-setting-ota.png b/docs/images/firmware-setting-ota.png new file mode 100644 index 00000000..c151fd93 Binary files /dev/null and b/docs/images/firmware-setting-ota.png differ diff --git a/main/manager-api/src/main/java/xiaozhi/common/constant/Constant.java b/main/manager-api/src/main/java/xiaozhi/common/constant/Constant.java index 52e840f4..b4b361ad 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/constant/Constant.java +++ b/main/manager-api/src/main/java/xiaozhi/common/constant/Constant.java @@ -200,4 +200,9 @@ public interface Constant { * 版本号 */ public static final String VERSION = "0.4.3"; + + /** + * 无效固件URL + */ + String INVALID_FIRMWARE_URL = "http://xiaozhi.server.com:8002/xiaozhi/otaMag/download/NOT_ACTIVATED_FIRMWARE_THIS_IS_A_INVALID_URL"; } \ No newline at end of file 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 14ac3d61..ee31c41f 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 @@ -142,7 +142,7 @@ public class DeviceServiceImpl extends BaseServiceImpl if (deviceById == null) { DeviceReportRespDTO.Firmware firmware = new DeviceReportRespDTO.Firmware(); firmware.setVersion(deviceReport.getApplication().getVersion()); - firmware.setUrl("http://xiaozhi.server.com:8002/xiaozhi/otaMag/download/NOT_ACTIVATED_FIRMWARE_THIS_IS_A_INVALID_URL"); + firmware.setUrl(Constant.INVALID_FIRMWARE_URL); response.setFirmware(firmware); } else { // 只有在设备已绑定且autoUpdate不为0的情况下才返回固件升级信息 @@ -378,7 +378,7 @@ public class DeviceServiceImpl extends BaseServiceImpl } firmware.setVersion(ota == null ? currentVersion : ota.getVersion()); - firmware.setUrl(downloadUrl == null ? "" : downloadUrl); + firmware.setUrl(downloadUrl == null ? Constant.INVALID_FIRMWARE_URL : downloadUrl); return firmware; } diff --git a/main/manager-web/src/components/HeaderBar.vue b/main/manager-web/src/components/HeaderBar.vue index 39075462..842441ce 100644 --- a/main/manager-web/src/components/HeaderBar.vue +++ b/main/manager-web/src/components/HeaderBar.vue @@ -58,7 +58,7 @@
-
+
@@ -103,7 +103,8 @@ export default { }, isChangePasswordDialogVisible: false, // 控制修改密码弹窗的显示 userDropdownVisible: false, - paramDropdownVisible: false + paramDropdownVisible: false, + isSmallScreen: false } }, computed: { @@ -113,7 +114,13 @@ export default { } }, mounted() { - this.fetchUserInfo() + this.fetchUserInfo(); + this.checkScreenSize(); + window.addEventListener('resize', this.checkScreenSize); + }, + //移除事件监听器 + beforeDestroy() { + window.removeEventListener('resize', this.checkScreenSize); }, methods: { goHome() { @@ -147,7 +154,9 @@ export default { } }) }, - + checkScreenSize() { + this.isSmallScreen = window.innerWidth <= 1386; + }, // 处理搜索 handleSearch() { const searchValue = this.search.trim(); @@ -354,45 +363,6 @@ export default { } } -@media (max-width: 1024px) { - .search-container { - margin-right: 10px; - max-width: 150px; - } - - .header-right { - gap: 5px; - } -} - -@media (max-width: 900px) { - .header-left { - margin-right: auto; - } - - .search-container { - max-width: 150px; - } -} - -@media (max-width: 768px) { - .search-container { - max-width: 145px; - } - - .custom-search-input>>>.el-input__inner { - padding-left: 10px; - font-size: 11px; - } -} - -@media (max-width: 600px) { - .search-container { - max-width: 120px; - min-width: 100px; - } -} - .equipment-management.more-dropdown { position: relative; } @@ -411,13 +381,4 @@ export default { color: #606266; white-space: nowrap; } - -@media (max-width: 768px) { - .equipment-management.more-dropdown .el-dropdown-menu { - position: fixed; - right: 10px; - top: 60px; - z-index: 2000; - } -} \ No newline at end of file diff --git a/main/xiaozhi-server/core/connection.py b/main/xiaozhi-server/core/connection.py index 7419dc68..0abb3cec 100644 --- a/main/xiaozhi-server/core/connection.py +++ b/main/xiaozhi-server/core/connection.py @@ -231,7 +231,9 @@ class ConnectionHandler: # 创建新事件循环(避免与主循环冲突) loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) - loop.run_until_complete(self.memory.save_memory(self.dialogue.dialogue)) + loop.run_until_complete( + self.memory.save_memory(self.dialogue.dialogue) + ) except Exception as e: self.logger.bind(tag=TAG).error(f"保存记忆失败: {e}") finally: @@ -441,10 +443,10 @@ class ConnectionHandler: def _initialize_memory(self): """初始化记忆模块""" self.memory.init_memory( - self.device_id, - self.llm, - self.config["summaryMemory"], - not self.read_config_from_api, + role_id=self.device_id, + llm=self.llm, + summary_memory=self.config.get("summaryMemory", None), + save_to_file=not self.read_config_from_api, ) def _initialize_intent(self): diff --git a/main/xiaozhi-server/core/providers/intent/intent_llm/intent_llm.py b/main/xiaozhi-server/core/providers/intent/intent_llm/intent_llm.py index b847ac73..195d058a 100644 --- a/main/xiaozhi-server/core/providers/intent/intent_llm/intent_llm.py +++ b/main/xiaozhi-server/core/providers/intent/intent_llm/intent_llm.py @@ -215,7 +215,7 @@ class IntentProvider(IntentProviderBase): # 记录识别到的function call logger.bind(tag=TAG).info( - f"识别到function call: {function_name}, 参数: {function_args}" + f"llm 识别到意图: {function_name}, 参数: {function_args}" ) # 添加到缓存 diff --git a/main/xiaozhi-server/core/providers/llm/ollama/ollama.py b/main/xiaozhi-server/core/providers/llm/ollama/ollama.py index fb34ced5..aad6354f 100644 --- a/main/xiaozhi-server/core/providers/llm/ollama/ollama.py +++ b/main/xiaozhi-server/core/providers/llm/ollama/ollama.py @@ -21,27 +21,67 @@ class LLMProvider(LLMProviderBase): api_key="ollama" # Ollama doesn't need an API key but OpenAI client requires one ) + # 检查是否是qwen3模型 + self.is_qwen3 = self.model_name and self.model_name.lower().startswith("qwen3") + def response(self, session_id, dialogue): try: + # 如果是qwen3模型,在用户最后一条消息中添加/no_think指令 + if self.is_qwen3: + # 复制对话列表,避免修改原始对话 + dialogue_copy = dialogue.copy() + + # 找到最后一条用户消息 + for i in range(len(dialogue_copy) - 1, -1, -1): + if dialogue_copy[i]["role"] == "user": + # 在用户消息前添加/no_think指令 + dialogue_copy[i]["content"] = "/no_think " + dialogue_copy[i]["content"] + logger.bind(tag=TAG).debug(f"为qwen3模型添加/no_think指令") + break + + # 使用修改后的对话 + dialogue = dialogue_copy + responses = self.client.chat.completions.create( model=self.model_name, messages=dialogue, stream=True ) - is_active=True + is_active = True + # 用于处理跨chunk的标签 + buffer = "" + for chunk in responses: try: delta = chunk.choices[0].delta if getattr(chunk, 'choices', None) else None content = delta.content if hasattr(delta, 'content') else '' + if content: - if '' in content: + # 将内容添加到缓冲区 + buffer += content + + # 处理缓冲区中的标签 + while '' in buffer and '' in buffer: + # 找到完整的标签并移除 + pre = buffer.split('', 1)[0] + post = buffer.split('', 1)[1] + buffer = pre + post + + # 处理只有开始标签的情况 + if '' in buffer: is_active = False - content = content.split('')[0] - if '' in content: + buffer = buffer.split('', 1)[0] + + # 处理只有结束标签的情况 + if '' in buffer: is_active = True - content = content.split('')[-1] - if is_active: - yield content + buffer = buffer.split('', 1)[1] + + # 如果当前处于活动状态且缓冲区有内容,则输出 + if is_active and buffer: + yield buffer + buffer = "" # 清空缓冲区 + except Exception as e: logger.bind(tag=TAG).error(f"Error processing chunk: {e}") @@ -51,6 +91,22 @@ class LLMProvider(LLMProviderBase): def response_with_functions(self, session_id, dialogue, functions=None): try: + # 如果是qwen3模型,在用户最后一条消息中添加/no_think指令 + if self.is_qwen3: + # 复制对话列表,避免修改原始对话 + dialogue_copy = dialogue.copy() + + # 找到最后一条用户消息 + for i in range(len(dialogue_copy) - 1, -1, -1): + if dialogue_copy[i]["role"] == "user": + # 在用户消息前添加/no_think指令 + dialogue_copy[i]["content"] = "/no_think " + dialogue_copy[i]["content"] + logger.bind(tag=TAG).debug(f"为qwen3模型添加/no_think指令") + break + + # 使用修改后的对话 + dialogue = dialogue_copy + stream = self.client.chat.completions.create( model=self.model_name, messages=dialogue, @@ -58,8 +114,49 @@ class LLMProvider(LLMProviderBase): tools=functions, ) + is_active = True + buffer = "" + for chunk in stream: - yield chunk.choices[0].delta.content, chunk.choices[0].delta.tool_calls + try: + delta = chunk.choices[0].delta if getattr(chunk, 'choices', None) else None + content = delta.content if hasattr(delta, 'content') else None + tool_calls = delta.tool_calls if hasattr(delta, 'tool_calls') else None + + # 如果是工具调用,直接传递 + if tool_calls: + yield None, tool_calls + continue + + # 处理文本内容 + if content: + # 将内容添加到缓冲区 + buffer += content + + # 处理缓冲区中的标签 + while '' in buffer and '' in buffer: + # 找到完整的标签并移除 + pre = buffer.split('', 1)[0] + post = buffer.split('', 1)[1] + buffer = pre + post + + # 处理只有开始标签的情况 + if '' in buffer: + is_active = False + buffer = buffer.split('', 1)[0] + + # 处理只有结束标签的情况 + if '' in buffer: + is_active = True + buffer = buffer.split('', 1)[1] + + # 如果当前处于活动状态且缓冲区有内容,则输出 + if is_active and buffer: + yield buffer, None + buffer = "" # 清空缓冲区 + except Exception as e: + logger.bind(tag=TAG).error(f"Error processing function chunk: {e}") + continue except Exception as e: logger.bind(tag=TAG).error(f"Error in Ollama function call: {e}") diff --git a/main/xiaozhi-server/core/providers/memory/base.py b/main/xiaozhi-server/core/providers/memory/base.py index be21692d..19dcabb8 100644 --- a/main/xiaozhi-server/core/providers/memory/base.py +++ b/main/xiaozhi-server/core/providers/memory/base.py @@ -21,6 +21,6 @@ class MemoryProviderBase(ABC): """Query memories for specific role based on similarity""" return "please implement query method" - def init_memory(self, role_id, llm, summary_memory=None): + def init_memory(self, role_id, llm, **kwargs): self.role_id = role_id self.llm = llm diff --git a/main/xiaozhi-server/core/providers/memory/mem_local_short/mem_local_short.py b/main/xiaozhi-server/core/providers/memory/mem_local_short/mem_local_short.py index 2e5c7db2..a916e6c9 100644 --- a/main/xiaozhi-server/core/providers/memory/mem_local_short/mem_local_short.py +++ b/main/xiaozhi-server/core/providers/memory/mem_local_short/mem_local_short.py @@ -112,8 +112,10 @@ class MemoryProvider(MemoryProviderBase): self.memory_path = get_project_dir() + "data/.memory.yaml" self.load_memory(summary_memory) - def init_memory(self, role_id, llm, summary_memory=None, save_to_file=True): - super().init_memory(role_id, llm) + def init_memory( + self, role_id, llm, summary_memory=None, save_to_file=True, **kwargs + ): + super().init_memory(role_id, llm, **kwargs) self.save_to_file = save_to_file self.load_memory(summary_memory) diff --git a/main/xiaozhi-server/core/utils/util.py b/main/xiaozhi-server/core/utils/util.py index b95f43d2..9ab77a5a 100644 --- a/main/xiaozhi-server/core/utils/util.py +++ b/main/xiaozhi-server/core/utils/util.py @@ -319,7 +319,7 @@ def initialize_modules( modules["memory"] = memory.create_instance( memory_type, config["Memory"][select_memory_module], - config['summaryMemory'], + config.get('summaryMemory', None), ) logger.bind(tag=TAG).info(f"初始化组件: memory成功 {select_memory_module}") diff --git a/main/xiaozhi-server/docker-compose_all.yml b/main/xiaozhi-server/docker-compose_all.yml index b62188d9..9339329e 100644 --- a/main/xiaozhi-server/docker-compose_all.yml +++ b/main/xiaozhi-server/docker-compose_all.yml @@ -76,6 +76,7 @@ services: expose: - 6379 container_name: xiaozhi-esp32-server-redis + restart: always healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 10s diff --git a/main/xiaozhi-server/requirements.txt b/main/xiaozhi-server/requirements.txt index 2b6f1dc9..9df7dde1 100755 --- a/main/xiaozhi-server/requirements.txt +++ b/main/xiaozhi-server/requirements.txt @@ -22,11 +22,12 @@ mem0ai==0.1.62 bs4==0.0.2 modelscope==1.23.2 sherpa_onnx==1.11.0 -mcp==1.7.1 +mcp==1.8.1 cnlunar==0.2.0 PySocks==1.7.1 dashscope==1.23.1 baidu-aip==4.16.13 chardet==5.2.0 aioconsole==0.8.1 -markitdown==0.1.1 \ No newline at end of file +markitdown==0.1.1 +mcp-proxy==0.6.0