diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 341581157..2e68245aa 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -3,8 +3,8 @@ ### Modifications / 改动点 - + - [x] This is NOT a breaking change. / 这不是一个破坏性变更。 @@ -21,23 +21,14 @@ -- [ ] 😊 如果 PR 中有新加入的功能,已经通过 Issue / 邮件等方式和作者讨论过。 - / If there are new features added in the PR, I have discussed it with the authors through issues/emails, etc. +- [ ] 😊 If there are new features added in the PR, I have discussed it with the authors through issues/emails, etc. + / 如果 PR 中有新加入的功能,已经通过 Issue / 邮件等方式和作者讨论过。 -- [ ] 👀 我的更改经过了良好的测试,**并已在上方提供了“验证步骤”和“运行截图”**。 - / My changes have been well-tested, **and "Verification Steps" and "Screenshots" have been provided above**. +- [ ] 👀 My changes have been well-tested, **and "Verification Steps" and "Screenshots" have been provided above**. + / 我的更改经过了良好的测试,**并已在上方提供了“验证步骤”和“运行截图”**。 -- [ ] 🤓 我确保没有引入新依赖库,或者引入了新依赖库的同时将其添加到 `requirements.txt` 和 `pyproject.toml` 文件相应位置。 - / I have ensured that no new dependencies are introduced, OR if new dependencies are introduced, they have been added to the appropriate locations in `requirements.txt` and `pyproject.toml`. +- [ ] 🤓 I have ensured that no new dependencies are introduced, OR if new dependencies are introduced, they have been added to the appropriate locations in `requirements.txt` and `pyproject.toml`. + / 我确保没有引入新依赖库,或者引入了新依赖库的同时将其添加到 `requirements.txt` 和 `pyproject.toml` 文件相应位置。 -- [ ] 😮 我的更改没有引入恶意代码。 - / My changes do not introduce malicious code. - -- [ ] ⚠️ 我已认真阅读并理解以上所有内容,确保本次提交符合规范。 - / I have read and understood all the above and confirm this PR follows the rules. - -- [ ] 🚀 我确保本次开发**基于 dev 分支**,并将代码合并至**开发分支**(除非极其紧急,才允许合并到主分支)。 - / I confirm that this development is **based on the dev branch** and will be merged into the **development branch**, unless it is extremely urgent to merge into the main branch. - -- [ ] ⚠️ 我**没有**认真阅读以上内容,直接提交。 - / I **did not** read the above carefully before submitting. +- [ ] 😮 My changes do not introduce malicious code. + / 我的更改没有引入恶意代码。 diff --git a/.github/workflows/dashboard_ci.yml b/.github/workflows/dashboard_ci.yml index 7bfbf6361..b6cd9aa2c 100644 --- a/.github/workflows/dashboard_ci.yml +++ b/.github/workflows/dashboard_ci.yml @@ -45,7 +45,7 @@ jobs: - name: Create GitHub Release if: github.event_name == 'push' - uses: ncipollo/release-action@v1.20.0 + uses: ncipollo/release-action@v1.21.0 with: tag: release-${{ github.sha }} owner: AstrBotDevs diff --git a/.github/workflows/pr-checklist-check.yml b/.github/workflows/pr-checklist-check.yml deleted file mode 100644 index f93eac126..000000000 --- a/.github/workflows/pr-checklist-check.yml +++ /dev/null @@ -1,45 +0,0 @@ -name: PR Checklist Check - -on: - pull_request_target: - types: [opened, edited, reopened, synchronize] - -jobs: - check: - runs-on: ubuntu-latest - - permissions: - pull-requests: write - issues: write - - steps: - - name: Check checklist - id: check - uses: actions/github-script@v7 - with: - script: | - const body = context.payload.pull_request.body || ""; - const regex = /-\s*\[\s*x\s*\].*没有.*认真阅读/i; - const bad = regex.test(body); - core.setOutput("bad", bad); - - - name: Close PR - if: steps.check.outputs.bad == 'true' - uses: actions/github-script@v7 - with: - script: | - const pr = context.payload.pull_request; - - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: pr.number, - body: `检测到你勾选了“我没有认真阅读”,PR 已关闭。` - }); - - await github.rest.pulls.update({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: pr.number, - state: "closed" - }); \ No newline at end of file diff --git a/.github/workflows/pr-title-check.yml b/.github/workflows/pr-title-check.yml new file mode 100644 index 000000000..ff7e79e61 --- /dev/null +++ b/.github/workflows/pr-title-check.yml @@ -0,0 +1,53 @@ +name: PR Title Check + +on: + pull_request_target: + types: [opened, edited, reopened, synchronize] + +jobs: + title-format: + runs-on: ubuntu-latest + permissions: + pull-requests: write + issues: write + + steps: + - name: Validate PR title + uses: actions/github-script@v8 + with: + script: | + const title = (context.payload.pull_request.title || "").trim(); + // allow only: + // feat: xxx + // feat(scope): xxx + const pattern = /^(feat)(\([a-z0-9-]+\))?:\s.+$/i; + const isValid = pattern.test(title); + const isSameRepo = + context.payload.pull_request.head.repo.full_name === context.payload.repository.full_name; + + if (!isValid) { + if (isSameRepo) { + try { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.pull_request.number, + body: [ + "⚠️ PR title format check failed.", + "Required formats:", + "- `feat: xxx`", + "- `feat(scope): xxx`", + "Please update your PR title and push again." + ].join("\n") + }); + } catch (e) { + core.warning(`Failed to post PR title comment: ${e.message}`); + } + } else { + core.warning("Fork PR: comment permission is restricted; skip posting review comment."); + } + } + + if (!isValid) { + core.setFailed("Invalid PR title. Expected format: feat: xxx or feat(scope): xxx."); + } diff --git a/astrbot/core/agent/handoff.py b/astrbot/core/agent/handoff.py index 8475009d3..0363e2d55 100644 --- a/astrbot/core/agent/handoff.py +++ b/astrbot/core/agent/handoff.py @@ -62,4 +62,4 @@ class HandoffTool(FunctionTool, Generic[TContext]): def default_description(self, agent_name: str | None) -> str: agent_name = agent_name or "another" - return f"Delegate tasks to {self.name} agent to handle the request." + return f"Delegate tasks to {agent_name} agent to handle the request." diff --git a/astrbot/core/astr_main_agent.py b/astrbot/core/astr_main_agent.py index 87b1726d6..e89e67c8d 100644 --- a/astrbot/core/astr_main_agent.py +++ b/astrbot/core/astr_main_agent.py @@ -351,14 +351,9 @@ async def _ensure_persona_and_skills( persona_tools = None pid = a.get("persona_id") if pid: - persona_tools = next( - ( - p.get("tools") - for p in plugin_context.persona_manager.personas_v3 - if p["name"] == pid - ), - None, - ) + persona = plugin_context.persona_manager.get_persona_v3_by_id(pid) + if persona is not None: + persona_tools = persona.get("tools") tools = a.get("tools", []) if persona_tools is not None: tools = persona_tools diff --git a/astrbot/core/computer/computer_client.py b/astrbot/core/computer/computer_client.py index b0a94f2f4..90a142801 100644 --- a/astrbot/core/computer/computer_client.py +++ b/astrbot/core/computer/computer_client.py @@ -223,13 +223,24 @@ def parse_description(text: str) -> str: break if end_idx is None: return "" - for line in lines[1:end_idx]: - if ":" not in line: - continue - key, value = line.split(":", 1) - if key.strip().lower() == "description": - return value.strip().strip('"').strip("'") - return "" + + frontmatter = "\n".join(lines[1:end_idx]) + try: + import yaml + except ImportError: + return "" + + try: + payload = yaml.safe_load(frontmatter) or dict() + except yaml.YAMLError: + return "" + if not isinstance(payload, dict): + return "" + + description = payload.get("description", "") + if not isinstance(description, str): + return "" + return description.strip() def load_managed_skills() -> list[str]: diff --git a/astrbot/core/db/__init__.py b/astrbot/core/db/__init__.py index 608ecc710..a18c127eb 100644 --- a/astrbot/core/db/__init__.py +++ b/astrbot/core/db/__init__.py @@ -33,10 +33,18 @@ class BaseDatabase(abc.ABC): DATABASE_URL = "" def __init__(self) -> None: + # SQLite only supports a single writer at a time. Without a busy + # timeout the driver raises "database is locked" instantly when a + # second write is attempted. Setting timeout=30 tells SQLite to + # wait up to 30 s for the lock, which is enough to ride out brief + # write bursts from concurrent agent/metrics/session operations. + is_sqlite = "sqlite" in self.DATABASE_URL + connect_args = {"timeout": 30} if is_sqlite else {} self.engine = create_async_engine( self.DATABASE_URL, echo=False, future=True, + connect_args=connect_args, ) self.AsyncSessionLocal = async_sessionmaker( self.engine, diff --git a/astrbot/core/persona_mgr.py b/astrbot/core/persona_mgr.py index 63ec70c23..2a893c9c4 100644 --- a/astrbot/core/persona_mgr.py +++ b/astrbot/core/persona_mgr.py @@ -44,6 +44,22 @@ class PersonaManager: raise ValueError(f"Persona with ID {persona_id} does not exist.") return persona + def get_persona_v3_by_id(self, persona_id: str | None) -> Personality | None: + """Resolve a v3 persona object by id. + + - None/empty id returns None. + - "default" maps to in-memory DEFAULT_PERSONALITY. + - Otherwise search in personas_v3 by persona name. + """ + if not persona_id: + return None + if persona_id == "default": + return DEFAULT_PERSONALITY + return next( + (persona for persona in self.personas_v3 if persona["name"] == persona_id), + None, + ) + async def get_default_persona_v3( self, umo: str | MessageSession | None = None, @@ -54,12 +70,7 @@ class PersonaManager: "default_personality", "default", ) - if not default_persona_id or default_persona_id == "default": - return DEFAULT_PERSONALITY - try: - return next(p for p in self.personas_v3 if p["name"] == default_persona_id) - except Exception: - return DEFAULT_PERSONALITY + return self.get_persona_v3_by_id(default_persona_id) or DEFAULT_PERSONALITY async def resolve_selected_persona( self, diff --git a/astrbot/core/provider/sources/openrouter_source.py b/astrbot/core/provider/sources/openrouter_source.py index 2cb446cf3..e49d0c929 100644 --- a/astrbot/core/provider/sources/openrouter_source.py +++ b/astrbot/core/provider/sources/openrouter_source.py @@ -16,4 +16,7 @@ class ProviderOpenRouter(ProviderOpenAIOfficial): self.client._custom_headers["HTTP-Referer"] = ( # type: ignore "https://github.com/AstrBotDevs/AstrBot" ) - self.client._custom_headers["X-TITLE"] = "AstrBot" # type: ignore + self.client._custom_headers["X-OpenRouter-Title"] = "AstrBot" # type: ignore + self.client._custom_headers["X-OpenRouter-Categories"] = ( + "general-chat,personal-agent" # type: ignore + ) diff --git a/astrbot/core/skills/skill_manager.py b/astrbot/core/skills/skill_manager.py index 9bbdb5aee..1f5ed14bb 100644 --- a/astrbot/core/skills/skill_manager.py +++ b/astrbot/core/skills/skill_manager.py @@ -11,6 +11,8 @@ from dataclasses import dataclass from datetime import datetime, timezone from pathlib import Path, PurePosixPath +import yaml + from astrbot.core.utils.astrbot_path import ( get_astrbot_data_path, get_astrbot_skills_path, @@ -69,13 +71,19 @@ def _parse_frontmatter_description(text: str) -> str: break if end_idx is None: return "" - for line in lines[1:end_idx]: - if ":" not in line: - continue - key, value = line.split(":", 1) - if key.strip().lower() == "description": - return value.strip().strip('"').strip("'") - return "" + + frontmatter = "\n".join(lines[1:end_idx]) + try: + payload = yaml.safe_load(frontmatter) or {} + except yaml.YAMLError: + return "" + if not isinstance(payload, dict): + return "" + + description = payload.get("description", "") + if not isinstance(description, str): + return "" + return description.strip() # Regex for sanitizing paths used in prompt examples — only allow @@ -128,7 +136,7 @@ def _build_skill_read_command_example(path: str) -> str: return f"cat {path}" if _is_windows_prompt_path(path): command = "type" - path_arg = f'"{path}"' + path_arg = f'"{os.path.normpath(path)}"' else: command = "cat" path_arg = shlex.quote(path) diff --git a/astrbot/core/star/register/star_handler.py b/astrbot/core/star/register/star_handler.py index 735bd3852..1385b5056 100644 --- a/astrbot/core/star/register/star_handler.py +++ b/astrbot/core/star/register/star_handler.py @@ -2,7 +2,7 @@ from __future__ import annotations import re from collections.abc import AsyncGenerator, Awaitable, Callable -from typing import TYPE_CHECKING, Any +from typing import Any import docstring_parser @@ -15,9 +15,6 @@ from astrbot.core.message.message_event_result import MessageEventResult from astrbot.core.provider.func_tool_manager import PY_TO_JSON_TYPE, SUPPORTED_TYPES from astrbot.core.provider.register import llm_tools -if TYPE_CHECKING: - from astrbot.core.astr_agent_context import AstrAgentContext - from ..filter.command import CommandFilter from ..filter.command_group import CommandGroupFilter from ..filter.custom_filter import CustomFilterAnd, CustomFilterOr @@ -619,7 +616,7 @@ class RegisteringAgent: kwargs["registering_agent"] = self return register_llm_tool(*args, **kwargs) - def __init__(self, agent: Agent[AstrAgentContext]) -> None: + def __init__(self, agent: Agent[Any]) -> None: self._agent = agent @@ -627,7 +624,7 @@ def register_agent( name: str, instruction: str, tools: list[str | FunctionTool] | None = None, - run_hooks: BaseAgentRunHooks[AstrAgentContext] | None = None, + run_hooks: BaseAgentRunHooks[Any] | None = None, ): """注册一个 Agent @@ -641,12 +638,12 @@ def register_agent( tools_ = tools or [] def decorator(awaitable: Callable[..., Awaitable[Any]]): - AstrAgent = Agent[AstrAgentContext] + AstrAgent = Agent[Any] agent = AstrAgent( name=name, instructions=instruction, tools=tools_, - run_hooks=run_hooks or BaseAgentRunHooks[AstrAgentContext](), + run_hooks=run_hooks or BaseAgentRunHooks[Any](), ) handoff_tool = HandoffTool(agent=agent) handoff_tool.handler = awaitable diff --git a/astrbot/core/subagent_orchestrator.py b/astrbot/core/subagent_orchestrator.py index 205c554cb..c6c595dfc 100644 --- a/astrbot/core/subagent_orchestrator.py +++ b/astrbot/core/subagent_orchestrator.py @@ -1,13 +1,16 @@ from __future__ import annotations -from typing import Any +import copy +from typing import TYPE_CHECKING, Any from astrbot import logger from astrbot.core.agent.agent import Agent from astrbot.core.agent.handoff import HandoffTool -from astrbot.core.persona_mgr import PersonaManager from astrbot.core.provider.func_tool_manager import FunctionToolManager +if TYPE_CHECKING: + from astrbot.core.persona_mgr import PersonaManager + class SubAgentOrchestrator: """Loads subagent definitions from config and registers handoff tools. @@ -43,15 +46,14 @@ class SubAgentOrchestrator: continue persona_id = item.get("persona_id") - persona_data = None - if persona_id: - try: - persona_data = await self._persona_mgr.get_persona(persona_id) - except StopIteration: - logger.warning( - "SubAgent persona %s not found, fallback to inline prompt.", - persona_id, - ) + if persona_id is not None: + persona_id = str(persona_id).strip() or None + persona_data = self._persona_mgr.get_persona_v3_by_id(persona_id) + if persona_id and persona_data is None: + logger.warning( + "SubAgent persona %s not found, fallback to inline prompt.", + persona_id, + ) instructions = str(item.get("system_prompt", "")).strip() public_description = str(item.get("public_description", "")).strip() @@ -62,11 +64,15 @@ class SubAgentOrchestrator: begin_dialogs = None if persona_data: - instructions = persona_data.system_prompt or instructions - begin_dialogs = persona_data.begin_dialogs - tools = persona_data.tools - if public_description == "" and persona_data.system_prompt: - public_description = persona_data.system_prompt[:120] + prompt = str(persona_data.get("prompt", "")).strip() + if prompt: + instructions = prompt + begin_dialogs = copy.deepcopy( + persona_data.get("_begin_dialogs_processed") + ) + tools = persona_data.get("tools") + if public_description == "" and prompt: + public_description = prompt[:120] if tools is None: tools = None elif not isinstance(tools, list): diff --git a/changelogs/v4.20.1.md b/changelogs/v4.20.1.md new file mode 100644 index 000000000..6c2c8c251 --- /dev/null +++ b/changelogs/v4.20.1.md @@ -0,0 +1,93 @@ +## What's Changed + +### 新增 + +- 补充 MiniMax Provider。([#6318](https://github.com/AstrBotDevs/AstrBot/pull/6318)) +- 新增 WebUI ChatUI 页面的会话批量删除功能。([#6160](https://github.com/AstrBotDevs/AstrBot/pull/6160)) +- 新增 WebUI ChatUI 配置发送快捷键。([#6272](https://github.com/AstrBotDevs/AstrBot/pull/6272)) + +### 优化 + +- 优化 UMO 处理兼容性。([#5996](https://github.com/AstrBotDevs/AstrBot/pull/5996)) +- 重构 `_extract_session_id`,改进聊天类型分支处理。(#5775) +- 优化聊天组件行为,使用 `shiki` 进行代码块渲染。([#6286](https://github.com/AstrBotDevs/AstrBot/pull/6286)) +- 优化 WebUI 主题配色与视觉体验。([#6263](https://github.com/AstrBotDevs/AstrBot/pull/6263)) +- 优化 OneBot @ 组件后处理,避免消息文本解析空格问题。([#6238](https://github.com/AstrBotDevs/AstrBot/pull/6238)) + +### 修复 + +- 修复创建新 Provider 后未同步 `providers_config` 的问题。([#6388](https://github.com/AstrBotDevs/AstrBot/pull/6388)) +- 修复 API 返回 `null choices` 时的 `TypeError`。([#6313](https://github.com/AstrBotDevs/AstrBot/pull/6313)) +- 修复 QQ Webhook 重试回调重复触发的问题。([#6320](https://github.com/AstrBotDevs/AstrBot/pull/6320)) +- 修复流式模式下 `delta` 为 `None` 导致工具调用时报错的问题。([#6365](https://github.com/AstrBotDevs/AstrBot/pull/6365)) +- 修复模型服务链接说明文字错误。([#6296](https://github.com/AstrBotDevs/AstrBot/pull/6296)) +- 修复 AI 在 tool-calling 模式设为 `skills-like` 时发送媒体失败的问题。([#6317](https://github.com/AstrBotDevs/AstrBot/pull/6317)) +- 修复 Telegram 适配器中 GIF 被错误转成静态图的问题。([#6329](https://github.com/AstrBotDevs/AstrBot/pull/6329)) +- 将 Provider 图标来源替换为 jsDelivr CDN 地址,修复部分环境下图标加载问题。([#6340](https://github.com/AstrBotDevs/AstrBot/pull/6340)) +- 修复 QQ 官方表情消息未解析为可读文本的问题。([#6355](https://github.com/AstrBotDevs/AstrBot/pull/6355)) +- 修复 WebChat 队列异常时流式结果页面崩溃的问题。([#6123](https://github.com/AstrBotDevs/AstrBot/pull/6123)) +- 修复子代理 handoff 工具在插件过滤时丢失的问题。([#6155](https://github.com/AstrBotDevs/AstrBot/pull/6155)) +- 修复 Cron 提示文案缺少空格及 `utcnow()` 的弃用警告问题。([#6192](https://github.com/AstrBotDevs/AstrBot/pull/6192)) +- 修复 WebUI 启动时 Sidebar hash 导航抖动/定位问题。([#6159](https://github.com/AstrBotDevs/AstrBot/pull/6159)) +- 修复启动重试过程中移除已移除 API Key 的 `ValueError` 报错。([#6193](https://github.com/AstrBotDevs/AstrBot/pull/6193)) +- 修复 README 启动命令引用更新为 `astrbot run`。([#6189](https://github.com/AstrBotDevs/AstrBot/pull/6189)) +- 修复 `Plain.toDict()` 在 `@` 提及场景下空白字符丢失的问题。([#6244](https://github.com/AstrBotDevs/AstrBot/pull/6244)) +- 修复 provider 依赖重复定义问题。([#6247](https://github.com/AstrBotDevs/AstrBot/pull/6247)) +- 修复 Telegram 中普通回复被误判为线程的处理问题。([#6174](https://github.com/AstrBotDevs/AstrBot/pull/6174)) + +### 其他 + +- 调整 `astrbot.service` 及 CI 配置,升级 GitHub Actions 版本。 + +--- + +## What's Changed (EN) + +### New Features + +- Added OpenRouter chat completion provider adapter with support for custom headers ([#6436](https://github.com/AstrBotDevs/AstrBot/pull/6436)). +- Added MiniMax provider ([#6318](https://github.com/AstrBotDevs/AstrBot/pull/6318)). +- Added batch conversation deletion in WebChat ([#6160](https://github.com/AstrBotDevs/AstrBot/pull/6160)). +- Added send shortcut settings and localization support for WebChat input ([#6272](https://github.com/AstrBotDevs/AstrBot/pull/6272)). +- Added local temporary directory binding in YAML config ([#6191](https://github.com/AstrBotDevs/AstrBot/pull/6191)). + +### Improvements + +- Improved UMO processing compatibility ([#5996](https://github.com/AstrBotDevs/AstrBot/pull/5996)). +- Refactored `_extract_session_id` for chat type handling (#5775). +- Improved chat component behavior and uses `shiki` for code-block rendering ([#6286](https://github.com/AstrBotDevs/AstrBot/pull/6286)). +- Improved WebUI theme color and visual behavior ([#6263](https://github.com/AstrBotDevs/AstrBot/pull/6263)). +- Improved OneBot `@` component spacing handling ([#6238](https://github.com/AstrBotDevs/AstrBot/pull/6238)). +- Improved PR checklist validation and closure messaging. + +### Bug Fixes + +- Fixed missing `providers_config` sync after creating new providers ([#6388](https://github.com/AstrBotDevs/AstrBot/pull/6388)). +- Fixed `TypeError` when API returns null choices ([#6313](https://github.com/AstrBotDevs/AstrBot/pull/6313)). +- Fixed repeated QQ webhook retry callbacks ([#6320](https://github.com/AstrBotDevs/AstrBot/pull/6320)). +- Fixed tool-calling streaming null `delta` handling to prevent `AttributeError` ([#6365](https://github.com/AstrBotDevs/AstrBot/pull/6365)). +- Fixed model service link wording in docs/config ([#6296](https://github.com/AstrBotDevs/AstrBot/pull/6296)). +- Fixed AI media sending failure when tool-calling mode is set to `skills-like` ([#6317](https://github.com/AstrBotDevs/AstrBot/pull/6317)). +- Fixed GIF being sent as static image in Telegram adapter ([#6329](https://github.com/AstrBotDevs/AstrBot/pull/6329)). +- Replaced npm registry URLs with jsDelivr CDN for provider icons ([#6340](https://github.com/AstrBotDevs/AstrBot/pull/6340)). +- Fixed QQ official face message parsing to readable text ([#6355](https://github.com/AstrBotDevs/AstrBot/pull/6355)). +- Fixed WebChat stream-result crash on queue errors ([#6123](https://github.com/AstrBotDevs/AstrBot/pull/6123)). +- Preserved subagent handoff tools during plugin filtering ([#6155](https://github.com/AstrBotDevs/AstrBot/pull/6155)). +- Fixed cron prompt spacing and deprecated `utcnow()` usage ([#6192](https://github.com/AstrBotDevs/AstrBot/pull/6192)). +- Fixed unstable sidebar hash navigation on startup ([#6159](https://github.com/AstrBotDevs/AstrBot/pull/6159)). +- Fixed `ValueError` in retry loop when removing an already removed API key ([#6193](https://github.com/AstrBotDevs/AstrBot/pull/6193)). +- Updated startup command to `astrbot run` across READMEs ([#6189](https://github.com/AstrBotDevs/AstrBot/pull/6189)). +- Preserved whitespace in `Plain.toDict()` for @ mentions ([#6244](https://github.com/AstrBotDevs/AstrBot/pull/6244)). +- Removed duplicate dependencies entries ([#6247](https://github.com/AstrBotDevs/AstrBot/pull/6247)). +- Fixed Telegram normal reply being treated as topic thread ([#6174](https://github.com/AstrBotDevs/AstrBot/pull/6174)). + +### Documentation + +- Updated `rainyun` backup/access documentation ([#6427](https://github.com/AstrBotDevs/AstrBot/pull/6427)). +- Updated `package.md` and platform docs, including Matrix and Wecom AI bot documentation. +- Fixed Discord invite link in community docs. + +### Chores + +- Updated PR templates/checklist workflow, repository service config, and automated checks. +- Refreshed repository automation and formatting maintenance, and removed obsolete changelog scripts. diff --git a/compose.yml b/compose.yml index 99557a1d8..044d484cb 100644 --- a/compose.yml +++ b/compose.yml @@ -1,5 +1,3 @@ -version: '3.8' - # 当接入 QQ NapCat 时,请使用这个 compose 文件一键部署: https://github.com/NapNeko/NapCat-Docker/blob/main/compose/astrbot.yml services: diff --git a/dashboard/src/i18n/locales/en-US/features/config-metadata.json b/dashboard/src/i18n/locales/en-US/features/config-metadata.json index 5688b4e45..4cd2bea22 100644 --- a/dashboard/src/i18n/locales/en-US/features/config-metadata.json +++ b/dashboard/src/i18n/locales/en-US/features/config-metadata.json @@ -851,7 +851,7 @@ }, "interval_method": { "description": "Interval Method", - "hint": "random 为随机时间,log 为根据消息长度计算,$y=log_(x)$,x为字数,y的单位为秒。" + "hint": "random uses a random delay. log calculates delay by message length: $y=log_{log\\_base}(x)$, where x is word count and y is in seconds." }, "interval": { "description": "Random Interval Time", diff --git a/dashboard/src/i18n/locales/en-US/features/session-management.json b/dashboard/src/i18n/locales/en-US/features/session-management.json index fdd6b4f82..839dc248f 100644 --- a/dashboard/src/i18n/locales/en-US/features/session-management.json +++ b/dashboard/src/i18n/locales/en-US/features/session-management.json @@ -93,24 +93,6 @@ "batchDeleteConfirm": { "title": "Confirm Batch Delete", "message": "Are you sure you want to delete {count} selected rules? Global settings will be used after deletion." - }, - "batchOperations": { - "title": "Batch Operations", - "hint": "Quick batch modify session settings", - "scope": "Apply to", - "scopeSelected": "Selected sessions", - "scopeAll": "All sessions", - "scopeGroup": "All groups", - "scopePrivate": "All private chats", - "llmStatus": "LLM Status", - "ttsStatus": "TTS Status", - "chatProvider": "Chat Model", - "ttsProvider": "TTS Model", - "apply": "Apply Changes" - }, - "status": { - "enabled": "Enabled", - "disabled": "Disabled" }, "batchOperations": { "title": "Batch Operations", @@ -126,6 +108,25 @@ "ttsProvider": "TTS Model", "apply": "Apply Changes" }, + "groups": { + "title": "Group Management", + "count": "{count} groups", + "addToGroup": "Add to Group", + "create": "Create Group", + "edit": "Edit Group", + "name": "Group Name", + "sessionsCount": "{count} sessions", + "empty": "No groups yet. Click 'Create Group' to create one.", + "availableSessions": "Available Sessions ({count})", + "selectedSessions": "Selected Sessions ({count})", + "searchPlaceholder": "Search...", + "noMatch": "No matches", + "noMembers": "No members", + "customGroupDivider": "── Custom Groups ──", + "customGroupOption": "📁 {name} ({count})", + "groupOption": "{name} ({count} sessions)", + "deleteConfirm": "Are you sure you want to delete group \"{name}\"?" + }, "status": { "enabled": "Enabled", "disabled": "Disabled" @@ -142,7 +143,16 @@ "noChanges": "No changes to save", "batchDeleteSuccess": "Batch delete successful", "batchDeleteError": "Batch delete failed", + "selectSessionsFirst": "Please select sessions first", + "selectAtLeastOneConfig": "Please select at least one setting to modify", + "batchUpdateSuccess": "Batch update successful", + "partialUpdateFailed": "Some updates failed", "batchUpdateError": "Batch update failed", - "batchUpdateSuccess": "Batch update success" + "groupNameRequired": "Group name cannot be empty", + "saveGroupError": "Failed to save group", + "deleteGroupError": "Failed to delete group", + "selectSessionsToAddFirst": "Please select sessions to add first", + "addToGroupSuccess": "Added {count} sessions to the group", + "addToGroupError": "Failed to add to group" } } diff --git a/dashboard/src/i18n/locales/ru-RU/features/session-management.json b/dashboard/src/i18n/locales/ru-RU/features/session-management.json index 19c3a9aa5..f0ffa31bb 100644 --- a/dashboard/src/i18n/locales/ru-RU/features/session-management.json +++ b/dashboard/src/i18n/locales/ru-RU/features/session-management.json @@ -108,6 +108,25 @@ "ttsProvider": "TTS-модель", "apply": "Применить" }, + "groups": { + "title": "Управление группами", + "count": "групп: {count}", + "addToGroup": "Добавить в группу", + "create": "Создать группу", + "edit": "Изменить группу", + "name": "Имя группы", + "sessionsCount": "сессий: {count}", + "empty": "Пока нет групп. Нажмите «Создать группу», чтобы добавить.", + "availableSessions": "Доступные сессии ({count})", + "selectedSessions": "Выбранные сессии ({count})", + "searchPlaceholder": "Поиск...", + "noMatch": "Нет совпадений", + "noMembers": "Нет участников", + "customGroupDivider": "── Пользовательские группы ──", + "customGroupOption": "📁 {name} ({count})", + "groupOption": "{name} (сессий: {count})", + "deleteConfirm": "Вы уверены, что хотите удалить группу \"{name}\"?" + }, "status": { "enabled": "Включено", "disabled": "Выключено" @@ -124,7 +143,16 @@ "noChanges": "Изменений не обнаружено", "batchDeleteSuccess": "Массовое удаление выполнено", "batchDeleteError": "Ошибка массового удаления", + "selectSessionsFirst": "Пожалуйста, сначала выберите сессии", + "selectAtLeastOneConfig": "Пожалуйста, выберите хотя бы одну настройку для изменения", + "batchUpdateSuccess": "Пакетное обновление успешно выполнено", + "partialUpdateFailed": "Некоторые обновления не выполнены", "batchUpdateError": "Ошибка пакетного обновления", - "batchUpdateSuccess": "Пакетное обновление успешно выполнено" + "groupNameRequired": "Имя группы не может быть пустым", + "saveGroupError": "Ошибка сохранения группы", + "deleteGroupError": "Ошибка удаления группы", + "selectSessionsToAddFirst": "Пожалуйста, сначала выберите сессии для добавления", + "addToGroupSuccess": "Добавлено сессий в группу: {count}", + "addToGroupError": "Ошибка добавления в группу" } -} \ No newline at end of file +} diff --git a/dashboard/src/i18n/locales/zh-CN/features/session-management.json b/dashboard/src/i18n/locales/zh-CN/features/session-management.json index 33b387cd2..7fb1d0eb4 100644 --- a/dashboard/src/i18n/locales/zh-CN/features/session-management.json +++ b/dashboard/src/i18n/locales/zh-CN/features/session-management.json @@ -108,6 +108,25 @@ "ttsProvider": "TTS 模型", "apply": "应用更改" }, + "groups": { + "title": "分组管理", + "count": "{count} 个分组", + "addToGroup": "添加到分组", + "create": "新建分组", + "edit": "编辑分组", + "name": "分组名称", + "sessionsCount": "{count} 个会话", + "empty": "暂无分组,点击「新建分组」创建", + "availableSessions": "可选会话 ({count})", + "selectedSessions": "已选会话 ({count})", + "searchPlaceholder": "搜索...", + "noMatch": "无匹配项", + "noMembers": "暂无成员", + "customGroupDivider": "── 自定义分组 ──", + "customGroupOption": "📁 {name} ({count})", + "groupOption": "{name} ({count} 个会话)", + "deleteConfirm": "确定要删除分组 \"{name}\" 吗?" + }, "status": { "enabled": "启用", "disabled": "禁用" @@ -123,6 +142,17 @@ "deleteError": "删除失败", "noChanges": "没有需要保存的更改", "batchDeleteSuccess": "批量删除成功", - "batchDeleteError": "批量删除失败" + "batchDeleteError": "批量删除失败", + "selectSessionsFirst": "请先选择要操作的会话", + "selectAtLeastOneConfig": "请至少选择一项要修改的配置", + "batchUpdateSuccess": "批量更新成功", + "partialUpdateFailed": "部分更新失败", + "batchUpdateError": "批量更新失败", + "groupNameRequired": "分组名称不能为空", + "saveGroupError": "保存分组失败", + "deleteGroupError": "删除分组失败", + "selectSessionsToAddFirst": "请先选择要添加的会话", + "addToGroupSuccess": "已添加 {count} 个会话到分组", + "addToGroupError": "添加失败" } } diff --git a/dashboard/src/views/SessionManagementPage.vue b/dashboard/src/views/SessionManagementPage.vue index 5008e1dd3..0d195cf67 100644 --- a/dashboard/src/views/SessionManagementPage.vue +++ b/dashboard/src/views/SessionManagementPage.vue @@ -156,24 +156,24 @@ - 分组管理 + {{ tm('groups.title') }} - {{ groups.length }} 个分组 + {{ tm('groups.count', { count: groups.length }) }} mdi-folder-plus - 添加到分组 + {{ tm('groups.addToGroup') }} - {{ g.name }} ({{ g.umo_count }}) + {{ tm('groups.customGroupOption', { name: g.name, count: g.umo_count }) }} - 新建分组 + {{ tm('groups.create') }} @@ -183,7 +183,7 @@
{{ group.name }}
-
{{ group.umo_count }} 个会话
+
{{ tm('groups.sessionsCount', { count: group.umo_count }) }}
@@ -199,7 +199,7 @@ - 暂无分组,点击「新建分组」创建 + {{ tm('groups.empty') }} @@ -207,15 +207,15 @@ - {{ groupDialogMode === 'create' ? '新建分组' : '编辑分组' }} + {{ groupDialogMode === 'create' ? tm('groups.create') : tm('groups.edit') }} - + -
可选会话 ({{ unselectedUmos.length }})
- +
{{ tm('groups.availableSessions', { count: unselectedUmos.length }) }}
+