From 94f0419ef7392ec177fdc9b1e311106dff2e35c9 Mon Sep 17 00:00:00 2001
From: Soulter <37870767+Soulter@users.noreply.github.com>
Date: Wed, 20 Aug 2025 16:41:22 +0800
Subject: [PATCH 01/14] docs: update readme
---
README.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 1a79a18df..9e0bfe40b 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-
+

@@ -25,7 +25,7 @@ _✨ 易上手的多平台 LLM 聊天机器人及开发框架 ✨_
问题提交
-AstrBot 是一个松耦合、异步、支持多消息平台部署、具有易用的插件系统和完善的大语言模型(LLM)接入功能的聊天机器人及开发框架。
+AstrBot 是一个开源的一站式 Agentic 聊天机器人平台及开发框架。
## ✨ 主要功能
From cbe94b84fca63a49957d3542b1dc09ccddb6767d Mon Sep 17 00:00:00 2001
From: AkkoYK <69787631+AkkoYK@users.noreply.github.com>
Date: Fri, 22 Aug 2025 16:55:07 +0800
Subject: [PATCH 02/14] =?UTF-8?q?feat:=20=E4=B8=BA=20FishAudio=20TTS=20?=
=?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=8F=AF=E9=80=89=E7=9A=84=20reference=5Fid?=
=?UTF-8?q?=20=E7=9B=B4=E6=8E=A5=E6=8C=87=E5=AE=9A=E5=8A=9F=E8=83=BD=20(#2?=
=?UTF-8?q?513)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* 移除TTS提供商:FishAudio TTS的角色名称查询机制,改为直接使用参考模型ID
/// 修改内容 ///
- 移除复杂的角色查询逻辑
删除了 get_reference_id_by_character 方法
移除了通过角色名称搜索模型ID的API调用逻辑
- 简化配置字段
将 fishaudio-tts-character 字段替换为 fishaudio-tts-reference-id
设置默认值为可莉的模型ID:626bb6d3f3364c9cbc3aa6a67300a664
- 优化代码结构
直接在初始化时获取reference_id
简化请求生成逻辑,直接使用配置的模型ID
/// 修改原因 ///
避免同名冲突:不同模型可能使用相同的角色名称,导致获取错误的模型
提高性能:移除了额外的API查询步骤,减少延迟
增强可靠性:用户直接指定准确的模型ID,避免搜索失败的情况
简化维护:减少了代码复杂度,降低维护成本
/// 新的使用方式 ///
用户需要从 FishAudio 模型的详情页面/URL 中获取具体的模型ID(如 626bb6d3f3364c9cbc3aa6a67300a664),并在配置中直接填入 fishaudio-tts-reference-id 字段。
这个修改使得FishAudio TTS的配置更加直观和可靠,同时提升了系统的整体性能。
* Refactor: 添加FishAudio TTS reference_id格式验证
添加ID格式验证逻辑,防止无效的reference_id调用API失败。
验证32位十六进制格式并提供详细错误提示。
* Feat: 添加FishAudio TTS可选reference_id配置实现向前兼容
新增可选的reference_id字段,优先使用直接ID,未配置时回退到角色名称查询。
保持完全向前兼容,现有配置无需修改。
---
astrbot/core/config/default.py | 6 +++
.../sources/fishaudio_tts_api_source.py | 40 +++++++++++++++++--
2 files changed, 43 insertions(+), 3 deletions(-)
diff --git a/astrbot/core/config/default.py b/astrbot/core/config/default.py
index 70122a282..528e8f899 100644
--- a/astrbot/core/config/default.py
+++ b/astrbot/core/config/default.py
@@ -971,6 +971,7 @@ CONFIG_METADATA_2 = {
"api_key": "",
"api_base": "https://api.fish.audio/v1",
"fishaudio-tts-character": "可莉",
+ "fishaudio-tts-reference-id": "",
"timeout": "20",
},
"阿里云百炼 TTS(API)": {
@@ -1564,6 +1565,11 @@ CONFIG_METADATA_2 = {
"type": "string",
"hint": "fishaudio TTS 的角色。默认为可莉。更多角色请访问:https://fish.audio/zh-CN/discovery",
},
+ "fishaudio-tts-reference-id": {
+ "description": "reference_id",
+ "type": "string",
+ "hint": "fishaudio TTS 的参考模型ID(可选)。如果填入此字段,将直接使用模型ID而不通过角色名称查询。例如:626bb6d3f3364c9cbc3aa6a67300a664。更多模型请访问:https://fish.audio/zh-CN/discovery,进入模型详情界面后可复制模型ID",
+ },
"whisper_hint": {
"description": "本地部署 Whisper 模型须知",
"type": "string",
diff --git a/astrbot/core/provider/sources/fishaudio_tts_api_source.py b/astrbot/core/provider/sources/fishaudio_tts_api_source.py
index c0cf044b8..893f27463 100644
--- a/astrbot/core/provider/sources/fishaudio_tts_api_source.py
+++ b/astrbot/core/provider/sources/fishaudio_tts_api_source.py
@@ -1,5 +1,6 @@
import os
import uuid
+import re
import ormsgpack
from pydantic import BaseModel, conint
from httpx import AsyncClient
@@ -24,8 +25,8 @@ class ServeTTSRequest(BaseModel):
# 参考音频
references: list[ServeReferenceAudio] = []
# 参考模型 ID
- # 例如 https://fish.audio/m/7f92f8afb8ec43bf81429cc1c9199cb1/
- # 其中reference_id为 7f92f8afb8ec43bf81429cc1c9199cb1
+ # 例如 https://fish.audio/m/626bb6d3f3364c9cbc3aa6a67300a664/
+ # 其中reference_id为 626bb6d3f3364c9cbc3aa6a67300a664
reference_id: str | None = None
# 对中英文文本进行标准化,这可以提高数字的稳定性
normalize: bool = True
@@ -44,6 +45,7 @@ class ProviderFishAudioTTSAPI(TTSProvider):
) -> None:
super().__init__(provider_config, provider_settings)
self.chosen_api_key: str = provider_config.get("api_key", "")
+ self.reference_id: str = provider_config.get("fishaudio-tts-reference-id", "")
self.character: str = provider_config.get("fishaudio-tts-character", "可莉")
self.api_base: str = provider_config.get(
"api_base", "https://api.fish-audio.cn/v1"
@@ -81,11 +83,43 @@ class ProviderFishAudioTTSAPI(TTSProvider):
return item["_id"]
return None
+ def _validate_reference_id(self, reference_id: str) -> bool:
+ """
+ 验证reference_id格式是否有效
+
+ Args:
+ reference_id: 参考模型ID
+
+ Returns:
+ bool: ID是否有效
+ """
+ if not reference_id or not reference_id.strip():
+ return False
+
+ # FishAudio的reference_id通常是32位十六进制字符串
+ # 例如: 626bb6d3f3364c9cbc3aa6a67300a664
+ pattern = r'^[a-fA-F0-9]{32}$'
+ return bool(re.match(pattern, reference_id.strip()))
+
async def _generate_request(self, text: str) -> dict:
+ # 向前兼容逻辑:优先使用reference_id,如果没有则使用角色名称查询
+ if self.reference_id and self.reference_id.strip():
+ # 验证reference_id格式
+ if not self._validate_reference_id(self.reference_id):
+ raise ValueError(
+ f"无效的FishAudio参考模型ID: '{self.reference_id}'. "
+ f"请确保ID是32位十六进制字符串(例如: 626bb6d3f3364c9cbc3aa6a67300a664)。"
+ f"您可以从 https://fish.audio/zh-CN/discovery 获取有效的模型ID。"
+ )
+ reference_id = self.reference_id.strip()
+ else:
+ # 回退到原来的角色名称查询逻辑
+ reference_id = await self._get_reference_id_by_character(self.character)
+
return ServeTTSRequest(
text=text,
format="wav",
- reference_id=await self._get_reference_id_by_character(self.character),
+ reference_id=reference_id,
)
async def get_audio(self, text: str) -> str:
From 29845fcc4c2aa56c79d95e5db826b107ee60266f Mon Sep 17 00:00:00 2001
From: Raven95676
Date: Sat, 23 Aug 2025 14:14:25 +0800
Subject: [PATCH 03/14] =?UTF-8?q?feat:=20Gemini=E6=B7=BB=E5=8A=A0=E5=AF=B9?=
=?UTF-8?q?LLMResponse=E7=9A=84raw=5Fcompletion=E6=94=AF=E6=8C=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
astrbot/core/provider/sources/gemini_source.py | 3 +++
1 file changed, 3 insertions(+)
diff --git a/astrbot/core/provider/sources/gemini_source.py b/astrbot/core/provider/sources/gemini_source.py
index b04e619a8..e1d12623c 100644
--- a/astrbot/core/provider/sources/gemini_source.py
+++ b/astrbot/core/provider/sources/gemini_source.py
@@ -431,6 +431,7 @@ class ProviderGoogleGenAI(Provider):
continue
llm_response = LLMResponse("assistant")
+ llm_response.raw_completion = result
llm_response.result_chain = self._process_content_parts(result, llm_response)
return llm_response
@@ -481,6 +482,7 @@ class ProviderGoogleGenAI(Provider):
part.function_call for part in chunk.candidates[0].content.parts
):
llm_response = LLMResponse("assistant", is_chunk=False)
+ llm_response.raw_completion = chunk
llm_response.result_chain = self._process_content_parts(
chunk, llm_response
)
@@ -496,6 +498,7 @@ class ProviderGoogleGenAI(Provider):
# Process the final chunk for potential tool calls or other content
if chunk.candidates[0].content.parts:
final_response = LLMResponse("assistant", is_chunk=False)
+ final_response.raw_completion = chunk
final_response.result_chain = self._process_content_parts(
chunk, final_response
)
From 5872f1e01794ca8adaac9781d62a22ead9354f10 Mon Sep 17 00:00:00 2001
From: xiewoc <70128845+xiewoc@users.noreply.github.com>
Date: Tue, 26 Aug 2025 20:40:59 +0800
Subject: [PATCH 04/14] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E5=AE=98?=
=?UTF-8?q?=E6=96=B9=20QQ=20=E6=8E=A5=E5=8F=A3=E5=8F=91=E9=80=81=E8=AF=AD?=
=?UTF-8?q?=E9=9F=B3=20(#2525)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* Update dingtalk_event.py
* Add files via upload
* Add files via upload
* Update qqofficial_platform_adapter.py
* Add files via upload
* chore: clean comments
* chore: clean code
---------
Co-authored-by: Soulter <37870767+Soulter@users.noreply.github.com>
---
.../sources/dingtalk/dingtalk_event.py | 2 +-
.../qqofficial/qqofficial_message_event.py | 99 ++++++++++++++++++-
2 files changed, 97 insertions(+), 4 deletions(-)
diff --git a/astrbot/core/platform/sources/dingtalk/dingtalk_event.py b/astrbot/core/platform/sources/dingtalk/dingtalk_event.py
index c2188dc36..bded9bfd0 100644
--- a/astrbot/core/platform/sources/dingtalk/dingtalk_event.py
+++ b/astrbot/core/platform/sources/dingtalk/dingtalk_event.py
@@ -26,7 +26,7 @@ class DingtalkMessageEvent(AstrMessageEvent):
await asyncio.get_event_loop().run_in_executor(
None,
client.reply_markdown,
- "AstrBot",
+ segment.text,
segment.text,
self.message_obj.raw_message,
)
diff --git a/astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py b/astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py
index 9bb68a040..f6284542c 100644
--- a/astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py
+++ b/astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py
@@ -3,15 +3,23 @@ import botpy.message
import botpy.types
import botpy.types.message
import asyncio
+import base64
+import aiofiles
from astrbot.core.utils.io import file_to_base64, download_image_by_url
+from astrbot.core.utils.tencent_record_helper import wav_to_tencent_silk
+from astrbot.core.utils.astrbot_path import get_astrbot_data_path
from astrbot.api.event import AstrMessageEvent, MessageChain
from astrbot.api.platform import AstrBotMessage, PlatformMetadata
-from astrbot.api.message_components import Plain, Image
+from astrbot.api.message_components import Plain, Image, Record
from botpy import Client
from botpy.http import Route
from astrbot.api import logger
+from botpy.types.message import Media
from botpy.types import message
+from typing import Optional
import random
+import uuid
+import os
class QQOfficialMessageEvent(AstrMessageEvent):
@@ -36,6 +44,7 @@ class QQOfficialMessageEvent(AstrMessageEvent):
stream_payload = {"state": 1, "id": None, "index": 0, "reset": False}
last_edit_time = 0 # 上次编辑消息的时间
throttle_interval = 1 # 编辑消息的间隔时间 (秒)
+ ret = None
try:
async for chain in generator:
source = self.message_obj.raw_message
@@ -85,9 +94,10 @@ class QQOfficialMessageEvent(AstrMessageEvent):
plain_text,
image_base64,
image_path,
+ record_file_path
) = await QQOfficialMessageEvent._parse_to_qqofficial(self.send_buffer)
- if not plain_text and not image_base64 and not image_path:
+ if not plain_text and not image_base64 and not image_path and not record_file_path:
return
payload = {
@@ -98,6 +108,8 @@ class QQOfficialMessageEvent(AstrMessageEvent):
if not isinstance(source, (botpy.message.Message, botpy.message.DirectMessage)):
payload["msg_seq"] = random.randint(1, 10000)
+ ret = None
+
match type(source):
case botpy.message.GroupMessage:
if image_base64:
@@ -106,6 +118,12 @@ class QQOfficialMessageEvent(AstrMessageEvent):
)
payload["media"] = media
payload["msg_type"] = 7
+ if record_file_path: # group record msg
+ media = await self.upload_group_and_c2c_record(
+ record_file_path, 3, group_openid=source.group_openid
+ )
+ payload["media"] = media
+ payload["msg_type"] = 7
ret = await self.bot.api.post_group_message(
group_openid=source.group_openid, **payload
)
@@ -116,6 +134,12 @@ class QQOfficialMessageEvent(AstrMessageEvent):
)
payload["media"] = media
payload["msg_type"] = 7
+ if record_file_path: # c2c record
+ media = await self.upload_group_and_c2c_record(
+ record_file_path, 3, openid = source.author.user_openid
+ )
+ payload["media"] = media
+ payload["msg_type"] = 7
if stream:
ret = await self.post_c2c_message(
openid=source.author.user_openid,
@@ -165,6 +189,59 @@ class QQOfficialMessageEvent(AstrMessageEvent):
)
return await self.bot.api._http.request(route, json=payload)
+ async def upload_group_and_c2c_record(
+ self,
+ file_source: str,
+ file_type: int,
+ srv_send_msg: bool = False,
+ **kwargs
+ ) -> Optional[Media]:
+ """
+ 上传媒体文件
+ """
+ # 构建基础payload
+ payload = {
+ "file_type": file_type,
+ "srv_send_msg": srv_send_msg
+ }
+
+ # 处理文件数据
+ if os.path.exists(file_source):
+ # 读取本地文件
+ async with aiofiles.open(file_source, 'rb') as f:
+ file_content = await f.read()
+ # use base64 encode
+ payload["file_data"] = base64.b64encode(file_content).decode('utf-8')
+ else:
+ # 使用URL
+ payload["url"] = file_source
+
+ # 添加接收者信息和确定路由
+ if "openid" in kwargs:
+ payload["openid"] = kwargs["openid"]
+ route = Route("POST", "/v2/users/{openid}/files", openid=kwargs["openid"])
+ elif "group_openid" in kwargs:
+ payload["group_openid"] =kwargs["group_openid"]
+ route = Route("POST", "/v2/groups/{group_openid}/files", group_openid=kwargs["group_openid"])
+ else:
+ return None
+
+ try:
+ # 使用底层HTTP请求
+ result = await self.bot.api._http.request(route, json=payload)
+
+ if result:
+ return Media(
+ file_uuid=result.get("file_uuid"),
+ file_info=result.get("file_info"),
+ ttl=result.get("ttl", 0),
+ file_id=result.get("id", "")
+ )
+ except Exception as e:
+ logger.error(f"上传请求错误: {e}")
+
+ return None
+
async def post_c2c_message(
self,
openid: str,
@@ -191,6 +268,7 @@ class QQOfficialMessageEvent(AstrMessageEvent):
plain_text = ""
image_base64 = None # only one img supported
image_file_path = None
+ record_file_path = None
for i in message.chain:
if isinstance(i, Plain):
plain_text += i.text
@@ -206,6 +284,21 @@ class QQOfficialMessageEvent(AstrMessageEvent):
else:
image_base64 = file_to_base64(i.file)
image_base64 = image_base64.removeprefix("base64://")
+ elif isinstance(i, Record):
+ if i.file:
+ record_wav_path = await i.convert_to_file_path() # wav 路径
+ temp_dir = os.path.join(get_astrbot_data_path(), "temp")
+ record_tecent_silk_path = os.path.join(temp_dir, f"{uuid.uuid4()}.silk")
+ try:
+ duration = await wav_to_tencent_silk(record_wav_path, record_tecent_silk_path)
+ if duration > 0:
+ record_file_path = record_tecent_silk_path
+ else:
+ record_file_path = None
+ logger.error("转换音频格式时出错:音频时长不大于0")
+ except Exception as e:
+ logger.error(f"处理语音时出错: {e}")
+ record_file_path = None
else:
logger.debug(f"qq_official 忽略 {i.type}")
- return plain_text, image_base64, image_file_path
+ return plain_text, image_base64, image_file_path, record_file_path
From 10b9228060d144f89fccbb41b22ae87558b38357 Mon Sep 17 00:00:00 2001
From: ZvZPvz <130430455+ZvZPvz@users.noreply.github.com>
Date: Tue, 26 Aug 2025 20:44:56 +0800
Subject: [PATCH 05/14] =?UTF-8?q?feat:=20=E8=B0=83=E7=94=A8=20deepseek-rea?=
=?UTF-8?q?soner=20=E6=97=B6=E8=87=AA=E5=8A=A8=E7=A7=BB=E9=99=A4=20tools?=
=?UTF-8?q?=20(#2531)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* 调用DeepSeek为思考模式时自动移除tools
* Update astrbot/core/provider/sources/openai_source.py
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
* Update openai_source.py
* Update openai_source.py
---------
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
---
astrbot/core/provider/sources/openai_source.py | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/astrbot/core/provider/sources/openai_source.py b/astrbot/core/provider/sources/openai_source.py
index b286eb701..9ee0fc695 100644
--- a/astrbot/core/provider/sources/openai_source.py
+++ b/astrbot/core/provider/sources/openai_source.py
@@ -99,10 +99,13 @@ class ProviderOpenAIOfficial(Provider):
for key in to_del:
del payloads[key]
- # 针对 qwen3 模型的特殊处理:非流式调用必须设置 enable_thinking=false
model = payloads.get("model", "")
+ # 针对 qwen3 模型的特殊处理:非流式调用必须设置 enable_thinking=false
if "qwen3" in model.lower():
- extra_body["enable_thinking"] = False
+ extra_body["enable_thinking"] = False
+ # 针对 deepseek 模型的特殊处理:deepseek-reasoner调用必须移除 tools ,否则将被切换至 deepseek-chat
+ elif model == "deepseek-reasoner" and "tools" in payloads:
+ del payloads["tools"]
completion = await self.client.chat.completions.create(
**payloads, stream=False, extra_body=extra_body
From 98d8eaee02c425c8244f85141678e44c28562c81 Mon Sep 17 00:00:00 2001
From: RC-CHN <67079377+RC-CHN@users.noreply.github.com>
Date: Tue, 26 Aug 2025 21:08:46 +0800
Subject: [PATCH 06/14] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=20no=5Fproxy?=
=?UTF-8?q?=20=E9=85=8D=E7=BD=AE=E6=94=AF=E6=8C=81=E4=BB=A5=E4=BC=98?=
=?UTF-8?q?=E5=8C=96=E4=BB=A3=E7=90=86=E8=AE=BE=E7=BD=AE=20(#2564)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
astrbot/core/config/default.py | 7 +++++++
astrbot/core/core_lifecycle.py | 6 +++++-
2 files changed, 12 insertions(+), 1 deletion(-)
diff --git a/astrbot/core/config/default.py b/astrbot/core/config/default.py
index 528e8f899..b299cbffb 100644
--- a/astrbot/core/config/default.py
+++ b/astrbot/core/config/default.py
@@ -103,6 +103,7 @@ DEFAULT_CONFIG = {
"t2i_endpoint": "",
"t2i_use_file_service": False,
"http_proxy": "",
+ "no_proxy": ["localhost", "127.0.0.1", "::1"],
"dashboard": {
"enable": True,
"username": "astrbot",
@@ -1916,6 +1917,12 @@ CONFIG_METADATA_2 = {
"type": "string",
"hint": "启用后,会以添加环境变量的方式设置代理。格式为 `http://ip:port`",
},
+ "no_proxy": {
+ "description": "直连地址列表",
+ "type": "list",
+ "items": {"type": "string"},
+ "hint": "在此处添加不希望通过代理访问的地址,例如内部服务地址。回车添加,可添加多个,如未设置代理请忽略此配置",
+ },
"timezone": {
"description": "时区",
"type": "string",
diff --git a/astrbot/core/core_lifecycle.py b/astrbot/core/core_lifecycle.py
index 069e92000..12226d9e1 100644
--- a/astrbot/core/core_lifecycle.py
+++ b/astrbot/core/core_lifecycle.py
@@ -52,14 +52,18 @@ class AstrBotCoreLifecycle:
os.environ["https_proxy"] = proxy_config
os.environ["http_proxy"] = proxy_config
logger.debug(f"Using proxy: {proxy_config}")
+ # 设置 no_proxy
+ no_proxy_list = self.astrbot_config.get("no_proxy", [])
+ os.environ["no_proxy"] = ",".join(no_proxy_list)
else:
# 清空代理环境变量
if "https_proxy" in os.environ:
del os.environ["https_proxy"]
if "http_proxy" in os.environ:
del os.environ["http_proxy"]
+ if "no_proxy" in os.environ:
+ del os.environ["no_proxy"]
logger.debug("HTTP proxy cleared")
- os.environ["no_proxy"] = "localhost,127.0.0.1,::1"
async def initialize(self):
"""
From 33407c9f0d7ec03a48828bc0d4292a9fef3ececc Mon Sep 17 00:00:00 2001
From: Junhua Don <1724728802@qq.com>
Date: Sun, 31 Aug 2025 11:12:25 +0800
Subject: [PATCH 07/14] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E7=BC=96?=
=?UTF-8?q?=E8=BE=91=E4=BC=9A=E8=AF=9D=E5=90=8D=E7=A7=B0=E7=AA=97=E5=8F=A3?=
=?UTF-8?q?=E7=9A=84=E5=9C=86=E8=A7=92=E5=92=8C=E5=B7=A6=E5=8F=B3=E8=BE=B9?=
=?UTF-8?q?=E8=B7=9D=E9=97=AE=E9=A2=98=20(#2583)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
dashboard/src/views/SessionManagementPage.vue | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/dashboard/src/views/SessionManagementPage.vue b/dashboard/src/views/SessionManagementPage.vue
index 1d25d0a90..9896fdde7 100644
--- a/dashboard/src/views/SessionManagementPage.vue
+++ b/dashboard/src/views/SessionManagementPage.vue
@@ -480,7 +480,7 @@
-
+
mdi-rename-box
{{ tm('nameEditor.title') }}
@@ -490,8 +490,9 @@
-
-
+
+
{{ tm('nameEditor.hint') }}
+
From 2f78d30e93517ad840badc6096a99368bcf02431 Mon Sep 17 00:00:00 2001
From: Soulter <905617992@qq.com>
Date: Sun, 31 Aug 2025 11:42:28 +0800
Subject: [PATCH 08/14] feat: automated release from every commit in master
branch
---
.github/workflows/dashboard_ci.yml | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/.github/workflows/dashboard_ci.yml b/.github/workflows/dashboard_ci.yml
index 9ea359579..baa2de576 100644
--- a/.github/workflows/dashboard_ci.yml
+++ b/.github/workflows/dashboard_ci.yml
@@ -25,6 +25,7 @@ jobs:
echo "COMMIT_SHA=$(git rev-parse HEAD)" >> $GITHUB_ENV
mkdir -p dashboard/dist/assets
echo $COMMIT_SHA > dashboard/dist/assets/version
+ zip -r dashboard/dist.zip dashboard/dist
- name: Archive production artifacts
uses: actions/upload-artifact@v4
@@ -33,3 +34,12 @@ jobs:
path: |
dashboard/dist
!dist/**/*.md
+
+ - name: Create GitHub Release
+ uses: ncipollo/release-action@v1
+ with:
+ tag: ${{ env.COMMIT_SHA }}
+ repo: AstrBotDevs/astrbot-release-harbour
+ body: "Automated release from commit ${{ env.COMMIT_SHA }}"
+ token: ${{ secrets.ASTRBOT_HARBOUR_TOKEN }}
+ artifacts: "dashboard/dist.zip"
\ No newline at end of file
From 32d6cd777637b63e05f4980bde87f6009a784958 Mon Sep 17 00:00:00 2001
From: Soulter <905617992@qq.com>
Date: Sun, 31 Aug 2025 11:50:26 +0800
Subject: [PATCH 09/14] fix: update GitHub release action parameters for
clarity
---
.github/workflows/dashboard_ci.yml | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/.github/workflows/dashboard_ci.yml b/.github/workflows/dashboard_ci.yml
index baa2de576..8689abbb5 100644
--- a/.github/workflows/dashboard_ci.yml
+++ b/.github/workflows/dashboard_ci.yml
@@ -39,7 +39,8 @@ jobs:
uses: ncipollo/release-action@v1
with:
tag: ${{ env.COMMIT_SHA }}
- repo: AstrBotDevs/astrbot-release-harbour
+ owner: AstrBotDevs
+ repo: astrbot-release-harbour
body: "Automated release from commit ${{ env.COMMIT_SHA }}"
token: ${{ secrets.ASTRBOT_HARBOUR_TOKEN }}
artifacts: "dashboard/dist.zip"
\ No newline at end of file
From c1202cda63059ae4ece2febef56d7eebd4a1d896 Mon Sep 17 00:00:00 2001
From: Soulter <905617992@qq.com>
Date: Sun, 31 Aug 2025 11:52:42 +0800
Subject: [PATCH 10/14] fix: update GitHub release action to use correct commit
SHA variable
---
.github/workflows/dashboard_ci.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/dashboard_ci.yml b/.github/workflows/dashboard_ci.yml
index 8689abbb5..19e681b84 100644
--- a/.github/workflows/dashboard_ci.yml
+++ b/.github/workflows/dashboard_ci.yml
@@ -38,9 +38,9 @@ jobs:
- name: Create GitHub Release
uses: ncipollo/release-action@v1
with:
- tag: ${{ env.COMMIT_SHA }}
+ tag: release-${{ github.sha }}
owner: AstrBotDevs
repo: astrbot-release-harbour
- body: "Automated release from commit ${{ env.COMMIT_SHA }}"
+ body: "Automated release from commit ${{ github.sha }}"
token: ${{ secrets.ASTRBOT_HARBOUR_TOKEN }}
artifacts: "dashboard/dist.zip"
\ No newline at end of file
From b1e4bff3eca02e03f09b74fd5575e081a996cc96 Mon Sep 17 00:00:00 2001
From: Soulter <905617992@qq.com>
Date: Sun, 31 Aug 2025 19:55:46 +0800
Subject: [PATCH 11/14] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E5=8D=87?=
=?UTF-8?q?=E7=BA=A7=E7=9A=84=E5=90=8C=E6=97=B6=E6=9B=B4=E6=96=B0=E5=88=B0?=
=?UTF-8?q?=E6=8C=87=E5=AE=9A=E7=89=88=E6=9C=AC=E7=9A=84=20WebUI?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.github/workflows/dashboard_ci.yml | 3 +-
astrbot/core/updator.py | 6 +--
astrbot/core/utils/io.py | 52 +++++++++++++++----
astrbot/core/zip_updator.py | 7 ++-
astrbot/dashboard/routes/update.py | 13 ++---
.../src/i18n/locales/en-US/core/header.json | 6 +--
.../src/i18n/locales/zh-CN/core/header.json | 5 +-
.../full/vertical-header/VerticalHeader.vue | 5 +-
8 files changed, 65 insertions(+), 32 deletions(-)
diff --git a/.github/workflows/dashboard_ci.yml b/.github/workflows/dashboard_ci.yml
index 19e681b84..3c96297e8 100644
--- a/.github/workflows/dashboard_ci.yml
+++ b/.github/workflows/dashboard_ci.yml
@@ -25,7 +25,8 @@ jobs:
echo "COMMIT_SHA=$(git rev-parse HEAD)" >> $GITHUB_ENV
mkdir -p dashboard/dist/assets
echo $COMMIT_SHA > dashboard/dist/assets/version
- zip -r dashboard/dist.zip dashboard/dist
+ cd dashboard
+ zip -r dist.zip dist
- name: Archive production artifacts
uses: actions/upload-artifact@v4
diff --git a/astrbot/core/updator.py b/astrbot/core/updator.py
index d7ee77bf5..e76c6f032 100644
--- a/astrbot/core/updator.py
+++ b/astrbot/core/updator.py
@@ -89,7 +89,6 @@ class AstrBotUpdator(RepoZipUpdator):
file_url = update_data[0]["zipball_url"]
elif str(version).startswith("v"):
# 更新到指定版本
- logger.info(f"正在更新到指定版本: {version}")
for data in update_data:
if data["tag_name"] == version:
file_url = data["zipball_url"]
@@ -98,8 +97,8 @@ class AstrBotUpdator(RepoZipUpdator):
else:
if len(str(version)) != 40:
raise Exception("commit hash 长度不正确,应为 40")
- logger.info(f"正在尝试更新到指定 commit: {version}")
- file_url = "https://github.com/Soulter/AstrBot/archive/" + version + ".zip"
+ file_url = f"https://github.com/Soulter/AstrBot/archive/{version}.zip"
+ logger.info(f"准备更新至指定版本的 AstrBot Core: {version}")
if proxy:
proxy = proxy.removesuffix("/")
@@ -107,6 +106,7 @@ class AstrBotUpdator(RepoZipUpdator):
try:
await download_file(file_url, "temp.zip")
+ logger.info("下载 AstrBot Core 更新文件完成,正在执行解压...")
self.unzip_file("temp.zip", self.MAIN_PATH)
except BaseException as e:
raise e
diff --git a/astrbot/core/utils/io.py b/astrbot/core/utils/io.py
index 2b34c2a14..5f854c5bc 100644
--- a/astrbot/core/utils/io.py
+++ b/astrbot/core/utils/io.py
@@ -8,6 +8,7 @@ import base64
import zipfile
import uuid
import psutil
+import logging
import certifi
@@ -16,6 +17,8 @@ from typing import Union
from PIL import Image
from .astrbot_path import get_astrbot_data_path
+logger = logging.getLogger("astrbot")
+
def on_error(func, path, exc_info):
"""
@@ -212,19 +215,50 @@ async def get_dashboard_version():
return None
-async def download_dashboard(path: str = None, extract_path: str = "data"):
+async def download_dashboard(
+ path: str | None = None,
+ extract_path: str = "data",
+ latest: bool = True,
+ version: str | None = None,
+ proxy: str | None = None,
+):
"""下载管理面板文件"""
if path is None:
path = os.path.join(get_astrbot_data_path(), "dashboard.zip")
- dashboard_release_url = "https://astrbot-registry.soulter.top/download/astrbot-dashboard/latest/dist.zip"
- try:
- await download_file(dashboard_release_url, path, show_progress=True)
- except BaseException as _:
- dashboard_release_url = (
- "https://github.com/Soulter/AstrBot/releases/latest/download/dist.zip"
+ if latest or len(str(version)) != 40:
+ logger.info("准备下载最新发行版本的 AstrBot WebUI")
+ ver_name = "latest" if latest else version
+ dashboard_release_url = f"https://astrbot-registry.soulter.top/download/astrbot-dashboard/{ver_name}/dist.zip"
+ try:
+ await download_file(dashboard_release_url, path, show_progress=True)
+ except BaseException as _:
+ if latest:
+ dashboard_release_url = "https://github.com/Soulter/AstrBot/releases/latest/download/dist.zip"
+ else:
+ dashboard_release_url = f"https://github.com/Soulter/AstrBot/releases/download/{version}/dist.zip"
+ if proxy:
+ dashboard_release_url = f"{proxy}/{dashboard_release_url}"
+ await download_file(dashboard_release_url, path, show_progress=True)
+ else:
+ logger.info(f"准备下载指定版本的 AstrBot WebUI: {version}")
+
+ url = (
+ "https://api.github.com/repos/AstrBotDevs/astrbot-release-harbour/releases"
)
- await download_file(dashboard_release_url, path, show_progress=True)
- print("解压管理面板文件中...")
+ if proxy:
+ url = f"{proxy}/{url}"
+ async with aiohttp.ClientSession(trust_env=True) as session:
+ async with session.get(url) as resp:
+ if resp.status == 200:
+ releases = await resp.json()
+ for release in releases:
+ if version in release["tag_name"]:
+ download_url = release["assets"][0]["browser_download_url"]
+ await download_file(download_url, path, show_progress=True)
+ else:
+ logger.warning(f"未找到指定的版本的 Dashboard 构建文件: {version}")
+ return
+
with zipfile.ZipFile(path, "r") as z:
z.extractall(extract_path)
diff --git a/astrbot/core/zip_updator.py b/astrbot/core/zip_updator.py
index a09342795..c72965e67 100644
--- a/astrbot/core/zip_updator.py
+++ b/astrbot/core/zip_updator.py
@@ -181,14 +181,13 @@ class RepoZipUpdator:
"""
os.makedirs(target_dir, exist_ok=True)
update_dir = ""
- logger.info(f"解压文件: {zip_path}")
with zipfile.ZipFile(zip_path, "r") as z:
update_dir = z.namelist()[0]
z.extractall(target_dir)
+ logger.debug(f"解压文件完成: {zip_path}")
files = os.listdir(os.path.join(target_dir, update_dir))
for f in files:
- logger.info(f"移动更新文件/目录: {f}")
if os.path.isdir(os.path.join(target_dir, update_dir, f)):
if os.path.exists(os.path.join(target_dir, f)):
shutil.rmtree(os.path.join(target_dir, f), onerror=on_error)
@@ -198,13 +197,13 @@ class RepoZipUpdator:
shutil.move(os.path.join(target_dir, update_dir, f), target_dir)
try:
- logger.info(
+ logger.debug(
f"删除临时更新文件: {zip_path} 和 {os.path.join(target_dir, update_dir)}"
)
shutil.rmtree(os.path.join(target_dir, update_dir), onerror=on_error)
os.remove(zip_path)
except BaseException:
- logger.warn(
+ logger.warning(
f"删除更新文件失败,可以手动删除 {zip_path} 和 {os.path.join(target_dir, update_dir)}"
)
diff --git a/astrbot/dashboard/routes/update.py b/astrbot/dashboard/routes/update.py
index f88e9a208..79aa56bc8 100644
--- a/astrbot/dashboard/routes/update.py
+++ b/astrbot/dashboard/routes/update.py
@@ -48,7 +48,7 @@ class UpdateRoute(Route):
"version": f"v{VERSION}",
"has_new_version": ret is not None,
"dashboard_version": dv,
- "dashboard_has_new_version": dv != f"v{VERSION}",
+ "dashboard_has_new_version": dv and dv != f"v{VERSION}",
},
).__dict__
except Exception as e:
@@ -82,11 +82,12 @@ class UpdateRoute(Route):
latest=latest, version=version, proxy=proxy
)
- if latest:
- try:
- await download_dashboard()
- except Exception as e:
- logger.error(f"下载管理面板文件失败: {e}。")
+ try:
+ await download_dashboard(
+ latest=latest, version=version, proxy=proxy
+ )
+ except Exception as e:
+ logger.error(f"下载管理面板文件失败: {e}。")
# pip 更新依赖
logger.info("更新依赖中...")
diff --git a/dashboard/src/i18n/locales/en-US/core/header.json b/dashboard/src/i18n/locales/en-US/core/header.json
index c7e354086..ee0949988 100644
--- a/dashboard/src/i18n/locales/en-US/core/header.json
+++ b/dashboard/src/i18n/locales/en-US/core/header.json
@@ -31,9 +31,9 @@
"description": "Versions marked as pre-release may contain unknown issues or bugs and are not recommended for production use. If you encounter any problems, please visit ",
"issueLink": "GitHub Issues"
},
- "tip": "💡 TIP: Switching to an older version or a specific version will not re-download the dashboard files, which may cause some data display errors. You can find the corresponding dashboard files dist.zip at",
- "tipLink": "here",
- "tipContinue": ", extract and replace the data/dist folder. Of course, the frontend source code is in the dashboard directory, you can also build it yourself using npm install and npm build.",
+ "tip": "💡 TIP:",
+ "tipLink": "",
+ "tipContinue": "By default, the corresponding version of the WebUI files will be downloaded when switching versions. The WebUI code is located in the dashboard directory of the project, and you can use npm to build it yourself.",
"dockerTip": "The `Update to Latest Version` button will try to update both the bot main program and the dashboard. If you are using Docker deployment, you can also re-pull the image or use",
"dockerTipLink": "watchtower",
"dockerTipContinue": "to automatically monitor and pull.",
diff --git a/dashboard/src/i18n/locales/zh-CN/core/header.json b/dashboard/src/i18n/locales/zh-CN/core/header.json
index 11331a419..611ad31c3 100644
--- a/dashboard/src/i18n/locales/zh-CN/core/header.json
+++ b/dashboard/src/i18n/locales/zh-CN/core/header.json
@@ -31,9 +31,8 @@
"description": "标有预发布标签的版本可能存在未知问题或 Bug,不建议在生产环境使用。如发现问题,请提交至 ",
"issueLink": "GitHub Issues"
},
- "tip": "💡 TIP: 跳到旧版本或者切换到某个版本不会重新下载管理面板文件,这可能会造成部分数据显示错误。您可在",
- "tipLink": "此处",
- "tipContinue": "找到对应的面板文件 dist.zip,解压后替换 data/dist 文件夹即可。当然,前端源代码在 dashboard 目录下,你也可以自己使用 npm install 和 npm build 构建。",
+ "tip": "💡 TIP: ",
+ "tipContinue": "默认在切换版本时会下载对应版本的 WebUI 文件。WebUI 代码位于项目的 dashboard 目录,您可使用 npm 自行构建。",
"dockerTip": "`更新到最新版本` 按钮会同时尝试更新机器人主程序和管理面板。如果您正在使用 Docker 部署,也可以重新拉取镜像或者使用",
"dockerTipLink": "watchtower",
"dockerTipContinue": "来自动监控拉取。",
diff --git a/dashboard/src/layouts/full/vertical-header/VerticalHeader.vue b/dashboard/src/layouts/full/vertical-header/VerticalHeader.vue
index 3e4f94f75..1ce5642e4 100644
--- a/dashboard/src/layouts/full/vertical-header/VerticalHeader.vue
+++ b/dashboard/src/layouts/full/vertical-header/VerticalHeader.vue
@@ -342,8 +342,7 @@ commonStore.getStartTime();
@@ -383,7 +382,7 @@ commonStore.getStartTime();
-
+
{{ item.tag_name }}
From c10973e160fef55d92c7dc983ad3c1277c5d7373 Mon Sep 17 00:00:00 2001
From: Soulter <905617992@qq.com>
Date: Sun, 31 Aug 2025 20:06:37 +0800
Subject: [PATCH 12/14] fix: update getDevCommits function to support GitHub
proxy and handle errors more gracefully
---
.../full/vertical-header/VerticalHeader.vue | 47 ++++++++++++++-----
1 file changed, 35 insertions(+), 12 deletions(-)
diff --git a/dashboard/src/layouts/full/vertical-header/VerticalHeader.vue b/dashboard/src/layouts/full/vertical-header/VerticalHeader.vue
index 1ce5642e4..b2be81941 100644
--- a/dashboard/src/layouts/full/vertical-header/VerticalHeader.vue
+++ b/dashboard/src/layouts/full/vertical-header/VerticalHeader.vue
@@ -189,23 +189,46 @@ function getReleases() {
}
function getDevCommits() {
- fetch('https://api.github.com/repos/Soulter/AstrBot/commits', {
- headers: {
- 'Host': 'api.github.com',
- 'Referer': 'https://api.github.com'
- }
- })
+ let proxy = localStorage.getItem('selectedGitHubProxy') || '';
+ const originalUrl = "https://api.github.com/repos/Soulter/AstrBot/commits";
+ let commits_url = originalUrl;
+ if (proxy !== '') {
+ proxy = proxy.endsWith('/') ? proxy : proxy + '/';
+ commits_url = proxy + originalUrl;
+ }
+
+ function fetchCommits(url: string, onError?: () => void) {
+ fetch(url, {
+ headers: {
+ 'Host': 'api.github.com',
+ 'Referer': 'https://api.github.com'
+ }
+ })
.then(response => response.json())
.then(data => {
- devCommits.value = data.map((commit: any) => ({
- sha: commit.sha,
- date: new Date(commit.commit.author.date).toLocaleString(),
- message: commit.commit.message
- }));
+ devCommits.value = Array.isArray(data)
+ ? data.map((commit: any) => ({
+ sha: commit.sha,
+ date: new Date(commit.commit.author.date).toLocaleString(),
+ message: commit.commit.message
+ }))
+ : [];
})
.catch(err => {
- console.log(err);
+ if (onError) {
+ onError();
+ } else {
+ console.log('获取开发版提交信息失败:', err);
+ }
});
+ }
+
+ fetchCommits(commits_url, () => {
+ if (proxy !== '' && commits_url !== originalUrl) {
+ console.log('使用代理请求失败,尝试直接请求');
+ fetchCommits(originalUrl);
+ }
+ });
}
function switchVersion(version: string) {
From e5eab2af340ab1c825ec72f99b4d4c8af18a3e03 Mon Sep 17 00:00:00 2001
From: Soulter <905617992@qq.com>
Date: Sun, 31 Aug 2025 20:16:18 +0800
Subject: [PATCH 13/14] fix: specify type for devCommits to enhance type safety
---
dashboard/src/layouts/full/vertical-header/VerticalHeader.vue | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/dashboard/src/layouts/full/vertical-header/VerticalHeader.vue b/dashboard/src/layouts/full/vertical-header/VerticalHeader.vue
index b2be81941..44163c7b3 100644
--- a/dashboard/src/layouts/full/vertical-header/VerticalHeader.vue
+++ b/dashboard/src/layouts/full/vertical-header/VerticalHeader.vue
@@ -36,7 +36,7 @@ let dashboardHasNewVersion = ref(false);
let dashboardCurrentVersion = ref('');
let version = ref('');
let releases = ref([]);
-let devCommits = ref([]);
+let devCommits = ref<{ sha: string; date: string; message: string }[]>([]);
let updatingDashboardLoading = ref(false);
let installLoading = ref(false);
From 1e5f243edbdd67064faa0ddbf8ca814bdedd2cf5 Mon Sep 17 00:00:00 2001
From: Soulter <905617992@qq.com>
Date: Sun, 31 Aug 2025 20:25:05 +0800
Subject: [PATCH 14/14] =?UTF-8?q?=F0=9F=93=A6=20release:=20v3.5.26?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
astrbot/core/config/default.py | 2 +-
changelogs/v3.5.26.md | 9 +++++++++
pyproject.toml | 2 +-
3 files changed, 11 insertions(+), 2 deletions(-)
create mode 100644 changelogs/v3.5.26.md
diff --git a/astrbot/core/config/default.py b/astrbot/core/config/default.py
index b299cbffb..637d30bcb 100644
--- a/astrbot/core/config/default.py
+++ b/astrbot/core/config/default.py
@@ -6,7 +6,7 @@ import os
from astrbot.core.utils.astrbot_path import get_astrbot_data_path
-VERSION = "3.5.25"
+VERSION = "3.5.26"
DB_PATH = os.path.join(get_astrbot_data_path(), "data_v3.db")
# 默认配置
diff --git a/changelogs/v3.5.26.md b/changelogs/v3.5.26.md
new file mode 100644
index 000000000..d5301ae92
--- /dev/null
+++ b/changelogs/v3.5.26.md
@@ -0,0 +1,9 @@
+# What's Changed
+
+1. 新增:为 FishAudio TTS 添加可选的 reference_id 直接指定功能 ([#2513](https://github.com/AstrBotDevs/AstrBot/issues/2513))
+2. 新增:Gemini 添加对 LLMResponse 的 raw_completion 支持
+3. 新增:支持官方 QQ 接口发送语音 ([#2525](https://github.com/AstrBotDevs/AstrBot/issues/2525))
+4. 新增:调用 deepseek-reasoner 时自动移除 tools ([#2531](https://github.com/AstrBotDevs/AstrBot/issues/2531))
+5. 新增:添加 no_proxy 配置支持以优化代理设置 ([#2564](https://github.com/AstrBotDevs/AstrBot/issues/2564))
+6. 新增:支持升级的同时更新到指定版本的 WebUI
+7. 修复: 修复编辑会话名称窗口的圆角和左右边距问题 ([#2583](https://github.com/AstrBotDevs/AstrBot/issues/2583))
diff --git a/pyproject.toml b/pyproject.toml
index 276261ff8..693aefe4b 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "AstrBot"
-version = "3.5.25"
+version = "3.5.26"
description = "易上手的多平台 LLM 聊天机器人及开发框架"
readme = "README.md"
requires-python = ">=3.10"