From 109754fac990cdafc9708765eb6ee28879de5e67 Mon Sep 17 00:00:00 2001
From: hrz <1710360675@qq.com>
Date: Wed, 23 Apr 2025 18:17:56 +0800
Subject: [PATCH 1/6] =?UTF-8?q?update:=E5=A2=9E=E5=8A=A0HomeAssistant?=
=?UTF-8?q?=E6=95=99=E7=A8=8B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/FAQ.md | 36 +-----
docs/homeassistant-integration.md | 111 ++++++++++++++++++
.../xiaozhi/common/constant/Constant.java | 2 +-
main/xiaozhi-server/config/logger.py | 2 +-
.../plugins_func/functions/hass_init.py | 2 +-
5 files changed, 115 insertions(+), 38 deletions(-)
create mode 100644 docs/homeassistant-integration.md
diff --git a/docs/FAQ.md b/docs/FAQ.md
index 9be27089..c859498a 100644
--- a/docs/FAQ.md
+++ b/docs/FAQ.md
@@ -102,41 +102,7 @@ VAD:
### 7、我想通过小智控制电灯、空调、远程开关机等操作 💡
-本项目,支持以工具调用的方式控制HomeAssistant设备
-
-1、首先选择一款支持function call支持的LLM,例如`ChatGLMLLM`。
-
-2、在配置文件中,将 `selected_module.Intent` 设置为 `function_call`。
-
-3、登录`HomeAssistant`,点击`左下角个人`,切换`安全`导航栏,划到底部`长期访问令牌`生成api_key。
-
-在配置文件中,配置好你的home assistant的`devices`(被控制的设备)和`api_key`和`base_url`等信息。例如:
-
-``` yaml
-plugins
- home_assistant:
- devices:
- - 客厅,玩具灯,switch.cuco_cn_460494544_cp1_on_p_2_1
- - 卧室,台灯,switch.iot_cn_831898993_socn1_on_p_2_1
- base_url: http://你的homeassistant地址:8123
- api_key: 你的home assistant api访问令牌
-```
-
-最后,允许function_call 插件在配置文件中启用`hass_get_state`(必须)、`hass_set_state`(必须)、`hass_play_music`(不想用ha听音乐可以不启动),例如:
-
-``` yaml
-Intent:
- ...
- function_call:
- type: nointent
- functions:
- - change_role
- - get_weather
- - get_news
- - hass_get_state
- - hass_set_state
- - hass_play_music
-```
+参考教程[ESP32设备与HomeAssistant集成指南](./homeassistant-integration.md)
### 8、更多问题,可联系我们反馈 💬
diff --git a/docs/homeassistant-integration.md b/docs/homeassistant-integration.md
new file mode 100644
index 00000000..d2dc4f9d
--- /dev/null
+++ b/docs/homeassistant-integration.md
@@ -0,0 +1,111 @@
+# ESP32设备与HomeAssistant集成指南
+
+## 简介
+
+本文档将指导您如何将ESP32设备与HomeAssistant进行集成。
+
+## 前提条件
+
+- 已安装并配置好`HomeAssistant`
+- 已经在HomeAssistant中集成`Xiaomi Home`,并将米家的设备导入进`HomeAssistant`
+- `xiaozhi-esp32-server智控台`能正常使用
+- 本次我选择的模型是:免费的ChatGLM,它支持functioncall函数调用
+
+## 网络环境
+
+1、我的`HomeAssistant`部署在下面,版本是`2025.3.4`
+```
+http://192.168.4.7:8123
+```
+
+2、我的`xiaozhi-esp32-server智控台`和`HomeAssistant`部署在同一台机器的另一个端口,版本是`0.3.10`
+```
+http://192.168.4.7:8002
+```
+
+
+## 配置步骤
+
+### 1. 登录`HomeAssistant`整理需要控制的设备清单
+
+登录`HomeAssistant`,点击`左下角的设置`,然后进入`设备与服务`,再点击顶部的`实体`。
+
+然后在实体中搜索你相关控制的开关,结果出来后,在列表中,点击其中一个结果,这是会出现一个开关的界面。
+
+在开关的界面,我们尝试点击开关,看看是开发会随着我们的点击开/关。如果能操作,说明是正常联网的。
+
+接着在开关面板找到设置按钮,点击后,可以查看这个开关的`实体标识符`。
+
+我们打开一个记事本,按照这样格式整理一条数据:
+
+位置+英文逗号+设备名称+英文逗号+`实体标识符`+英文分号
+
+例如,我在公司,我有一个玩具灯,他的标识符是switch.cuco_cn_460494544_cp1_on_p_2_1,那么就这个写这一条数据
+
+```
+公司,玩具灯,switch.cuco_cn_460494544_cp1_on_p_2_1;
+```
+
+当然最后我可能要操作两个灯,我的最终的结果是:
+
+```
+公司,玩具灯,switch.cuco_cn_460494544_cp1_on_p_2_1;
+公司,台灯,switch.iot_cn_831898993_socn1_on_p_2_1;
+```
+
+这段字符,我们成为“设备清单字符”需要保存好,等一下有用。
+
+
+### 2. 登录`HomeAssistant`拿到开发密钥
+
+登录`HomeAssistant`,点击`左下角个人`,切换`安全`导航栏,划到底部`长期访问令牌`生成api_key。
+
+
+### 3. 登录`智控台`
+
+使用管理员账号,登录`智控台`。点击顶部菜单`参数管理`,搜索`plugins.home_assistant.`,会有三条结果出来
+
+编辑`plugins.home_assistant.devices`,把刚才整理的设备清单字符粘贴进去。
+
+
+编辑`plugins.home_assistant.base_url`,把你部署的`HomeAssistant`接口地址粘贴进去,我粘贴进去的地址是这样的
+
+```
+http://192.168.4.7:8123
+```
+
+编辑`plugins.home_assistant.api_key`,把你从`HomeAssistant`复制过来的密钥,粘贴进去
+
+
+### 4. 设置`意图识别`函数
+
+在智控台,点击顶部菜单“模型配置”,在左侧栏,找到“意图识别”,找到id为`Intent_function_call`的意图,点击编辑
+
+然后在弹框中,在原来的基础上追加两个函数:“hass_get_state”和“hass_set_state”
+
+修改前
+```
+change_role;get_weather;get_news;play_music
+```
+
+修改后
+
+```
+change_role;get_weather;get_news;play_music;hass_get_state;hass_set_state
+```
+
+### 6. 手动重启xiaozhi-server
+
+重启xiaozhi-server程序
+
+
+### 5. 确认角色配置是否设置了函数意图识别
+
+在智控台,点击顶部菜单“智能体管理”,找到设备所在的智能体,点击“配置角色”
+
+确认意图识别(Intent),是否选择“函数调用意图识别”
+
+
+### 6. 唤醒设别进行控制
+
+尝试和esp32说,“打开XXX灯”
\ No newline at end of file
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 e11d5874..47d45e7e 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
@@ -172,5 +172,5 @@ public interface Constant {
/**
* 版本号
*/
- public static final String VERSION = "0.3.10";
+ public static final String VERSION = "0.3.11";
}
\ No newline at end of file
diff --git a/main/xiaozhi-server/config/logger.py b/main/xiaozhi-server/config/logger.py
index b31fb061..705b244c 100644
--- a/main/xiaozhi-server/config/logger.py
+++ b/main/xiaozhi-server/config/logger.py
@@ -3,7 +3,7 @@ import sys
from loguru import logger
from config.config_loader import load_config
-SERVER_VERSION = "0.3.10"
+SERVER_VERSION = "0.3.11"
def get_module_abbreviation(module_name, module_dict):
diff --git a/main/xiaozhi-server/plugins_func/functions/hass_init.py b/main/xiaozhi-server/plugins_func/functions/hass_init.py
index 4bd2f2ec..06d7c8bc 100644
--- a/main/xiaozhi-server/plugins_func/functions/hass_init.py
+++ b/main/xiaozhi-server/plugins_func/functions/hass_init.py
@@ -13,7 +13,7 @@ def append_devices_to_prompt(conn):
"functions", []
)
if "hass_get_state" in funcs or "hass_set_state" in funcs:
- prompt = "下面是我家智能设备,可以通过homeassistant控制\n"
+ prompt = "\n下面是我家智能设备列表(位置,设备名,entity_id),可以通过homeassistant控制\n"
devices = conn.config["plugins"]["home_assistant"].get("devices", [])
if len(devices) == 0:
return
From e6a90c40fb4a8102d2439441ce80cc1913e9fb74 Mon Sep 17 00:00:00 2001
From: TinsFox
Date: Wed, 23 Apr 2025 20:25:04 +0800
Subject: [PATCH 2/6] =?UTF-8?q?update:=20=E4=BF=AE=E6=94=B9=E6=99=BA?=
=?UTF-8?q?=E6=8E=A7=E5=8F=B0=E7=99=BB=E5=BD=95=E5=9C=B0=E5=9D=80=E4=B8=BA?=
=?UTF-8?q?8002?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/Deployment_all.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/Deployment_all.md b/docs/Deployment_all.md
index 21c629f8..a0bb2f16 100644
--- a/docs/Deployment_all.md
+++ b/docs/Deployment_all.md
@@ -353,7 +353,7 @@ pip install -r requirements.txt
## 8.配置项目文件
-使用超级管理员账号,登录智控台 http://127.0.0.1:8001 ,在顶部菜单找到`参数管理`,找到列表中第三条数据,参数编码是`server.secret`,复制它到`参数值`。
+使用超级管理员账号,登录智控台 http://127.0.0.1:8002 ,在顶部菜单找到`参数管理`,找到列表中第三条数据,参数编码是`server.secret`,复制它到`参数值`。
`server.secret`需要说明一下,这个`参数值`很重要,作用是让我们的`Server`端连接`manager-api`。`server.secret`是每次从零部署manager模块时,会自动随机生成的密钥。
From 951370eee75b65aa1eedb1c0bf28f0fa7a929c67 Mon Sep 17 00:00:00 2001
From: hrz <1710360675@qq.com>
Date: Wed, 23 Apr 2025 21:29:42 +0800
Subject: [PATCH 3/6] =?UTF-8?q?update:=E8=AE=A9alibl=E3=80=81=E7=AD=89?=
=?UTF-8?q?=E9=9D=9E=E5=8E=9F=E7=94=9Fllm=E4=BD=BF=E7=94=A8function?=
=?UTF-8?q?=E6=97=B6=E4=B8=8D=E5=8D=A1=E9=A1=BF?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/firmware-build.md | 30 +++++++++--
.../core/providers/llm/AliBL/AliBL.py | 19 +++++--
.../core/providers/llm/fastgpt/fastgpt.py | 51 ++++++++++---------
.../core/providers/llm/gemini/gemini.py | 4 ++
4 files changed, 73 insertions(+), 31 deletions(-)
diff --git a/docs/firmware-build.md b/docs/firmware-build.md
index f821805a..ebb7413b 100644
--- a/docs/firmware-build.md
+++ b/docs/firmware-build.md
@@ -1,16 +1,40 @@
# esp32固件编译
-## 第1步 配置环境
+## 第1步 准备你的ota地址
+如果你按照教程使用的是全模块部署,就应该会有ota地址。
+
+此刻,请你用浏览器打开你的ota地址,例如我的ota地址
+```
+http://192.168.1.25:8002/xiaozhi/ota/
+```
+
+如果显示“OTA接口运行正常,websocket集群数量:X”。那就往下。
+
+如果显示“OTA接口运行不正常”,大概是你还没在`智控台`配置`Websocket`地址。那就:
+
+- 1、使用超级管理员登录智控台
+
+- 2、顶部菜单点击`参数管理`
+
+- 3、在列表中找到`server.websocket`项目,输入你的`Websocket`地址。例如我的就是
+
+```
+ws://192.168.1.25:8000/xiaozhi/v1/
+```
+
+配置完后,再使用浏览器刷新你的ota接口地址,看看是不是正常了。如果还不正常就,就再次确认一下Websocket是否正常启动,是否配置了Websocket地址。
+
+## 第2步 配置环境
先按照这个教程配置项目环境[《Windows搭建 ESP IDF 5.3.2开发环境以及编译小智》](https://icnynnzcwou8.feishu.cn/wiki/JEYDwTTALi5s2zkGlFGcDiRknXf)
-## 第2步 打开配置文件
+## 第3步 打开配置文件
配置好编译环境后,下载虾哥iaozhi-esp32项目源码,
从这里下载虾哥[xiaozhi-esp32项目源码](https://github.com/78/xiaozhi-esp32)。
下载后,打开`xiaozhi-esp32/main/Kconfig.projbuild`文件。
-## 第3步 修改OTA地址
+## 第4步 修改OTA地址
找到`OTA_VERSION_URL`的`default`的内容,把`https://api.tenclass.net/xiaozhi/ota/`
改成你自己的地址,例如,我的接口地址是`http://192.168.1.25:8002/xiaozhi/ota/`,就把内容改成这个。
diff --git a/main/xiaozhi-server/core/providers/llm/AliBL/AliBL.py b/main/xiaozhi-server/core/providers/llm/AliBL/AliBL.py
index 51efdc06..769f3117 100644
--- a/main/xiaozhi-server/core/providers/llm/AliBL/AliBL.py
+++ b/main/xiaozhi-server/core/providers/llm/AliBL/AliBL.py
@@ -6,6 +6,7 @@ from core.providers.llm.base import LLMProviderBase
TAG = __name__
logger = setup_logging()
+
class LLMProvider(LLMProviderBase):
def __init__(self, config):
self.api_key = config["api_key"]
@@ -19,21 +20,25 @@ class LLMProvider(LLMProviderBase):
# 处理dialogue
if self.is_No_prompt:
dialogue.pop(0)
- logger.bind(tag=TAG).debug(f"【阿里百练API服务】处理后的dialogue: {dialogue}")
+ logger.bind(tag=TAG).debug(
+ f"【阿里百练API服务】处理后的dialogue: {dialogue}"
+ )
# 构造调用参数
call_params = {
"api_key": self.api_key,
"app_id": self.app_id,
"session_id": session_id,
- "messages": dialogue
+ "messages": dialogue,
}
if self.memory_id != False:
# 百练memory需要prompt参数
prompt = dialogue[-1].get("content")
call_params["memory_id"] = self.memory_id
call_params["prompt"] = prompt
- logger.bind(tag=TAG).debug(f"【阿里百练API服务】处理后的prompt: {prompt}")
+ logger.bind(tag=TAG).debug(
+ f"【阿里百练API服务】处理后的prompt: {prompt}"
+ )
responses = Application.call(**call_params)
if responses.status_code != HTTPStatus.OK:
@@ -44,9 +49,15 @@ class LLMProvider(LLMProviderBase):
)
yield "【阿里百练API服务响应异常】"
else:
- logger.bind(tag=TAG).debug(f"【阿里百练API服务】构造参数: {call_params}")
+ logger.bind(tag=TAG).debug(
+ f"【阿里百练API服务】构造参数: {call_params}"
+ )
yield responses.output.text
except Exception as e:
logger.bind(tag=TAG).error(f"【阿里百练API服务】响应异常: {e}")
yield "【LLM服务响应异常】"
+
+ def response_with_functions(self, session_id, dialogue, functions=None):
+ logger.bind(tag=TAG).info(f"阿里百练暂未实现完整的工具调用(function call)")
+ return self.response(session_id, dialogue)
diff --git a/main/xiaozhi-server/core/providers/llm/fastgpt/fastgpt.py b/main/xiaozhi-server/core/providers/llm/fastgpt/fastgpt.py
index 38f9a2c9..e0e6ccc7 100644
--- a/main/xiaozhi-server/core/providers/llm/fastgpt/fastgpt.py
+++ b/main/xiaozhi-server/core/providers/llm/fastgpt/fastgpt.py
@@ -21,37 +21,36 @@ class LLMProvider(LLMProviderBase):
# 发起流式请求
with requests.post(
- f"{self.base_url}/chat/completions",
- headers={"Authorization": f"Bearer {self.api_key}"},
- json={
- "stream": True,
- "chatId": session_id,
- "detail": self.detail,
- "variables": self.variables,
- "messages": [
- {
- "role": "user",
- "content": last_msg["content"]
- }
- ]
- },
- stream=True
+ f"{self.base_url}/chat/completions",
+ headers={"Authorization": f"Bearer {self.api_key}"},
+ json={
+ "stream": True,
+ "chatId": session_id,
+ "detail": self.detail,
+ "variables": self.variables,
+ "messages": [{"role": "user", "content": last_msg["content"]}],
+ },
+ stream=True,
) as r:
for line in r.iter_lines():
if line:
try:
- if line.startswith(b'data: '):
- if line[6:].decode('utf-8') == '[DONE]':
+ if line.startswith(b"data: "):
+ if line[6:].decode("utf-8") == "[DONE]":
break
data = json.loads(line[6:])
- if 'choices' in data and len(data['choices']) > 0:
- delta = data['choices'][0].get('delta', {})
- if delta and 'content' in delta and delta['content'] is not None:
- content = delta['content']
- if '' in content:
+ if "choices" in data and len(data["choices"]) > 0:
+ delta = data["choices"][0].get("delta", {})
+ if (
+ delta
+ and "content" in delta
+ and delta["content"] is not None
+ ):
+ content = delta["content"]
+ if "" in content:
continue
- if '' in content:
+ if "" in content:
continue
yield content
@@ -62,4 +61,8 @@ class LLMProvider(LLMProviderBase):
except Exception as e:
logger.bind(tag=TAG).error(f"Error in response generation: {e}")
- yield "【服务响应异常】"
\ No newline at end of file
+ yield "【服务响应异常】"
+
+ def response_with_functions(self, session_id, dialogue, functions=None):
+ logger.bind(tag=TAG).info(f"fastgpt暂未实现完整的工具调用(function call)")
+ return self.response(session_id, dialogue)
diff --git a/main/xiaozhi-server/core/providers/llm/gemini/gemini.py b/main/xiaozhi-server/core/providers/llm/gemini/gemini.py
index ca78caa9..a91a6bf0 100644
--- a/main/xiaozhi-server/core/providers/llm/gemini/gemini.py
+++ b/main/xiaozhi-server/core/providers/llm/gemini/gemini.py
@@ -134,3 +134,7 @@ class LLMProvider(LLMProviderBase):
yield f"JSON解码错误:{e}"
except Exception as e:
yield f"发生错误:{e}"
+
+ def response_with_functions(self, session_id, dialogue, functions=None):
+ logger.bind(tag=TAG).info(f"gemini暂未实现完整的工具调用(function call)")
+ return self.response(session_id, dialogue)
From 3c62adab3c0c1547e07ba277a9a165256a8c3f48 Mon Sep 17 00:00:00 2001
From: hrz <1710360675@qq.com>
Date: Wed, 23 Apr 2025 23:23:25 +0800
Subject: [PATCH 4/6] =?UTF-8?q?fix:=E5=8F=96=E6=B6=88docker=E7=89=88?=
=?UTF-8?q?=E4=B8=8A=E4=BC=A0ota=E5=9B=BA=E4=BB=B6=E6=97=B6=E9=BB=98?=
=?UTF-8?q?=E8=AE=A4=E7=9A=84=E6=96=87=E4=BB=B6=E5=A4=A7=E5=B0=8F=E9=99=90?=
=?UTF-8?q?=E5=88=B6?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Dockerfile-web | 2 +-
README.md | 5 +-
docs/docker/nginx.conf | 67 ++++++++++++------
.../src/components/FirmwareDialog.vue | 68 ++++++++++++++++++-
4 files changed, 116 insertions(+), 26 deletions(-)
diff --git a/Dockerfile-web b/Dockerfile-web
index 0c4001f1..9e5da678 100644
--- a/Dockerfile-web
+++ b/Dockerfile-web
@@ -23,7 +23,7 @@ RUN apt-get update && \
rm -rf /var/lib/apt/lists/*
# 配置Nginx
-COPY docs/docker/nginx.conf /etc/nginx/conf.d/default.conf
+COPY docs/docker/nginx.conf /etc/nginx/nginx.conf
# 复制前端构建产物
COPY --from=web-builder /app/dist /usr/share/nginx/html
diff --git a/README.md b/README.md
index 2b6173c3..4f762d97 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@
本项目为开源智能硬件项目
xiaozhi-esp32提供后端服务
-根据小智通信协议使用Python实现
+根据小智通信协议使用Python、Java、Vue实现
帮助您快速搭建小智服务器
@@ -15,7 +15,8 @@
· 简体中文
· 更新日志
· 部署文档
-· 反馈问题
+· 反馈问题
+· 常见问题
diff --git a/docs/docker/nginx.conf b/docs/docker/nginx.conf
index 6a746260..9b9c3889 100644
--- a/docs/docker/nginx.conf
+++ b/docs/docker/nginx.conf
@@ -1,26 +1,53 @@
-server {
- listen 8002;
- server_name localhost;
+user root;
+worker_processes 4;
- # 静态资源服务(Vue项目)
- location / {
- root /usr/share/nginx/html;
- try_files $uri $uri/ /index.html;
- }
+events {
+ worker_connections 1024;
+}
- # API反向代理(Java项目)
- location /xiaozhi/ {
- proxy_pass http://127.0.0.1:8003;
- proxy_set_header Host $host;
- proxy_cookie_path /api/ /;
- proxy_set_header Referer $http_referer;
- proxy_set_header Cookie $http_cookie;
+http {
+ include mime.types;
+ default_type application/octet-stream;
+ sendfile on;
+ keepalive_timeout 300;
+ client_header_timeout 180s;
+ client_body_timeout 180s;
+ client_max_body_size 1024M;
- proxy_connect_timeout 10;
- proxy_send_timeout 10;
- proxy_read_timeout 10;
+ gzip on;
+ gzip_buffers 32 4K;
+ gzip_comp_level 6;
+ gzip_min_length 100;
+ gzip_types application/javascript text/css text/xml image/jpeg image/gif image/png;
+ gzip_disable "MSIE [1-6]\.";
+ gzip_vary on;
- proxy_set_header X-Real-IP $remote_addr;
- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ server {
+ # 无域名访问,就用localhost
+ server_name localhost;
+ # 80端口
+ listen 8002;
+
+ # 转发到编译后到web目录
+ location / {
+ root /usr/share/nginx/html;
+ try_files $uri $uri/ /index.html;
+ }
+
+ # 转发到manager-api
+ location /xiaozhi/ {
+ proxy_pass http://127.0.0.1:8003;
+ proxy_set_header Host $host;
+ proxy_cookie_path /manager/ /;
+ proxy_set_header Referer $http_referer;
+ proxy_set_header Cookie $http_cookie;
+
+ proxy_connect_timeout 10;
+ proxy_send_timeout 10;
+ proxy_read_timeout 10;
+
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ }
}
}
\ No newline at end of file
diff --git a/main/manager-web/src/components/FirmwareDialog.vue b/main/manager-web/src/components/FirmwareDialog.vue
index 9839f4a3..2cbf83a1 100644
--- a/main/manager-web/src/components/FirmwareDialog.vue
+++ b/main/manager-web/src/components/FirmwareDialog.vue
@@ -1,5 +1,5 @@
-
+
@@ -13,11 +13,14 @@
-
+
点击上传
只能上传固件文件(.bin/.apk),且不超过100MB
+
@@ -53,6 +56,9 @@ export default {
data() {
return {
firmwareTypes: FIRMWARE_TYPES,
+ uploadProgress: 0,
+ uploadStatus: '',
+ isUploading: false,
rules: {
firmwareName: [
{ required: true, message: '请输入固件名称(板子+版本号)', trigger: 'blur' }
@@ -108,16 +114,72 @@ export default {
},
handleUpload(options) {
const { file } = options
+ this.uploadProgress = 0
+ this.uploadStatus = ''
+ this.isUploading = true
+
+ // 使用setTimeout实现简单的0-50%过渡
+ const timer = setTimeout(() => {
+ if (this.uploadProgress < 50) { // 只有当进度小于50%时才设置
+ this.uploadProgress = 50
+ }
+ }, 1000)
+
Api.ota.uploadFirmware(file, (res) => {
+ clearTimeout(timer) // 清除定时器
res = res.data
if (res.code === 0) {
this.form.firmwarePath = res.data
this.form.size = file.size
+ this.uploadProgress = 100
+ this.uploadStatus = 'success'
this.$message.success('固件文件上传成功')
+ // 延迟2秒后隐藏进度条
+ setTimeout(() => {
+ this.isUploading = false
+ }, 2000)
} else {
+ this.uploadStatus = 'exception'
this.$message.error(res.msg || '文件上传失败')
+ this.isUploading = false
+ }
+ }, (progressEvent) => {
+ if (progressEvent.total) {
+ const progress = Math.round((progressEvent.loaded * 100) / progressEvent.total)
+ // 只有当进度大于50%时才更新
+ if (progress > 50) {
+ this.uploadProgress = progress
+ }
+ // 如果上传完成但还没收到成功响应,保持进度条显示
+ if (progress === 100) {
+ this.uploadStatus = ''
+ }
}
})
+ },
+ handleRemove() {
+ this.form.firmwarePath = ''
+ this.form.size = 0
+ this.uploadProgress = 0
+ this.uploadStatus = ''
+ this.isUploading = false
+ },
+ handleOpen() {
+ // 重置上传相关状态
+ this.uploadProgress = 0
+ this.uploadStatus = ''
+ this.isUploading = false
+ // 重置表单中的文件相关字段
+ if (!this.form.id) { // 只在新增时重置
+ this.form.firmwarePath = ''
+ this.form.size = 0
+ // 重置上传组件
+ this.$nextTick(() => {
+ if (this.$refs.upload) {
+ this.$refs.upload.clearFiles()
+ }
+ })
+ }
}
}
}
From 969b5fcfbb2877e0e9396cdca20e029325d35d4b Mon Sep 17 00:00:00 2001
From: CGD <3030332422@qq.com>
Date: Thu, 24 Apr 2025 09:11:58 +0800
Subject: [PATCH 5/6] =?UTF-8?q?update:"=E9=BB=98=E8=AE=A4=E6=8F=90?=
=?UTF-8?q?=E7=A4=BA=E8=AF=8D"=E5=8F=96=E6=B6=88=E7=94=A8=E6=88=B7?=
=?UTF-8?q?=E4=BD=8D=E7=BD=AE?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
main/xiaozhi-server/core/connection.py | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/main/xiaozhi-server/core/connection.py b/main/xiaozhi-server/core/connection.py
index 5bf7540b..8fa09318 100644
--- a/main/xiaozhi-server/core/connection.py
+++ b/main/xiaozhi-server/core/connection.py
@@ -226,12 +226,12 @@ class ConnectionHandler:
"""加载意图识别"""
self._initialize_intent()
"""加载位置信息"""
- self.client_ip_info = get_ip_info(self.client_ip, self.logger)
- if self.client_ip_info is not None and "city" in self.client_ip_info:
- self.logger.bind(tag=TAG).info(f"Client ip info: {self.client_ip_info}")
- self.prompt = self.prompt + f"\nuser location:{self.client_ip_info}"
-
- self.dialogue.update_system_message(self.prompt)
+ # self.client_ip_info = get_ip_info(self.client_ip, self.logger)
+ # if self.client_ip_info is not None and "city" in self.client_ip_info:
+ # self.logger.bind(tag=TAG).info(f"Client ip info: {self.client_ip_info}")
+ # self.prompt = self.prompt + f"\nuser location:{self.client_ip_info}"
+ #
+ # self.dialogue.update_system_message(self.prompt)
def _initialize_private_config(self):
read_config_from_api = self.config.get("read_config_from_api", False)
From c0c5630bfbb2eddf780a54207ccf77abd42fd2c1 Mon Sep 17 00:00:00 2001
From: CGD <3030332422@qq.com>
Date: Thu, 24 Apr 2025 09:12:56 +0800
Subject: [PATCH 6/6] =?UTF-8?q?update:=E4=BF=AE=E6=94=B9"=E8=8E=B7?=
=?UTF-8?q?=E5=8F=96=E5=A4=A9=E6=B0=94"=E7=9A=84=E9=80=BB=E8=BE=91?=
=?UTF-8?q?=E9=A1=BA=E5=BA=8F?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../plugins_func/functions/get_weather.py | 57 +++++++++++++------
1 file changed, 41 insertions(+), 16 deletions(-)
diff --git a/main/xiaozhi-server/plugins_func/functions/get_weather.py b/main/xiaozhi-server/plugins_func/functions/get_weather.py
index 060274d9..3764401e 100644
--- a/main/xiaozhi-server/plugins_func/functions/get_weather.py
+++ b/main/xiaozhi-server/plugins_func/functions/get_weather.py
@@ -2,6 +2,7 @@ import requests
from bs4 import BeautifulSoup
from config.logger import setup_logging
from plugins_func.register import register_function, ToolType, ActionResponse, Action
+from core.utils.util import get_ip_info
TAG = __name__
logger = setup_logging()
@@ -11,9 +12,12 @@ GET_WEATHER_FUNCTION_DESC = {
"function": {
"name": "get_weather",
"description": (
- "获取某个地点的天气,用户应提供一个位置,比如用户说杭州天气,参数为:杭州。"
+ "获取某个地点的天气信息。当用户询问与天气相关的问题(例如'今天的天气怎么样?'、'明天会下雨吗?'、'广州天气如何?'等),"
+ "或对话中包含'天气'字眼时,调用此功能。用户可以提供具体位置(如城市名),"
+ "如果未提供位置,则自动获取用户当前位置查询天气。"
"如果用户说的是省份,默认用省会城市。如果用户说的不是省份或城市而是一个地名,"
"默认用该地所在省份的省会城市。"
+ "当IP解析失败会使用默认地址"
),
"parameters": {
"type": "object",
@@ -57,6 +61,7 @@ WEATHER_CODE_MAP = {
"900": "热", "901": "冷", "999": "未知"
}
+
def fetch_city_info(location, api_key):
url = f"https://geoapi.qweather.com/v2/city/lookup?key={api_key}&location={location}&lang=zh"
response = requests.get(url, headers=HEADERS).json()
@@ -97,27 +102,47 @@ def parse_weather_info(soup):
def get_weather(conn, location: str = None, lang: str = "zh_CN"):
api_key = conn.config["plugins"]["get_weather"]["api_key"]
default_location = conn.config["plugins"]["get_weather"]["default_location"]
- location = location or conn.client_ip_info.get("city") or default_location
- logger.bind(tag=TAG).debug(f"获取天气: {location}")
+ print("用户设置的default_location为:", default_location)
+ client_ip = conn.client_ip
+ print("用户提供的location为:", location)
+ # 优先使用用户提供的location参数
+ if not location:
+ # 通过客户端IP解析城市
+ if client_ip:
+ # 动态解析IP对应的城市信息
+ ip_info = get_ip_info(client_ip, logger)
+ print("解析用户IP后的城市为:", ip_info)
+ location = ip_info.get("city") if ip_info and "city" in ip_info else None
+ else:
+ # 若IP解析失败或无IP,使用默认位置
+ location = default_location
+ print("解析失败,将使用默认地址", location)
city_info = fetch_city_info(location, api_key)
if not city_info:
return ActionResponse(Action.REQLLM, f"未找到相关的城市: {location},请确认地点是否正确", None)
-
soup = fetch_weather_page(city_info['fxLink'])
if not soup:
return ActionResponse(Action.REQLLM, None, "请求失败")
-
city_name, current_abstract, current_basic, temps_list = parse_weather_info(soup)
- weather_report = f"根据下列数据,用{lang}回应用户的查询天气请求:\n{city_name}未来7天天气:\n"
- for i, (date, weather, high, low) in enumerate(temps_list):
- if high and low:
- weather_report += f"{date}: {low}到{high}, {weather}\n"
- weather_report += (
- f"当前天气: {current_abstract}\n"
- f"当前天气参数: {current_basic}\n"
- f"(确保只报告指定单日的天气情况,除非未来会出现异常天气;或者用户明确要求想要了解多日天气,如果未指定,默认报告今天的天气。"
- "参数为0的值不需要报告给用户,每次都报告体感温度,根据语境选择合适的参数内容告知用户,并对参数给出相应评价)"
- )
- return ActionResponse(Action.REQLLM, weather_report, None)
\ No newline at end of file
+ print(f"当前查询的城市名称是: {city_name}")
+
+ weather_report = f"您查询的位置是:{city_name}\n\n当前天气: {current_abstract}\n"
+
+ # 添加有效的当前天气参数
+ if current_basic:
+ weather_report += "详细参数:\n"
+ for key, value in current_basic.items():
+ if value != "0": # 过滤无效值
+ weather_report += f" · {key}: {value}\n"
+
+ # 添加7天预报
+ weather_report += "\n未来7天预报:\n"
+ for date, weather, high, low in temps_list:
+ weather_report += f"{date}: {weather},气温 {low}~{high}\n"
+
+ # 提示语
+ weather_report += "\n(如需某一天的具体天气,请告诉我日期)"
+
+ return ActionResponse(Action.REQLLM, weather_report, None)