From 3914d766db5d8bb873e744b57d3750a8598dda8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=A8=E3=82=A4=E3=82=AB=E3=82=AF?= <62183434+zouyonghe@users.noreply.github.com> Date: Thu, 12 Mar 2026 11:50:29 +0900 Subject: [PATCH 01/62] fix: install only missing plugin dependencies (#6088) * chore: ignore local worktrees * fix: install only missing plugin dependencies * fix: harden missing dependency install fallback * fix: clarify dependency install fallback logging * refactor: simplify dependency install test helpers * refactor: reuse requirements precheck planning --- astrbot/core/star/star_manager.py | 68 +++++- astrbot/core/utils/requirements_utils.py | 84 ++++++- tests/test_pip_helper_modules.py | 168 +++++++++++++- tests/test_plugin_manager.py | 265 ++++++++++++++++++++--- 4 files changed, 542 insertions(+), 43 deletions(-) diff --git a/astrbot/core/star/star_manager.py b/astrbot/core/star/star_manager.py index cf000c5a4..57be1e9a9 100644 --- a/astrbot/core/star/star_manager.py +++ b/astrbot/core/star/star_manager.py @@ -1,12 +1,14 @@ """插件的重载、启停、安装、卸载等操作。""" import asyncio +import contextlib import functools import inspect import json import logging import os import sys +import tempfile import traceback from types import ModuleType @@ -29,12 +31,12 @@ from astrbot.core.utils.astrbot_path import ( get_astrbot_config_path, get_astrbot_path, get_astrbot_plugin_path, + get_astrbot_temp_path, ) from astrbot.core.utils.io import remove_dir from astrbot.core.utils.metrics import Metric from astrbot.core.utils.requirements_utils import ( - RequirementsPrecheckFailed, - find_missing_requirements_or_raise, + plan_missing_requirements_install, ) from . import StarMetadata @@ -74,30 +76,78 @@ class PluginDependencyInstallError(Exception): self.error = error +@contextlib.contextmanager +def _temporary_filtered_requirements_file( + *, + install_lines: tuple[str, ...], +): + filtered_requirements_path: str | None = None + temp_dir = get_astrbot_temp_path() + + try: + os.makedirs(temp_dir, exist_ok=True) + with tempfile.NamedTemporaryFile( + mode="w", + suffix="_plugin_requirements.txt", + delete=False, + dir=temp_dir, + encoding="utf-8", + ) as filtered_requirements_file: + filtered_requirements_file.write("\n".join(install_lines) + "\n") + filtered_requirements_path = filtered_requirements_file.name + + yield filtered_requirements_path + finally: + if filtered_requirements_path and os.path.exists(filtered_requirements_path): + try: + os.remove(filtered_requirements_path) + except OSError as exc: + logger.warning( + "删除临时插件依赖文件失败:%s(路径:%s)", + exc, + filtered_requirements_path, + ) + + async def _install_requirements_with_precheck( *, plugin_label: str, requirements_path: str, ) -> None: - try: - missing = find_missing_requirements_or_raise(requirements_path) - except RequirementsPrecheckFailed: + install_plan = plan_missing_requirements_install(requirements_path) + + if install_plan is None: logger.info( - f"正在安装插件 {plugin_label} 的依赖库(预检查失败,回退到完整安装): " + f"正在安装插件 {plugin_label} 的依赖库(缺失依赖预检查不可裁剪,回退到完整安装): " f"{requirements_path}" ) await pip_installer.install(requirements_path=requirements_path) return - if not missing: + if not install_plan.missing_names: logger.info(f"插件 {plugin_label} 的依赖已满足,跳过安装。") return + if not install_plan.install_lines: + fallback_reason = install_plan.fallback_reason or "unknown reason" + logger.info( + "检测到插件 %s 缺失依赖,但无法安全裁剪 requirements,回退到完整安装: %s (%s)", + plugin_label, + requirements_path, + fallback_reason, + ) + await pip_installer.install(requirements_path=requirements_path) + return + logger.info( f"检测到插件 {plugin_label} 缺失依赖,正在按 requirements.txt 安装: " - f"{requirements_path} -> {sorted(missing)}" + f"{requirements_path} -> {sorted(install_plan.missing_names)}" ) - await pip_installer.install(requirements_path=requirements_path) + + with _temporary_filtered_requirements_file( + install_lines=install_plan.install_lines, + ) as filtered_requirements_path: + await pip_installer.install(requirements_path=filtered_requirements_path) class PluginManager: diff --git a/astrbot/core/utils/requirements_utils.py b/astrbot/core/utils/requirements_utils.py index 7f3827256..e031de846 100644 --- a/astrbot/core/utils/requirements_utils.py +++ b/astrbot/core/utils/requirements_utils.py @@ -4,7 +4,7 @@ import os import re import shlex import sys -from collections.abc import Iterable, Iterator +from collections.abc import Iterable, Iterator, Sequence from dataclasses import dataclass from packaging.requirements import InvalidRequirement, Requirement @@ -29,6 +29,13 @@ class ParsedPackageInput: requirement_names: frozenset[str] +@dataclass(frozen=True) +class MissingRequirementsPlan: + missing_names: frozenset[str] + install_lines: tuple[str, ...] + fallback_reason: str | None = None + + def canonicalize_distribution_name(name: str) -> str: return re.sub(r"[-_.]+", "-", name).strip("-").lower() @@ -364,8 +371,8 @@ def _load_requirement_lines_for_precheck( None, ) if fallback_line is not None: - logger.warning( - "预检查缺失依赖失败,将回退到完整安装: unresolved direct reference in %s: %s", + logger.info( + "缺失依赖预检查发现无法安全裁剪的 option/direct-reference 行,将回退到完整安装: %s (%s)", requirements_path, fallback_line, ) @@ -381,6 +388,13 @@ def find_missing_requirements(requirements_path: str) -> set[str] | None: if not can_precheck or requirement_lines is None: return None + return find_missing_requirements_from_lines(requirement_lines) + + +def find_missing_requirements_from_lines( + requirement_lines: Sequence[str], +) -> set[str] | None: + required = list(iter_requirements(lines=requirement_lines)) if not required: return set() @@ -401,6 +415,70 @@ def find_missing_requirements(requirements_path: str) -> set[str] | None: return missing +def build_missing_requirements_install_lines( + requirements_path: str, + requirement_lines: Sequence[str], + missing_names: set[str] | frozenset[str], +) -> tuple[str, ...] | None: + wanted_names = set(missing_names) + install_lines: list[str] = [] + for line in requirement_lines: + parsed = _parse_requirement_line(line) + if parsed is None: + if looks_like_direct_reference(line) or line.startswith(("-", "--")): + logger.debug( + "缺失依赖行筛选回退到完整安装:requirements 中包含无法安全裁剪的 option/direct-reference 行: %s (%s)", + requirements_path, + line, + ) + return None + continue + + name, _specifier = parsed + if name in wanted_names: + install_lines.append(line) + + return tuple(install_lines) + + +def plan_missing_requirements_install( + requirements_path: str, +) -> MissingRequirementsPlan | None: + can_precheck, requirement_lines = _load_requirement_lines_for_precheck( + requirements_path + ) + if not can_precheck or requirement_lines is None: + return None + + missing = find_missing_requirements_from_lines(requirement_lines) + if missing is None: + return None + + install_lines = build_missing_requirements_install_lines( + requirements_path, + requirement_lines, + missing, + ) + if install_lines is None: + return None + if missing and not install_lines: + logger.warning( + "预检查缺失依赖成功,但无法映射到可安装 requirement 行,将回退到完整安装: %s -> %s", + requirements_path, + sorted(missing), + ) + return MissingRequirementsPlan( + missing_names=frozenset(missing), + install_lines=(), + fallback_reason="unmapped missing requirement names", + ) + + return MissingRequirementsPlan( + missing_names=frozenset(missing), + install_lines=install_lines, + ) + + def find_missing_requirements_or_raise(requirements_path: str) -> set[str]: missing = find_missing_requirements(requirements_path) if missing is None: diff --git a/tests/test_pip_helper_modules.py b/tests/test_pip_helper_modules.py index dcb5cdb21..506dd0945 100644 --- a/tests/test_pip_helper_modules.py +++ b/tests/test_pip_helper_modules.py @@ -145,24 +145,182 @@ def test_find_missing_requirements_or_raise_uses_requirements_exception(tmp_path requirements_utils.find_missing_requirements_or_raise(str(requirements_path)) +def test_build_missing_requirements_install_lines_keeps_only_missing_lines(tmp_path): + requirements_path = tmp_path / "requirements.txt" + requirements_path.write_text( + 'aiohttp>=3.0\nboto3==1.2; python_version >= "3.0"\nbotocore\n', + encoding="utf-8", + ) + + install_lines = requirements_utils.build_missing_requirements_install_lines( + str(requirements_path), + [ + "aiohttp>=3.0", + 'boto3==1.2; python_version >= "3.0"', + "botocore", + ], + {"boto3", "botocore"}, + ) + + assert install_lines == ( + 'boto3==1.2; python_version >= "3.0"', + "botocore", + ) + + +def test_build_missing_requirements_install_lines_returns_empty_tuple_when_all_satisfied( + tmp_path, +): + requirements_path = tmp_path / "requirements.txt" + requirements_path.write_text("aiohttp>=3.0\nboto3\n", encoding="utf-8") + + install_lines = requirements_utils.build_missing_requirements_install_lines( + str(requirements_path), ["aiohttp>=3.0", "boto3"], set() + ) + + assert install_lines == () + + +def test_build_missing_requirements_install_lines_returns_none_for_option_lines( + tmp_path, +): + requirements_path = tmp_path / "requirements.txt" + requirements_path.write_text( + "--extra-index-url https://example.com/simple\nboto3\n", + encoding="utf-8", + ) + + install_lines = requirements_utils.build_missing_requirements_install_lines( + str(requirements_path), + ["--extra-index-url https://example.com/simple", "boto3"], + {"boto3"}, + ) + + assert install_lines is None + + +def test_build_missing_requirements_install_lines_skips_inactive_marker_lines( + tmp_path, +): + requirements_path = tmp_path / "requirements.txt" + requirements_path.write_text( + 'boto3\nother-package; sys_platform == "win32"\n', + encoding="utf-8", + ) + + install_lines = requirements_utils.build_missing_requirements_install_lines( + str(requirements_path), + ["boto3", 'other-package; sys_platform == "win32"'], + {"boto3"}, + ) + + assert install_lines == ("boto3",) + + +def test_plan_missing_requirements_install_returns_none_when_missing_names_cannot_map_to_lines( + monkeypatch, + tmp_path, +): + requirements_path = tmp_path / "requirements.txt" + requirements_path.write_text("boto3\n", encoding="utf-8") + + monkeypatch.setattr( + requirements_utils, + "find_missing_requirements_from_lines", + lambda lines: {"botocore"}, + ) + + plan = requirements_utils.plan_missing_requirements_install(str(requirements_path)) + + assert plan is not None + assert plan.missing_names == frozenset({"botocore"}) + assert plan.install_lines == () + assert plan.fallback_reason == "unmapped missing requirement names" + + +def test_plan_missing_requirements_install_loads_requirement_lines_once( + monkeypatch, + tmp_path, +): + requirements_path = tmp_path / "requirements.txt" + requirements_path.write_text("boto3\nbotocore\n", encoding="utf-8") + calls = [] + + def mock_load(path): + calls.append(path) + return True, ["boto3", "botocore"] + + monkeypatch.setattr( + requirements_utils, + "_load_requirement_lines_for_precheck", + mock_load, + ) + monkeypatch.setattr( + requirements_utils, + "collect_installed_distribution_versions", + lambda paths: {}, + ) + monkeypatch.setattr( + requirements_utils, + "get_requirement_check_paths", + lambda: ["/tmp/site-packages"], + ) + + plan = requirements_utils.plan_missing_requirements_install(str(requirements_path)) + + assert plan is not None + assert plan.missing_names == frozenset({"boto3", "botocore"}) + assert plan.install_lines == ("boto3", "botocore") + assert calls == [str(requirements_path)] + + +def test_build_missing_requirements_install_lines_logs_why_option_lines_fall_back( + monkeypatch, + tmp_path, +): + requirements_path = tmp_path / "requirements.txt" + requirements_path.write_text( + "--extra-index-url https://example.com/simple\nboto3\n", + encoding="utf-8", + ) + + debug_logs = [] + + monkeypatch.setattr( + "astrbot.core.utils.requirements_utils.logger.debug", + lambda line, *args: debug_logs.append(line % args if args else line), + ) + + install_lines = requirements_utils.build_missing_requirements_install_lines( + str(requirements_path), + ["--extra-index-url https://example.com/simple", "boto3"], + {"boto3"}, + ) + + assert install_lines is None + assert any(str(requirements_path) in log for log in debug_logs) + assert any("option/direct-reference" in log for log in debug_logs) + + def test_find_missing_requirements_logs_path_and_reason_on_precheck_fallback( monkeypatch, tmp_path, ): requirements_path = tmp_path / "requirements.txt" requirements_path.write_text("git+https://example.com/demo.git\n", encoding="utf-8") - warning_logs = [] + + info_logs = [] monkeypatch.setattr( - "astrbot.core.utils.requirements_utils.logger.warning", - lambda line, *args: warning_logs.append(line % args if args else line), + "astrbot.core.utils.requirements_utils.logger.info", + lambda line, *args: info_logs.append(line % args if args else line), ) missing = requirements_utils.find_missing_requirements(str(requirements_path)) assert missing is None - assert any(str(requirements_path) in log for log in warning_logs) - assert any("direct reference" in log for log in warning_logs) + assert any(str(requirements_path) in log for log in info_logs) + assert any("option/direct-reference" in log for log in info_logs) def test_load_requirement_lines_for_precheck_uses_parse_requirement_line_result( diff --git a/tests/test_plugin_manager.py b/tests/test_plugin_manager.py index 1b52990a5..b1dafc87e 100644 --- a/tests/test_plugin_manager.py +++ b/tests/test_plugin_manager.py @@ -1,4 +1,5 @@ import asyncio +import os from pathlib import Path import pytest @@ -6,6 +7,7 @@ import yaml from astrbot.core.star.star_manager import PluginDependencyInstallError, PluginManager from astrbot.core.utils.pip_installer import PipInstallError +from astrbot.core.utils.requirements_utils import MissingRequirementsPlan # --- Test Data & Helpers --- @@ -74,13 +76,25 @@ def _build_reload_mock(events): return mock_reload -def _build_dependency_install_mock(events, fail: bool): +def _build_dependency_install_mock( + events, + fail: bool, + *, + capture_content: bool = False, +): async def mock_install_requirements( - *, requirements_path: str = None, package_name: str = None, **kwargs + *, + requirements_path: str | None = None, + package_name: str | None = None, + **kwargs, ): del kwargs if requirements_path: - events.append(("deps", str(requirements_path))) + path = Path(requirements_path) + event = ("deps", str(path)) + if capture_content: + event = (*event, path.read_text(encoding="utf-8")) + events.append(event) if package_name: events.append(("deps_pkg", package_name)) if fail: @@ -90,24 +104,56 @@ def _build_dependency_install_mock(events, fail: bool): def _mock_missing_requirements(monkeypatch, missing: set[str]): + _mock_missing_requirements_plan(monkeypatch, missing, sorted(missing)) + + +def _mock_missing_requirements_plan( + monkeypatch, + missing_names, + install_lines, + *, + fallback_reason: str | None = None, +): monkeypatch.setattr( - "astrbot.core.star.star_manager.find_missing_requirements_or_raise", - lambda requirements_path: missing, + "astrbot.core.star.star_manager.plan_missing_requirements_install", + lambda requirements_path: MissingRequirementsPlan( + missing_names=frozenset(missing_names), + install_lines=tuple(install_lines), + fallback_reason=fallback_reason, + ), ) def _mock_precheck_fails(monkeypatch): - from astrbot.core import RequirementsPrecheckFailed - - def mock_fail(requirements_path): - raise RequirementsPrecheckFailed("mock precheck failure") - monkeypatch.setattr( - "astrbot.core.star.star_manager.find_missing_requirements_or_raise", - mock_fail, + "astrbot.core.star.star_manager.plan_missing_requirements_install", + lambda requirements_path: None, ) +def _assert_dependency_install_event_matches( + event, + *, + expected_original_path: Path, + expected_content: str | None = None, + expect_filtered_tempfile: bool | None = None, +): + assert event[0] == "deps" + used_path = Path(event[1]) + should_be_filtered = expected_content is not None + if expect_filtered_tempfile is not None: + should_be_filtered = expect_filtered_tempfile + + if not should_be_filtered: + assert used_path == expected_original_path + else: + assert used_path != expected_original_path + assert used_path.name.endswith("_plugin_requirements.txt") + if expected_content is not None: + if len(event) >= 3: + assert event[2] == expected_content + + # --- Fixtures --- @@ -188,13 +234,21 @@ async def test_install_plugin_dependency_install_flow( if dependency_install_fails: with pytest.raises(PluginDependencyInstallError, match="pip failed"): await plugin_manager_pm.install_plugin(TEST_PLUGIN_REPO) - assert events == [("deps", str(plugin_path / "requirements.txt"))] + assert len(events) == 1 + _assert_dependency_install_event_matches( + events[0], + expected_original_path=plugin_path / "requirements.txt", + expected_content="networkx\n", + ) else: await plugin_manager_pm.install_plugin(TEST_PLUGIN_REPO) - assert events == [ - ("deps", str(plugin_path / "requirements.txt")), - ("load", TEST_PLUGIN_DIR), - ] + assert len(events) == 2 + _assert_dependency_install_event_matches( + events[0], + expected_original_path=plugin_path / "requirements.txt", + expected_content="networkx\n", + ) + assert events[1] == ("load", TEST_PLUGIN_DIR) @pytest.mark.asyncio @@ -265,13 +319,21 @@ async def test_reload_failed_plugin_dependency_install_flow( if dependency_install_fails: with pytest.raises(PluginDependencyInstallError, match="pip failed"): await plugin_manager_pm.reload_failed_plugin(TEST_PLUGIN_DIR) - assert events == [("deps", str(local_updator / "requirements.txt"))] + assert len(events) == 1 + _assert_dependency_install_event_matches( + events[0], + expected_original_path=local_updator / "requirements.txt", + expected_content="networkx\n", + ) else: await plugin_manager_pm.reload_failed_plugin(TEST_PLUGIN_DIR) - assert events == [ - ("deps", str(local_updator / "requirements.txt")), - ("load", TEST_PLUGIN_DIR), - ] + assert len(events) == 2 + _assert_dependency_install_event_matches( + events[0], + expected_original_path=local_updator / "requirements.txt", + expected_content="networkx\n", + ) + assert events[1] == ("load", TEST_PLUGIN_DIR) @pytest.mark.asyncio @@ -337,7 +399,9 @@ async def test_ensure_plugin_requirements_wraps_pip_install_error( mock_install_requirements, ) - with pytest.raises(PluginDependencyInstallError, match="install failed") as exc_info: + with pytest.raises( + PluginDependencyInstallError, match="install failed" + ) as exc_info: await plugin_manager_pm._ensure_plugin_requirements( str(local_updator), TEST_PLUGIN_DIR, @@ -403,10 +467,20 @@ async def test_update_plugin_dependency_install_flow( if dependency_install_fails: with pytest.raises(PluginDependencyInstallError, match="pip failed"): await plugin_manager_pm.update_plugin(TEST_PLUGIN_NAME) - assert ("deps", str(local_updator / "requirements.txt")) in events + dep_event = next(event for event in events if event[0] == "deps") + _assert_dependency_install_event_matches( + dep_event, + expected_original_path=local_updator / "requirements.txt", + expected_content="networkx\n", + ) else: await plugin_manager_pm.update_plugin(TEST_PLUGIN_NAME) - assert ("deps", str(local_updator / "requirements.txt")) in events + dep_event = next(event for event in events if event[0] == "deps") + _assert_dependency_install_event_matches( + dep_event, + expected_original_path=local_updator / "requirements.txt", + expected_content="networkx\n", + ) assert ("reload", TEST_PLUGIN_DIR) in events @@ -468,5 +542,144 @@ async def test_install_plugin_runs_dependency_install_when_precheck_fails( await plugin_manager_pm.install_plugin(TEST_PLUGIN_REPO) - assert ("deps", str(plugin_path / "requirements.txt")) in events + dep_event = next(event for event in events if event[0] == "deps") + _assert_dependency_install_event_matches( + dep_event, + expected_original_path=plugin_path / "requirements.txt", + ) assert ("load", TEST_PLUGIN_DIR) in events + + +@pytest.mark.asyncio +async def test_ensure_plugin_requirements_installs_only_missing_requirement_lines( + plugin_manager_pm: PluginManager, local_updator: Path, monkeypatch +): + requirements_path = local_updator / "requirements.txt" + requirements_path.write_text( + "aiohttp>=3.0\nboto3==1.2\nbotocore\n", + encoding="utf-8", + ) + events = [] + _mock_missing_requirements_plan( + monkeypatch, {"boto3", "botocore"}, ["boto3==1.2", "botocore"] + ) + + monkeypatch.setattr( + "astrbot.core.star.star_manager.pip_installer.install", + _build_dependency_install_mock(events, False, capture_content=True), + ) + + await plugin_manager_pm._ensure_plugin_requirements( + str(local_updator), + TEST_PLUGIN_DIR, + ) + + assert len(events) == 1 + kind, used_path, content = events[0] + assert kind == "deps" + assert used_path != str(requirements_path) + assert content == "boto3==1.2\nbotocore\n" + assert not Path(used_path).exists() + + +@pytest.mark.asyncio +async def test_ensure_plugin_requirements_creates_temp_dir_before_filtered_install( + plugin_manager_pm: PluginManager, local_updator: Path, monkeypatch, tmp_path +): + requirements_path = local_updator / "requirements.txt" + requirements_path.write_text("boto3\n", encoding="utf-8") + temp_dir = tmp_path / "missing-temp-dir" + events = [] + _mock_missing_requirements_plan(monkeypatch, {"boto3"}, ["boto3"]) + + monkeypatch.setattr( + "astrbot.core.star.star_manager.get_astrbot_temp_path", + lambda: str(temp_dir), + ) + monkeypatch.setattr( + "astrbot.core.star.star_manager.pip_installer.install", + _build_dependency_install_mock(events, False, capture_content=True), + ) + + await plugin_manager_pm._ensure_plugin_requirements( + str(local_updator), + TEST_PLUGIN_DIR, + ) + + assert temp_dir.is_dir() + assert len(events) == 1 + + +@pytest.mark.asyncio +async def test_ensure_plugin_requirements_falls_back_when_missing_names_have_no_install_lines( + plugin_manager_pm: PluginManager, local_updator: Path, monkeypatch +): + requirements_path = local_updator / "requirements.txt" + requirements_path.write_text("boto3\n", encoding="utf-8") + events = [] + + monkeypatch.setattr( + "astrbot.core.star.star_manager.plan_missing_requirements_install", + lambda path: MissingRequirementsPlan( + missing_names=frozenset({"botocore"}), + install_lines=(), + fallback_reason="unmapped missing requirement names", + ), + ) + monkeypatch.setattr( + "astrbot.core.star.star_manager.pip_installer.install", + _build_dependency_install_mock(events, False), + ) + + await plugin_manager_pm._ensure_plugin_requirements( + str(local_updator), + TEST_PLUGIN_DIR, + ) + + assert events == [("deps", str(requirements_path))] + + +@pytest.mark.asyncio +async def test_ensure_plugin_requirements_does_not_mask_install_error_when_cleanup_fails( + plugin_manager_pm: PluginManager, local_updator: Path, monkeypatch, tmp_path +): + requirements_path = local_updator / "requirements.txt" + requirements_path.write_text("boto3\n", encoding="utf-8") + temp_dir = tmp_path / "cleanup-fails" + _mock_missing_requirements_plan(monkeypatch, {"boto3"}, ["boto3"]) + warning_logs = [] + + async def mock_install_requirements( + *, requirements_path: str | None = None, **kwargs + ): + del kwargs, requirements_path + raise RuntimeError("pip failed") + + original_remove = os.remove + + def flaky_remove(path): + if str(path).endswith("_plugin_requirements.txt"): + raise OSError("cleanup failed") + return original_remove(path) + + monkeypatch.setattr( + "astrbot.core.star.star_manager.get_astrbot_temp_path", + lambda: str(temp_dir), + ) + monkeypatch.setattr( + "astrbot.core.star.star_manager.pip_installer.install", + mock_install_requirements, + ) + monkeypatch.setattr("astrbot.core.star.star_manager.os.remove", flaky_remove) + monkeypatch.setattr( + "astrbot.core.star.star_manager.logger.warning", + lambda line, *args: warning_logs.append(line % args if args else line), + ) + + with pytest.raises(PluginDependencyInstallError, match="pip failed"): + await plugin_manager_pm._ensure_plugin_requirements( + str(local_updator), + TEST_PLUGIN_DIR, + ) + + assert any("删除临时插件依赖文件失败" in log for log in warning_logs) From fbcbde0a4b205d07f0789fb323797979f4c3b01b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=A8=E3=82=A4=E3=82=AB=E3=82=AF?= <62183434+zouyonghe@users.noreply.github.com> Date: Thu, 12 Mar 2026 20:18:23 +0900 Subject: [PATCH 02/62] chore: update dependency and workflow versions (#6119) --- .github/workflows/build-docs.yml | 6 +- .github/workflows/dashboard_ci.yml | 2 +- .github/workflows/docker-image.yml | 20 +- .github/workflows/release.yml | 2 +- dashboard/package.json | 20 +- dashboard/pnpm-lock.yaml | 864 ++++++++++++++------- k8s/astrbot_with_napcat/02-deployment.yaml | 4 +- 7 files changed, 627 insertions(+), 291 deletions(-) diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml index 246469680..f0c25a6c8 100644 --- a/.github/workflows/build-docs.yml +++ b/.github/workflows/build-docs.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest # 运行环境 steps: - name: checkout - uses: actions/checkout@master + uses: actions/checkout@v6 - name: nodejs installation uses: actions/setup-node@v6 with: @@ -23,7 +23,7 @@ jobs: run: npm run docs:build working-directory: './docs' - name: scp - uses: appleboy/scp-action@master + uses: appleboy/scp-action@v1.0.0 with: host: ${{ secrets.HOST_NEKO }} username: ${{ secrets.USERNAME }} @@ -31,7 +31,7 @@ jobs: source: 'docs/.vitepress/dist/*' target: '/tmp/' - name: script - uses: appleboy/ssh-action@master + uses: appleboy/ssh-action@v1.2.5 with: host: ${{ secrets.HOST_NEKO }} username: ${{ secrets.USERNAME }} diff --git a/.github/workflows/dashboard_ci.yml b/.github/workflows/dashboard_ci.yml index 46d2fea73..7bfbf6361 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 + uses: ncipollo/release-action@v1.20.0 with: tag: release-${{ github.sha }} owner: AstrBotDevs diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index d79d628c3..ccf560435 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -64,20 +64,20 @@ jobs: echo "build_date=$build_date" >> $GITHUB_OUTPUT - name: Set QEMU - uses: docker/setup-qemu-action@v4 + uses: docker/setup-qemu-action@v4.0.0 - name: Set Docker Buildx - uses: docker/setup-buildx-action@v4 + uses: docker/setup-buildx-action@v4.0.0 - name: Log in to DockerHub - uses: docker/login-action@v4 + uses: docker/login-action@v4.0.0 with: username: ${{ secrets.DOCKER_HUB_USERNAME }} password: ${{ secrets.DOCKER_HUB_PASSWORD }} - name: Login to GitHub Container Registry if: env.HAS_GHCR_TOKEN == 'true' - uses: docker/login-action@v4 + uses: docker/login-action@v4.0.0 with: registry: ghcr.io username: ${{ env.GHCR_OWNER }} @@ -98,7 +98,7 @@ jobs: echo "EOF" >> $GITHUB_OUTPUT - name: Build and Push Nightly Image - uses: docker/build-push-action@v7 + uses: docker/build-push-action@v7.0.0 with: context: . platforms: linux/amd64,linux/arm64 @@ -163,27 +163,27 @@ jobs: cp -r dashboard/dist data/ - name: Set QEMU - uses: docker/setup-qemu-action@v4 + uses: docker/setup-qemu-action@v4.0.0 - name: Set Docker Buildx - uses: docker/setup-buildx-action@v4 + uses: docker/setup-buildx-action@v4.0.0 - name: Log in to DockerHub - uses: docker/login-action@v4 + uses: docker/login-action@v4.0.0 with: username: ${{ secrets.DOCKER_HUB_USERNAME }} password: ${{ secrets.DOCKER_HUB_PASSWORD }} - name: Login to GitHub Container Registry if: env.HAS_GHCR_TOKEN == 'true' - uses: docker/login-action@v4 + uses: docker/login-action@v4.0.0 with: registry: ghcr.io username: ${{ env.GHCR_OWNER }} password: ${{ secrets.GHCR_GITHUB_TOKEN }} - name: Build and Push Release Image - uses: docker/build-push-action@v7 + uses: docker/build-push-action@v7.0.0 with: context: . platforms: linux/amd64,linux/arm64 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 41f59f0a6..68da87dfd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -50,7 +50,7 @@ jobs: echo "tag=$tag" >> "$GITHUB_OUTPUT" - name: Setup pnpm - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@v4.3.0 with: version: 10.28.2 diff --git a/dashboard/package.json b/dashboard/package.json index 7b4a7f071..56f1d8731 100644 --- a/dashboard/package.json +++ b/dashboard/package.json @@ -17,17 +17,17 @@ "@tiptap/starter-kit": "2.1.7", "@tiptap/vue-3": "2.1.7", "apexcharts": "3.42.0", - "axios": ">=1.6.2 <1.10.0 || >1.10.0 <2.0.0", + "axios": "1.13.5", "axios-mock-adapter": "^1.22.0", "chance": "1.1.11", "date-fns": "2.30.0", - "dompurify": "^3.3.1", + "dompurify": "^3.3.2", "event-source-polyfill": "^1.0.31", "highlight.js": "^11.11.1", "js-md5": "^0.8.3", "katex": "^0.16.27", - "lodash": "4.17.21", - "markdown-it": "^14.1.0", + "lodash": "4.17.23", + "markdown-it": "^14.1.1", "markstream-vue": "^0.0.6", "mermaid": "^11.12.2", "monaco-editor": "^0.52.2", @@ -38,7 +38,7 @@ "stream-markdown": "^0.0.13", "stream-monaco": "^0.0.17", "vee-validate": "4.11.3", - "vite-plugin-vuetify": "1.0.2", + "vite-plugin-vuetify": "2.1.3", "vue": "3.3.4", "vue-i18n": "^11.1.5", "vue-router": "4.2.4", @@ -54,7 +54,7 @@ "@types/dompurify": "^3.0.5", "@types/markdown-it": "^14.1.2", "@types/node": "^20.5.7", - "@vitejs/plugin-vue": "4.3.3", + "@vitejs/plugin-vue": "5.2.4", "@vue/eslint-config-prettier": "8.0.0", "@vue/eslint-config-typescript": "11.0.3", "@vue/tsconfig": "^0.4.0", @@ -64,9 +64,15 @@ "sass": "1.66.1", "sass-loader": "13.3.2", "typescript": "5.1.6", - "vite": "4.4.9", + "vite": "6.4.1", "vue-cli-plugin-vuetify": "2.5.8", "vue-tsc": "1.8.8", "vuetify-loader": "^2.0.0-alpha.9" + }, + "pnpm": { + "overrides": { + "immutable": "4.3.8", + "lodash-es": "4.17.23" + } } } diff --git a/dashboard/pnpm-lock.yaml b/dashboard/pnpm-lock.yaml index ea8636c61..af45aa6f0 100644 --- a/dashboard/pnpm-lock.yaml +++ b/dashboard/pnpm-lock.yaml @@ -4,6 +4,10 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +overrides: + immutable: 4.3.8 + lodash-es: 4.17.23 + importers: .: @@ -21,11 +25,11 @@ importers: specifier: 3.42.0 version: 3.42.0 axios: - specifier: '>=1.6.2 <1.10.0 || >1.10.0 <2.0.0' - version: 1.13.4 + specifier: 1.13.5 + version: 1.13.5 axios-mock-adapter: specifier: ^1.22.0 - version: 1.22.0(axios@1.13.4) + version: 1.22.0(axios@1.13.5) chance: specifier: 1.1.11 version: 1.1.11 @@ -33,8 +37,8 @@ importers: specifier: 2.30.0 version: 2.30.0 dompurify: - specifier: ^3.3.1 - version: 3.3.1 + specifier: ^3.3.2 + version: 3.3.2 event-source-polyfill: specifier: ^1.0.31 version: 1.0.31 @@ -48,11 +52,11 @@ importers: specifier: ^0.16.27 version: 0.16.28 lodash: - specifier: 4.17.21 - version: 4.17.21 + specifier: 4.17.23 + version: 4.17.23 markdown-it: - specifier: ^14.1.0 - version: 14.1.0 + specifier: ^14.1.1 + version: 14.1.1 markstream-vue: specifier: ^0.0.6 version: 0.0.6(katex@0.16.28)(mermaid@11.12.2)(shiki@3.22.0)(stream-markdown@0.0.13(shiki@3.22.0))(stream-monaco@0.0.17(monaco-editor@0.52.2))(vue-i18n@11.2.8(vue@3.3.4))(vue@3.3.4) @@ -84,8 +88,8 @@ importers: specifier: 4.11.3 version: 4.11.3(vue@3.3.4) vite-plugin-vuetify: - specifier: 1.0.2 - version: 1.0.2(vite@4.4.9(@types/node@20.19.32)(sass@1.66.1)(terser@5.46.0))(vue@3.3.4)(vuetify@3.7.11) + specifier: 2.1.3 + version: 2.1.3(vite@6.4.1(@types/node@20.19.32)(sass@1.66.1)(terser@5.46.0))(vue@3.3.4)(vuetify@3.7.11) vue: specifier: 3.3.4 version: 3.3.4 @@ -103,7 +107,7 @@ importers: version: 0.1.4 vuetify: specifier: 3.7.11 - version: 3.7.11(typescript@5.1.6)(vite-plugin-vuetify@1.0.2)(vue@3.3.4) + version: 3.7.11(typescript@5.1.6)(vite-plugin-vuetify@2.1.3)(vue@3.3.4) yup: specifier: 1.2.0 version: 1.2.0 @@ -127,8 +131,8 @@ importers: specifier: ^20.5.7 version: 20.19.32 '@vitejs/plugin-vue': - specifier: 4.3.3 - version: 4.3.3(vite@4.4.9(@types/node@20.19.32)(sass@1.66.1)(terser@5.46.0))(vue@3.3.4) + specifier: 5.2.4 + version: 5.2.4(vite@6.4.1(@types/node@20.19.32)(sass@1.66.1)(terser@5.46.0))(vue@3.3.4) '@vue/eslint-config-prettier': specifier: 8.0.0 version: 8.0.0(@types/eslint@9.6.1)(eslint@8.48.0)(prettier@3.0.2) @@ -157,8 +161,8 @@ importers: specifier: 5.1.6 version: 5.1.6 vite: - specifier: 4.4.9 - version: 4.4.9(@types/node@20.19.32)(sass@1.66.1)(terser@5.46.0) + specifier: 6.4.1 + version: 6.4.1(@types/node@20.19.32)(sass@1.66.1)(terser@5.46.0) vue-cli-plugin-vuetify: specifier: 2.5.8 version: 2.5.8(sass-loader@13.3.2(sass@1.66.1)(webpack@5.105.0))(vue@3.3.4)(vuetify-loader@2.0.0-alpha.9(@vue/compiler-sfc@3.3.4)(vue@3.3.4)(vuetify@3.7.11)(webpack@5.105.0))(webpack@5.105.0) @@ -213,135 +217,159 @@ packages: '@chevrotain/utils@11.0.3': resolution: {integrity: sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==} - '@esbuild/android-arm64@0.18.20': - resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==} - engines: {node: '>=12'} + '@esbuild/aix-ppc64@0.25.12': + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.12': + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} + engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.18.20': - resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==} - engines: {node: '>=12'} + '@esbuild/android-arm@0.25.12': + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} + engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.18.20': - resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==} - engines: {node: '>=12'} + '@esbuild/android-x64@0.25.12': + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} + engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.18.20': - resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==} - engines: {node: '>=12'} + '@esbuild/darwin-arm64@0.25.12': + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} + engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.18.20': - resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==} - engines: {node: '>=12'} + '@esbuild/darwin-x64@0.25.12': + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} + engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.18.20': - resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==} - engines: {node: '>=12'} + '@esbuild/freebsd-arm64@0.25.12': + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} + engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.18.20': - resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==} - engines: {node: '>=12'} + '@esbuild/freebsd-x64@0.25.12': + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} + engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.18.20': - resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==} - engines: {node: '>=12'} + '@esbuild/linux-arm64@0.25.12': + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} + engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.18.20': - resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==} - engines: {node: '>=12'} + '@esbuild/linux-arm@0.25.12': + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} + engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.18.20': - resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==} - engines: {node: '>=12'} + '@esbuild/linux-ia32@0.25.12': + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} + engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.18.20': - resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==} - engines: {node: '>=12'} + '@esbuild/linux-loong64@0.25.12': + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} + engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.18.20': - resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==} - engines: {node: '>=12'} + '@esbuild/linux-mips64el@0.25.12': + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} + engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.18.20': - resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==} - engines: {node: '>=12'} + '@esbuild/linux-ppc64@0.25.12': + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} + engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.18.20': - resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==} - engines: {node: '>=12'} + '@esbuild/linux-riscv64@0.25.12': + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} + engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.18.20': - resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==} - engines: {node: '>=12'} + '@esbuild/linux-s390x@0.25.12': + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} + engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.18.20': - resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==} - engines: {node: '>=12'} + '@esbuild/linux-x64@0.25.12': + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} + engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-x64@0.18.20': - resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==} - engines: {node: '>=12'} + '@esbuild/netbsd-arm64@0.25.12': + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.12': + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} + engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-x64@0.18.20': - resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==} - engines: {node: '>=12'} + '@esbuild/openbsd-arm64@0.25.12': + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.12': + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} + engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/sunos-x64@0.18.20': - resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==} - engines: {node: '>=12'} + '@esbuild/openharmony-arm64@0.25.12': + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.25.12': + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} + engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.18.20': - resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==} - engines: {node: '>=12'} + '@esbuild/win32-arm64@0.25.12': + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} + engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.18.20': - resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==} - engines: {node: '>=12'} + '@esbuild/win32-ia32@0.25.12': + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} + engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.18.20': - resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==} - engines: {node: '>=12'} + '@esbuild/win32-x64@0.25.12': + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} + engines: {node: '>=18'} cpu: [x64] os: [win32] @@ -460,6 +488,144 @@ packages: '@remirror/core-constants@3.0.0': resolution: {integrity: sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==} + '@rollup/rollup-android-arm-eabi@4.59.0': + resolution: {integrity: sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.59.0': + resolution: {integrity: sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.59.0': + resolution: {integrity: sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.59.0': + resolution: {integrity: sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.59.0': + resolution: {integrity: sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.59.0': + resolution: {integrity: sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.59.0': + resolution: {integrity: sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.59.0': + resolution: {integrity: sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.59.0': + resolution: {integrity: sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.59.0': + resolution: {integrity: sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loong64-gnu@4.59.0': + resolution: {integrity: sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-loong64-musl@4.59.0': + resolution: {integrity: sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==} + cpu: [loong64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-ppc64-gnu@4.59.0': + resolution: {integrity: sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-ppc64-musl@4.59.0': + resolution: {integrity: sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==} + cpu: [ppc64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-riscv64-gnu@4.59.0': + resolution: {integrity: sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-musl@4.59.0': + resolution: {integrity: sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.59.0': + resolution: {integrity: sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.59.0': + resolution: {integrity: sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.59.0': + resolution: {integrity: sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-openbsd-x64@4.59.0': + resolution: {integrity: sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.59.0': + resolution: {integrity: sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.59.0': + resolution: {integrity: sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.59.0': + resolution: {integrity: sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.59.0': + resolution: {integrity: sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.59.0': + resolution: {integrity: sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==} + cpu: [x64] + os: [win32] + '@rushstack/eslint-patch@1.3.3': resolution: {integrity: sha512-0xd7qez0AQ+MbHatZTlI1gu5vkG8r7MYRUJAHPAHJBmGLs16zpkrpAVLvjQKQOqaXPDUBwOiJzNc00znHSCVBw==} @@ -745,6 +911,9 @@ packages: '@types/node@20.19.32': resolution: {integrity: sha512-Ez8QE4DMfhjjTsES9K2dwfV258qBui7qxUsoaixZDiTzbde4U12e1pXGNu/ECsUIOi5/zoCxAQxIhQnaUQ2VvA==} + '@types/node@20.19.37': + resolution: {integrity: sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==} + '@types/semver@7.7.1': resolution: {integrity: sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==} @@ -815,11 +984,11 @@ packages: '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} - '@vitejs/plugin-vue@4.3.3': - resolution: {integrity: sha512-ssxyhIAZqB0TrpUg6R0cBpCuMk9jTIlO1GNSKKQD6S8VjnXi6JXKfUXjSsxey9IwQiaRGsO1WnW9Rkl1L6AJVw==} - engines: {node: ^14.18.0 || >=16.0.0} + '@vitejs/plugin-vue@5.2.4': + resolution: {integrity: sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==} + engines: {node: ^18.0.0 || >=20.0.0} peerDependencies: - vite: ^4.0.0 + vite: ^5.0.0 || ^6.0.0 vue: ^3.2.25 '@volar/language-core@1.10.10': @@ -915,6 +1084,12 @@ packages: vue: ^3.0.0 vuetify: ^3.0.0-beta.4 + '@vuetify/loader-shared@2.1.2': + resolution: {integrity: sha512-X+1jBLmXHkpQEnC0vyOb4rtX2QSkBiFhaFXz8yhQqN2A4vQ6k2nChxN4Ol7VAY5KoqMdFoRMnmNdp/1qYXDQig==} + peerDependencies: + vue: ^3.0.0 + vuetify: '>=3' + '@webassemblyjs/ast@1.14.1': resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==} @@ -985,6 +1160,11 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} + hasBin: true + ajv-formats@2.1.1: resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} peerDependencies: @@ -1006,8 +1186,8 @@ packages: ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} - ajv@8.17.1: - resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} + ajv@8.18.0: + resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} alien-signals@2.0.8: resolution: {integrity: sha512-844G1VLkk0Pe2SJjY0J8vp8ADI73IM4KliNu2OGlYzWpO28NexEUvjHTcFjFX3VXoiUtwTbHxLNI9ImkcoBqzA==} @@ -1042,14 +1222,15 @@ packages: peerDependencies: axios: '>= 0.17.0' - axios@1.13.4: - resolution: {integrity: sha512-1wVkUaAO6WyaYtCkcYCOx12ZgpGf9Zif+qXa4n+oYzK558YryKqiL6UWwd5DqiH3VRW0GYhTZQ/vlgJrCoNQlg==} + axios@1.13.5: + resolution: {integrity: sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==} balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - baseline-browser-mapping@2.9.19: - resolution: {integrity: sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==} + baseline-browser-mapping@2.10.0: + resolution: {integrity: sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==} + engines: {node: '>=6.0.0'} hasBin: true big.js@5.2.2: @@ -1091,8 +1272,8 @@ packages: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} - caniuse-lite@1.0.30001769: - resolution: {integrity: sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==} + caniuse-lite@1.0.30001778: + resolution: {integrity: sha512-PN7uxFL+ExFJO61aVmP1aIEG4i9whQd4eoSCebav62UwDyp5OHh06zN4jqKSMePVgxHifCw1QJxdRkA1Pisekg==} ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} @@ -1384,22 +1565,23 @@ packages: resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} engines: {node: '>=6.0.0'} - dompurify@3.3.1: - resolution: {integrity: sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==} + dompurify@3.3.2: + resolution: {integrity: sha512-6obghkliLdmKa56xdbLOpUZ43pAR6xFy1uOrxBaIDjT+yaRuuybLjGS9eVBoSR/UPU5fq3OXClEHLJNGvbxKpQ==} + engines: {node: '>=20'} dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} - electron-to-chromium@1.5.286: - resolution: {integrity: sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==} + electron-to-chromium@1.5.307: + resolution: {integrity: sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg==} emojis-list@3.0.0: resolution: {integrity: sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==} engines: {node: '>= 4'} - enhanced-resolve@5.19.0: - resolution: {integrity: sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==} + enhanced-resolve@5.20.0: + resolution: {integrity: sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ==} engines: {node: '>=10.13.0'} entities@4.5.0: @@ -1429,9 +1611,9 @@ packages: resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} engines: {node: '>= 0.4'} - esbuild@0.18.20: - resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==} - engines: {node: '>=12'} + esbuild@0.25.12: + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} + engines: {node: '>=18'} hasBin: true escalade@3.2.0: @@ -1542,6 +1724,15 @@ packages: fastq@1.20.1: resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + file-entry-cache@6.0.1: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} @@ -1684,8 +1875,8 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} - immutable@4.3.7: - resolution: {integrity: sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==} + immutable@4.3.8: + resolution: {integrity: sha512-d/Ld9aLbKpNwyl0KiM2CT1WYvkitQ1TSvmRtkcV8FKStiDoA7Slzgjmb/1G2yhKM1p0XeNOieaTbFZmU1d3Xuw==} import-fresh@3.3.1: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} @@ -1818,17 +2009,14 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} - lodash-es@4.17.21: - resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} - lodash-es@4.17.23: resolution: {integrity: sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==} lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - lodash@4.17.21: - resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + lodash@4.17.23: + resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==} magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} @@ -1862,8 +2050,8 @@ packages: resolution: {integrity: sha512-nZpRTJj4S6bN0I5wsNBtgzDKx+HYBBSsvKjGdYw7/tPdrzfo3gUTt3ZbeAjPGeZaC6a4LFi4JdhTVeLm3F6TIQ==} engines: {node: '>=18'} - markdown-it@14.1.0: - resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==} + markdown-it@14.1.1: + resolution: {integrity: sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==} hasBin: true marked@16.4.2: @@ -1978,8 +2166,8 @@ packages: neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} - node-releases@2.0.27: - resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} + node-releases@2.0.36: + resolution: {integrity: sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==} normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} @@ -2072,6 +2260,10 @@ packages: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + pinia@2.1.6: resolution: {integrity: sha512-bIU6QuE5qZviMmct5XwCesXelb5VavdOWKWaB17ggk++NUwQWWbP5YnsONTk3b752QkW9sACiR81rorpeOMSvQ==} peerDependencies: @@ -2127,8 +2319,8 @@ packages: property-information@7.1.0: resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} - prosemirror-changeset@2.3.1: - resolution: {integrity: sha512-j0kORIBm8ayJNl3zQvD1TTPHJX3g042et6y/KQhZhnPrruO8exkTgG8X+NRpj7kIyMMEx74Xb3DyMIBtO0IKkQ==} + prosemirror-changeset@2.4.0: + resolution: {integrity: sha512-LvqH2v7Q2SF6yxatuPP2e8vSUKS/L+xAU7dPDC4RMyHMhZoGDfBC74mYuyYF4gLqOEG758wajtyhNnsTkuhvng==} prosemirror-collab@1.3.1: resolution: {integrity: sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==} @@ -2139,8 +2331,8 @@ packages: prosemirror-dropcursor@1.8.2: resolution: {integrity: sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw==} - prosemirror-gapcursor@1.4.0: - resolution: {integrity: sha512-z00qvurSdCEWUIulij/isHaqu4uLS8r/Fi61IbjdIPJEonQgggbJsLnstW7Lgdk4zQ68/yr6B6bf7sJXowIgdQ==} + prosemirror-gapcursor@1.4.1: + resolution: {integrity: sha512-pMdYaEnjNMSwl11yjEGtgTmLkR08m/Vl+Jj443167p9eB3HVQKhYCc4gmHVDsLPODfZfjr/MmirsdyZziXbQKw==} prosemirror-history@1.5.0: resolution: {integrity: sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg==} @@ -2154,8 +2346,8 @@ packages: prosemirror-markdown@1.13.4: resolution: {integrity: sha512-D98dm4cQ3Hs6EmjK500TdAOew4Z03EV71ajEFiWra3Upr7diytJsjF4mPV2dW+eK5uNectiRj0xFxYI9NLXDbw==} - prosemirror-menu@1.2.5: - resolution: {integrity: sha512-qwXzynnpBIeg1D7BAtjOusR+81xCp53j7iWu/IargiRZqRjGIlQuu1f3jFi+ehrHhWMLoyOQTSRx/IWZJqOYtQ==} + prosemirror-menu@1.3.0: + resolution: {integrity: sha512-TImyPXCHPcDsSka2/lwJ6WjTASr4re/qWq1yoTTuLOqfXucwF6VcRa2LWCkM/EyTD1UO3CUwiH8qURJoWJRxwg==} prosemirror-model@1.25.4: resolution: {integrity: sha512-PIM7E43PBxKce8OQeezAs9j4TP+5yDpZVbuurd1h5phUxEKIu+G2a+EUZzIC5nS1mJktDJWzbqS23n1tsAf5QA==} @@ -2204,9 +2396,6 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - randombytes@2.1.0: - resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} - readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} @@ -2252,9 +2441,9 @@ packages: robust-predicates@3.0.2: resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==} - rollup@3.29.5: - resolution: {integrity: sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w==} - engines: {node: '>=14.18.0', npm: '>=8.0.0'} + rollup@4.59.0: + resolution: {integrity: sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true rope-sequence@1.3.4: @@ -2269,9 +2458,6 @@ packages: rw@1.3.3: resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==} - safe-buffer@5.2.1: - resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} @@ -2316,9 +2502,6 @@ packages: engines: {node: '>=10'} hasBin: true - serialize-javascript@6.0.2: - resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} - shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -2435,8 +2618,8 @@ packages: resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} engines: {node: '>=6'} - terser-webpack-plugin@5.3.16: - resolution: {integrity: sha512-h9oBFCWrq78NyWWVcSwZarJkZ01c2AyGrzs1crmHZO3QUg9D61Wu4NPjBy69n7JqylFF5y+CsUZYmYEIZ3mR+Q==} + terser-webpack-plugin@5.4.0: + resolution: {integrity: sha512-Bn5vxm48flOIfkdl5CaD2+1CiUVbonWQ3KQPyP7/EuIl9Gbzq/gQFOzaMFUEgVjB1396tcK0SG8XcNJ/2kDH8g==} engines: {node: '>= 10.13.0'} peerDependencies: '@swc/core': '*' @@ -2466,6 +2649,10 @@ packages: resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} engines: {node: '>=18'} + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + tippy.js@6.3.7: resolution: {integrity: sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==} @@ -2568,41 +2755,53 @@ packages: vfile@6.0.3: resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} - vite-plugin-vuetify@1.0.2: - resolution: {integrity: sha512-MubIcKD33O8wtgQXlbEXE7ccTEpHZ8nPpe77y9Wy3my2MWw/PgehP9VqTp92BLqr0R1dSL970Lynvisx3UxBFw==} - engines: {node: '>=12'} + vite-plugin-vuetify@2.1.3: + resolution: {integrity: sha512-Q4SC/4TqbNvaZIFb9YsfBqkGlYHbJJJ6uU3CnRBZqLUF3s5eCMVZAaV4GkTbehIH/bhSj42lMXztOwc71u6rVw==} + engines: {node: ^18.0.0 || >=20.0.0} peerDependencies: - vite: ^2.7.0 || ^3.0.0 || ^4.0.0 + vite: '>=5' vue: ^3.0.0 - vuetify: ^3.0.0-beta.4 + vuetify: '>=3' - vite@4.4.9: - resolution: {integrity: sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==} - engines: {node: ^14.18.0 || >=16.0.0} + vite@6.4.1: + resolution: {integrity: sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true peerDependencies: - '@types/node': '>= 14' + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: '>=1.21.0' less: '*' lightningcss: ^1.21.0 sass: '*' + sass-embedded: '*' stylus: '*' sugarss: '*' - terser: ^5.4.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 peerDependenciesMeta: '@types/node': optional: true + jiti: + optional: true less: optional: true lightningcss: optional: true sass: optional: true + sass-embedded: + optional: true stylus: optional: true sugarss: optional: true terser: optional: true + tsx: + optional: true + yaml: + optional: true vscode-jsonrpc@8.2.0: resolution: {integrity: sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==} @@ -2718,8 +2917,8 @@ packages: resolution: {integrity: sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==} engines: {node: '>=10.13.0'} - webpack-sources@3.3.3: - resolution: {integrity: sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==} + webpack-sources@3.3.4: + resolution: {integrity: sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q==} engines: {node: '>=10.13.0'} webpack@5.105.0: @@ -2786,12 +2985,12 @@ snapshots: dependencies: '@chevrotain/gast': 11.0.3 '@chevrotain/types': 11.0.3 - lodash-es: 4.17.21 + lodash-es: 4.17.23 '@chevrotain/gast@11.0.3': dependencies: '@chevrotain/types': 11.0.3 - lodash-es: 4.17.21 + lodash-es: 4.17.23 '@chevrotain/regexp-to-ast@11.0.3': {} @@ -2799,70 +2998,82 @@ snapshots: '@chevrotain/utils@11.0.3': {} - '@esbuild/android-arm64@0.18.20': + '@esbuild/aix-ppc64@0.25.12': optional: true - '@esbuild/android-arm@0.18.20': + '@esbuild/android-arm64@0.25.12': optional: true - '@esbuild/android-x64@0.18.20': + '@esbuild/android-arm@0.25.12': optional: true - '@esbuild/darwin-arm64@0.18.20': + '@esbuild/android-x64@0.25.12': optional: true - '@esbuild/darwin-x64@0.18.20': + '@esbuild/darwin-arm64@0.25.12': optional: true - '@esbuild/freebsd-arm64@0.18.20': + '@esbuild/darwin-x64@0.25.12': optional: true - '@esbuild/freebsd-x64@0.18.20': + '@esbuild/freebsd-arm64@0.25.12': optional: true - '@esbuild/linux-arm64@0.18.20': + '@esbuild/freebsd-x64@0.25.12': optional: true - '@esbuild/linux-arm@0.18.20': + '@esbuild/linux-arm64@0.25.12': optional: true - '@esbuild/linux-ia32@0.18.20': + '@esbuild/linux-arm@0.25.12': optional: true - '@esbuild/linux-loong64@0.18.20': + '@esbuild/linux-ia32@0.25.12': optional: true - '@esbuild/linux-mips64el@0.18.20': + '@esbuild/linux-loong64@0.25.12': optional: true - '@esbuild/linux-ppc64@0.18.20': + '@esbuild/linux-mips64el@0.25.12': optional: true - '@esbuild/linux-riscv64@0.18.20': + '@esbuild/linux-ppc64@0.25.12': optional: true - '@esbuild/linux-s390x@0.18.20': + '@esbuild/linux-riscv64@0.25.12': optional: true - '@esbuild/linux-x64@0.18.20': + '@esbuild/linux-s390x@0.25.12': optional: true - '@esbuild/netbsd-x64@0.18.20': + '@esbuild/linux-x64@0.25.12': optional: true - '@esbuild/openbsd-x64@0.18.20': + '@esbuild/netbsd-arm64@0.25.12': optional: true - '@esbuild/sunos-x64@0.18.20': + '@esbuild/netbsd-x64@0.25.12': optional: true - '@esbuild/win32-arm64@0.18.20': + '@esbuild/openbsd-arm64@0.25.12': optional: true - '@esbuild/win32-ia32@0.18.20': + '@esbuild/openbsd-x64@0.25.12': optional: true - '@esbuild/win32-x64@0.18.20': + '@esbuild/openharmony-arm64@0.25.12': + optional: true + + '@esbuild/sunos-x64@0.25.12': + optional: true + + '@esbuild/win32-arm64@0.25.12': + optional: true + + '@esbuild/win32-ia32@0.25.12': + optional: true + + '@esbuild/win32-x64@0.25.12': optional: true '@eslint-community/eslint-utils@4.9.1(eslint@8.48.0)': @@ -2985,6 +3196,81 @@ snapshots: '@remirror/core-constants@3.0.0': {} + '@rollup/rollup-android-arm-eabi@4.59.0': + optional: true + + '@rollup/rollup-android-arm64@4.59.0': + optional: true + + '@rollup/rollup-darwin-arm64@4.59.0': + optional: true + + '@rollup/rollup-darwin-x64@4.59.0': + optional: true + + '@rollup/rollup-freebsd-arm64@4.59.0': + optional: true + + '@rollup/rollup-freebsd-x64@4.59.0': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.59.0': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.59.0': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.59.0': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.59.0': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.59.0': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.59.0': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.59.0': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.59.0': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.59.0': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.59.0': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.59.0': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.59.0': + optional: true + + '@rollup/rollup-linux-x64-musl@4.59.0': + optional: true + + '@rollup/rollup-openbsd-x64@4.59.0': + optional: true + + '@rollup/rollup-openharmony-arm64@4.59.0': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.59.0': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.59.0': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.59.0': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.59.0': + optional: true + '@rushstack/eslint-patch@1.3.3': {} '@shikijs/core@3.22.0': @@ -3121,16 +3407,16 @@ snapshots: '@tiptap/pm@2.27.2': dependencies: - prosemirror-changeset: 2.3.1 + prosemirror-changeset: 2.4.0 prosemirror-collab: 1.3.1 prosemirror-commands: 1.7.1 prosemirror-dropcursor: 1.8.2 - prosemirror-gapcursor: 1.4.0 + prosemirror-gapcursor: 1.4.1 prosemirror-history: 1.5.0 prosemirror-inputrules: 1.5.1 prosemirror-keymap: 1.2.3 prosemirror-markdown: 1.13.4 - prosemirror-menu: 1.2.5 + prosemirror-menu: 1.3.0 prosemirror-model: 1.25.4 prosemirror-schema-basic: 1.2.4 prosemirror-schema-list: 1.5.1 @@ -3293,7 +3579,7 @@ snapshots: '@types/dompurify@3.2.0': dependencies: - dompurify: 3.3.1 + dompurify: 3.3.2 '@types/eslint-scope@3.7.7': dependencies: @@ -3332,6 +3618,10 @@ snapshots: dependencies: undici-types: 6.21.0 + '@types/node@20.19.37': + dependencies: + undici-types: 6.21.0 + '@types/semver@7.7.1': {} '@types/trusted-types@2.0.7': @@ -3425,9 +3715,9 @@ snapshots: '@ungap/structured-clone@1.3.0': {} - '@vitejs/plugin-vue@4.3.3(vite@4.4.9(@types/node@20.19.32)(sass@1.66.1)(terser@5.46.0))(vue@3.3.4)': + '@vitejs/plugin-vue@5.2.4(vite@6.4.1(@types/node@20.19.32)(sass@1.66.1)(terser@5.46.0))(vue@3.3.4)': dependencies: - vite: 4.4.9(@types/node@20.19.32)(sass@1.66.1)(terser@5.46.0) + vite: 6.4.1(@types/node@20.19.32)(sass@1.66.1)(terser@5.46.0) vue: 3.3.4 '@volar/language-core@1.10.10': @@ -3573,7 +3863,13 @@ snapshots: find-cache-dir: 3.3.2 upath: 2.0.1 vue: 3.3.4 - vuetify: 3.7.11(typescript@5.1.6)(vite-plugin-vuetify@1.0.2)(vue@3.3.4) + vuetify: 3.7.11(typescript@5.1.6)(vite-plugin-vuetify@2.1.3)(vue@3.3.4) + + '@vuetify/loader-shared@2.1.2(vue@3.3.4)(vuetify@3.7.11)': + dependencies: + upath: 2.0.1 + vue: 3.3.4 + vuetify: 3.7.11(typescript@5.1.6)(vite-plugin-vuetify@2.1.3)(vue@3.3.4) '@webassemblyjs/ast@1.14.1': dependencies: @@ -3657,9 +3953,9 @@ snapshots: '@yr/monotone-cubic-spline@1.0.3': {} - acorn-import-phases@1.0.4(acorn@8.15.0): + acorn-import-phases@1.0.4(acorn@8.16.0): dependencies: - acorn: 8.15.0 + acorn: 8.16.0 acorn-jsx@5.3.2(acorn@8.15.0): dependencies: @@ -3667,17 +3963,19 @@ snapshots: acorn@8.15.0: {} - ajv-formats@2.1.1(ajv@8.17.1): + acorn@8.16.0: {} + + ajv-formats@2.1.1(ajv@8.18.0): optionalDependencies: - ajv: 8.17.1 + ajv: 8.18.0 ajv-keywords@3.5.2(ajv@6.12.6): dependencies: ajv: 6.12.6 - ajv-keywords@5.1.0(ajv@8.17.1): + ajv-keywords@5.1.0(ajv@8.18.0): dependencies: - ajv: 8.17.1 + ajv: 8.18.0 fast-deep-equal: 3.1.3 ajv@6.12.6: @@ -3687,7 +3985,7 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 - ajv@8.17.1: + ajv@8.18.0: dependencies: fast-deep-equal: 3.1.3 fast-uri: 3.1.0 @@ -3723,13 +4021,13 @@ snapshots: asynckit@0.4.0: {} - axios-mock-adapter@1.22.0(axios@1.13.4): + axios-mock-adapter@1.22.0(axios@1.13.5): dependencies: - axios: 1.13.4 + axios: 1.13.5 fast-deep-equal: 3.1.3 is-buffer: 2.0.5 - axios@1.13.4: + axios@1.13.5: dependencies: follow-redirects: 1.15.11 form-data: 4.0.5 @@ -3739,7 +4037,7 @@ snapshots: balanced-match@1.0.2: {} - baseline-browser-mapping@2.9.19: {} + baseline-browser-mapping@2.10.0: {} big.js@5.2.2: {} @@ -3762,10 +4060,10 @@ snapshots: browserslist@4.28.1: dependencies: - baseline-browser-mapping: 2.9.19 - caniuse-lite: 1.0.30001769 - electron-to-chromium: 1.5.286 - node-releases: 2.0.27 + baseline-browser-mapping: 2.10.0 + caniuse-lite: 1.0.30001778 + electron-to-chromium: 1.5.307 + node-releases: 2.0.36 update-browserslist-db: 1.2.3(browserslist@4.28.1) buffer-from@1.1.2: {} @@ -3779,7 +4077,7 @@ snapshots: callsites@3.1.0: {} - caniuse-lite@1.0.30001769: {} + caniuse-lite@1.0.30001778: {} ccount@2.0.1: {} @@ -3806,7 +4104,7 @@ snapshots: '@chevrotain/regexp-to-ast': 11.0.3 '@chevrotain/types': 11.0.3 '@chevrotain/utils': 11.0.3 - lodash-es: 4.17.21 + lodash-es: 4.17.23 chokidar@3.6.0: dependencies: @@ -4088,7 +4386,7 @@ snapshots: dependencies: esutils: 2.0.3 - dompurify@3.3.1: + dompurify@3.3.2: optionalDependencies: '@types/trusted-types': 2.0.7 @@ -4098,11 +4396,11 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 - electron-to-chromium@1.5.286: {} + electron-to-chromium@1.5.307: {} emojis-list@3.0.0: {} - enhanced-resolve@5.19.0: + enhanced-resolve@5.20.0: dependencies: graceful-fs: 4.2.11 tapable: 2.3.0 @@ -4128,30 +4426,34 @@ snapshots: has-tostringtag: 1.0.2 hasown: 2.0.2 - esbuild@0.18.20: + esbuild@0.25.12: optionalDependencies: - '@esbuild/android-arm': 0.18.20 - '@esbuild/android-arm64': 0.18.20 - '@esbuild/android-x64': 0.18.20 - '@esbuild/darwin-arm64': 0.18.20 - '@esbuild/darwin-x64': 0.18.20 - '@esbuild/freebsd-arm64': 0.18.20 - '@esbuild/freebsd-x64': 0.18.20 - '@esbuild/linux-arm': 0.18.20 - '@esbuild/linux-arm64': 0.18.20 - '@esbuild/linux-ia32': 0.18.20 - '@esbuild/linux-loong64': 0.18.20 - '@esbuild/linux-mips64el': 0.18.20 - '@esbuild/linux-ppc64': 0.18.20 - '@esbuild/linux-riscv64': 0.18.20 - '@esbuild/linux-s390x': 0.18.20 - '@esbuild/linux-x64': 0.18.20 - '@esbuild/netbsd-x64': 0.18.20 - '@esbuild/openbsd-x64': 0.18.20 - '@esbuild/sunos-x64': 0.18.20 - '@esbuild/win32-arm64': 0.18.20 - '@esbuild/win32-ia32': 0.18.20 - '@esbuild/win32-x64': 0.18.20 + '@esbuild/aix-ppc64': 0.25.12 + '@esbuild/android-arm': 0.25.12 + '@esbuild/android-arm64': 0.25.12 + '@esbuild/android-x64': 0.25.12 + '@esbuild/darwin-arm64': 0.25.12 + '@esbuild/darwin-x64': 0.25.12 + '@esbuild/freebsd-arm64': 0.25.12 + '@esbuild/freebsd-x64': 0.25.12 + '@esbuild/linux-arm': 0.25.12 + '@esbuild/linux-arm64': 0.25.12 + '@esbuild/linux-ia32': 0.25.12 + '@esbuild/linux-loong64': 0.25.12 + '@esbuild/linux-mips64el': 0.25.12 + '@esbuild/linux-ppc64': 0.25.12 + '@esbuild/linux-riscv64': 0.25.12 + '@esbuild/linux-s390x': 0.25.12 + '@esbuild/linux-x64': 0.25.12 + '@esbuild/netbsd-arm64': 0.25.12 + '@esbuild/netbsd-x64': 0.25.12 + '@esbuild/openbsd-arm64': 0.25.12 + '@esbuild/openbsd-x64': 0.25.12 + '@esbuild/openharmony-arm64': 0.25.12 + '@esbuild/sunos-x64': 0.25.12 + '@esbuild/win32-arm64': 0.25.12 + '@esbuild/win32-ia32': 0.25.12 + '@esbuild/win32-x64': 0.25.12 escalade@3.2.0: {} @@ -4286,6 +4588,10 @@ snapshots: dependencies: reusify: 1.1.0 + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + file-entry-cache@6.0.1: dependencies: flat-cache: 3.2.0 @@ -4441,7 +4747,7 @@ snapshots: ignore@5.3.2: {} - immutable@4.3.7: {} + immutable@4.3.8: {} import-fresh@3.3.1: dependencies: @@ -4487,7 +4793,7 @@ snapshots: jest-worker@27.5.1: dependencies: - '@types/node': 20.19.32 + '@types/node': 20.19.37 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -4556,13 +4862,11 @@ snapshots: dependencies: p-locate: 5.0.0 - lodash-es@4.17.21: {} - lodash-es@4.17.23: {} lodash.merge@4.6.2: {} - lodash@4.17.21: {} + lodash@4.17.23: {} magic-string@0.30.21: dependencies: @@ -4594,7 +4898,7 @@ snapshots: punycode.js: 2.3.1 uc.micro: 2.1.0 - markdown-it@14.1.0: + markdown-it@14.1.1: dependencies: argparse: 2.0.1 entities: 4.5.0 @@ -4651,7 +4955,7 @@ snapshots: d3-sankey: 0.12.3 dagre-d3-es: 7.0.13 dayjs: 1.11.19 - dompurify: 3.3.1 + dompurify: 3.3.2 katex: 0.16.28 khroma: 2.1.0 lodash-es: 4.17.23 @@ -4718,7 +5022,7 @@ snapshots: neo-async@2.6.2: {} - node-releases@2.0.27: {} + node-releases@2.0.36: {} normalize-path@3.0.0: {} @@ -4799,6 +5103,8 @@ snapshots: picomatch@2.3.1: {} + picomatch@4.0.3: {} + pinia@2.1.6(typescript@5.1.6)(vue@3.3.4): dependencies: '@vue/devtools-api': 6.6.4 @@ -4849,7 +5155,7 @@ snapshots: property-information@7.1.0: {} - prosemirror-changeset@2.3.1: + prosemirror-changeset@2.4.0: dependencies: prosemirror-transform: 1.11.0 @@ -4869,7 +5175,7 @@ snapshots: prosemirror-transform: 1.11.0 prosemirror-view: 1.41.6 - prosemirror-gapcursor@1.4.0: + prosemirror-gapcursor@1.4.1: dependencies: prosemirror-keymap: 1.2.3 prosemirror-model: 1.25.4 @@ -4896,10 +5202,10 @@ snapshots: prosemirror-markdown@1.13.4: dependencies: '@types/markdown-it': 14.1.2 - markdown-it: 14.1.0 + markdown-it: 14.1.1 prosemirror-model: 1.25.4 - prosemirror-menu@1.2.5: + prosemirror-menu@1.3.0: dependencies: crelt: 1.0.6 prosemirror-commands: 1.7.1 @@ -4962,10 +5268,6 @@ snapshots: queue-microtask@1.2.3: {} - randombytes@2.1.0: - dependencies: - safe-buffer: 5.2.1 - readdirp@3.6.0: dependencies: picomatch: 2.3.1 @@ -5004,8 +5306,35 @@ snapshots: robust-predicates@3.0.2: {} - rollup@3.29.5: + rollup@4.59.0: + dependencies: + '@types/estree': 1.0.8 optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.59.0 + '@rollup/rollup-android-arm64': 4.59.0 + '@rollup/rollup-darwin-arm64': 4.59.0 + '@rollup/rollup-darwin-x64': 4.59.0 + '@rollup/rollup-freebsd-arm64': 4.59.0 + '@rollup/rollup-freebsd-x64': 4.59.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.59.0 + '@rollup/rollup-linux-arm-musleabihf': 4.59.0 + '@rollup/rollup-linux-arm64-gnu': 4.59.0 + '@rollup/rollup-linux-arm64-musl': 4.59.0 + '@rollup/rollup-linux-loong64-gnu': 4.59.0 + '@rollup/rollup-linux-loong64-musl': 4.59.0 + '@rollup/rollup-linux-ppc64-gnu': 4.59.0 + '@rollup/rollup-linux-ppc64-musl': 4.59.0 + '@rollup/rollup-linux-riscv64-gnu': 4.59.0 + '@rollup/rollup-linux-riscv64-musl': 4.59.0 + '@rollup/rollup-linux-s390x-gnu': 4.59.0 + '@rollup/rollup-linux-x64-gnu': 4.59.0 + '@rollup/rollup-linux-x64-musl': 4.59.0 + '@rollup/rollup-openbsd-x64': 4.59.0 + '@rollup/rollup-openharmony-arm64': 4.59.0 + '@rollup/rollup-win32-arm64-msvc': 4.59.0 + '@rollup/rollup-win32-ia32-msvc': 4.59.0 + '@rollup/rollup-win32-x64-gnu': 4.59.0 + '@rollup/rollup-win32-x64-msvc': 4.59.0 fsevents: 2.3.3 rope-sequence@1.3.4: {} @@ -5023,8 +5352,6 @@ snapshots: rw@1.3.3: {} - safe-buffer@5.2.1: {} - safer-buffer@2.1.2: {} sass-loader@13.3.2(sass@1.66.1)(webpack@5.105.0): @@ -5037,7 +5364,7 @@ snapshots: sass@1.66.1: dependencies: chokidar: 3.6.0 - immutable: 4.3.7 + immutable: 4.3.8 source-map-js: 1.2.1 schema-utils@3.3.0: @@ -5049,18 +5376,14 @@ snapshots: schema-utils@4.3.3: dependencies: '@types/json-schema': 7.0.15 - ajv: 8.17.1 - ajv-formats: 2.1.1(ajv@8.17.1) - ajv-keywords: 5.1.0(ajv@8.17.1) + ajv: 8.18.0 + ajv-formats: 2.1.1(ajv@8.18.0) + ajv-keywords: 5.1.0(ajv@8.18.0) semver@6.3.1: {} semver@7.7.4: {} - serialize-javascript@6.0.2: - dependencies: - randombytes: 2.1.0 - shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 @@ -5181,19 +5504,18 @@ snapshots: tapable@2.3.0: {} - terser-webpack-plugin@5.3.16(webpack@5.105.0): + terser-webpack-plugin@5.4.0(webpack@5.105.0): dependencies: '@jridgewell/trace-mapping': 0.3.31 jest-worker: 27.5.1 schema-utils: 4.3.3 - serialize-javascript: 6.0.2 terser: 5.46.0 webpack: 5.105.0 terser@5.46.0: dependencies: '@jridgewell/source-map': 0.3.11 - acorn: 8.15.0 + acorn: 8.16.0 commander: 2.20.3 source-map-support: 0.5.21 @@ -5203,6 +5525,11 @@ snapshots: tinyexec@1.0.2: {} + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + tippy.js@6.3.7: dependencies: '@popperjs/core': 2.11.8 @@ -5297,22 +5624,25 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vite-plugin-vuetify@1.0.2(vite@4.4.9(@types/node@20.19.32)(sass@1.66.1)(terser@5.46.0))(vue@3.3.4)(vuetify@3.7.11): + vite-plugin-vuetify@2.1.3(vite@6.4.1(@types/node@20.19.32)(sass@1.66.1)(terser@5.46.0))(vue@3.3.4)(vuetify@3.7.11): dependencies: - '@vuetify/loader-shared': 1.7.1(vue@3.3.4)(vuetify@3.7.11) + '@vuetify/loader-shared': 2.1.2(vue@3.3.4)(vuetify@3.7.11) debug: 4.4.3 upath: 2.0.1 - vite: 4.4.9(@types/node@20.19.32)(sass@1.66.1)(terser@5.46.0) + vite: 6.4.1(@types/node@20.19.32)(sass@1.66.1)(terser@5.46.0) vue: 3.3.4 - vuetify: 3.7.11(typescript@5.1.6)(vite-plugin-vuetify@1.0.2)(vue@3.3.4) + vuetify: 3.7.11(typescript@5.1.6)(vite-plugin-vuetify@2.1.3)(vue@3.3.4) transitivePeerDependencies: - supports-color - vite@4.4.9(@types/node@20.19.32)(sass@1.66.1)(terser@5.46.0): + vite@6.4.1(@types/node@20.19.32)(sass@1.66.1)(terser@5.46.0): dependencies: - esbuild: 0.18.20 + esbuild: 0.25.12 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 postcss: 8.5.6 - rollup: 3.29.5 + rollup: 4.59.0 + tinyglobby: 0.2.15 optionalDependencies: '@types/node': 20.19.32 fsevents: 2.3.3 @@ -5359,7 +5689,7 @@ snapshots: eslint-visitor-keys: 3.4.3 espree: 9.6.1 esquery: 1.7.0 - lodash: 4.17.21 + lodash: 4.17.23 semver: 7.7.4 transitivePeerDependencies: - supports-color @@ -5416,17 +5746,17 @@ snapshots: null-loader: 4.0.1(webpack@5.105.0) querystring: 0.2.1 upath: 2.0.1 - vuetify: 3.7.11(typescript@5.1.6)(vite-plugin-vuetify@1.0.2)(vue@3.3.4) + vuetify: 3.7.11(typescript@5.1.6)(vite-plugin-vuetify@2.1.3)(vue@3.3.4) webpack: 5.105.0 transitivePeerDependencies: - vue - vuetify@3.7.11(typescript@5.1.6)(vite-plugin-vuetify@1.0.2)(vue@3.3.4): + vuetify@3.7.11(typescript@5.1.6)(vite-plugin-vuetify@2.1.3)(vue@3.3.4): dependencies: vue: 3.3.4 optionalDependencies: typescript: 5.1.6 - vite-plugin-vuetify: 1.0.2(vite@4.4.9(@types/node@20.19.32)(sass@1.66.1)(terser@5.46.0))(vue@3.3.4)(vuetify@3.7.11) + vite-plugin-vuetify: 2.1.3(vite@6.4.1(@types/node@20.19.32)(sass@1.66.1)(terser@5.46.0))(vue@3.3.4)(vuetify@3.7.11) w3c-keyname@2.2.8: {} @@ -5435,7 +5765,7 @@ snapshots: glob-to-regexp: 0.4.1 graceful-fs: 4.2.11 - webpack-sources@3.3.3: {} + webpack-sources@3.3.4: {} webpack@5.105.0: dependencies: @@ -5445,11 +5775,11 @@ snapshots: '@webassemblyjs/ast': 1.14.1 '@webassemblyjs/wasm-edit': 1.14.1 '@webassemblyjs/wasm-parser': 1.14.1 - acorn: 8.15.0 - acorn-import-phases: 1.0.4(acorn@8.15.0) + acorn: 8.16.0 + acorn-import-phases: 1.0.4(acorn@8.16.0) browserslist: 4.28.1 chrome-trace-event: 1.0.4 - enhanced-resolve: 5.19.0 + enhanced-resolve: 5.20.0 es-module-lexer: 2.0.0 eslint-scope: 5.1.1 events: 3.3.0 @@ -5461,9 +5791,9 @@ snapshots: neo-async: 2.6.2 schema-utils: 4.3.3 tapable: 2.3.0 - terser-webpack-plugin: 5.3.16(webpack@5.105.0) + terser-webpack-plugin: 5.4.0(webpack@5.105.0) watchpack: 2.5.1 - webpack-sources: 3.3.3 + webpack-sources: 3.3.4 transitivePeerDependencies: - '@swc/core' - esbuild diff --git a/k8s/astrbot_with_napcat/02-deployment.yaml b/k8s/astrbot_with_napcat/02-deployment.yaml index 8b41f4ffb..510e1520c 100644 --- a/k8s/astrbot_with_napcat/02-deployment.yaml +++ b/k8s/astrbot_with_napcat/02-deployment.yaml @@ -26,7 +26,7 @@ spec: # 初始化容器:首次生成随机 machine-id,后续复用 initContainers: - name: init-machine-id - image: busybox:latest + image: busybox:1.37.0 command: - /bin/sh - -c @@ -123,4 +123,4 @@ spec: - name: localtime hostPath: path: /etc/localtime - type: File \ No newline at end of file + type: File From bca1476eabdb17801e054cb5a2f77a81734b4a05 Mon Sep 17 00:00:00 2001 From: letr <123731298+letr007@users.noreply.github.com> Date: Thu, 12 Mar 2026 20:19:00 +0800 Subject: [PATCH 03/62] fix(extension): refresh plugin market install state after install (#6124) * fix(extension): refresh market install state after plugin install * chore: remove redundant call Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --------- Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- dashboard/src/views/extension/useExtensionPage.js | 1 + 1 file changed, 1 insertion(+) diff --git a/dashboard/src/views/extension/useExtensionPage.js b/dashboard/src/views/extension/useExtensionPage.js index 6680eb8cd..4534c2f33 100644 --- a/dashboard/src/views/extension/useExtensionPage.js +++ b/dashboard/src/views/extension/useExtensionPage.js @@ -1309,6 +1309,7 @@ export const useExtensionPage = () => { onLoadingDialogResult(1, resData.message); dialog.value = false; await getExtensions(); + checkAlreadyInstalled(); viewReadme({ name: resData.data.name, From 01eae72a64562d221c5b16e485327ea79f2bc929 Mon Sep 17 00:00:00 2001 From: advent259141 <2968474907@qq.com> Date: Thu, 12 Mar 2026 23:05:00 +0800 Subject: [PATCH 04/62] =?UTF-8?q?docs:=20=E6=B7=BB=E5=8A=A0=20Astrbook=20?= =?UTF-8?q?=E5=92=8C=E7=8E=96=E5=B8=95=E5=96=B5=E7=A4=BE=E5=8C=BA=E9=93=BE?= =?UTF-8?q?=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/zh/community.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/zh/community.md b/docs/zh/community.md index c8a7da71a..6ee1d4c6f 100644 --- a/docs/zh/community.md +++ b/docs/zh/community.md @@ -23,6 +23,14 @@ https://discord.gg/PxgzhmxJ +### Astrbook + +- [Astrbook](https://book.astrbot.app/) - 专为 AI Agent 打造的社交社区,你可以在这里看到机器人们的日常动态,也可以将你的 Bot 接入其中。 + +### 玖帕喵 Prompt Market + +- [玖帕喵](https://jiupamiao.asia/) - AI 人设与 Prompt 分享市场,在这里发现和分享高质量的 Prompts。玖帕喵,喵喵喵喵,喵! + ### GitHub 欢迎提交 Issue 或 Pull Request: From bdac0b65f426e00b5b9ced8ba924189cbe6d2ae4 Mon Sep 17 00:00:00 2001 From: orbisai0security Date: Thu, 12 Mar 2026 21:23:47 +0530 Subject: [PATCH 05/62] fix: resolve critical vulnerability V-004 (#6093) Automatically generated security fix Co-authored-by: orbisai0security --- astrbot/dashboard/routes/backup.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/astrbot/dashboard/routes/backup.py b/astrbot/dashboard/routes/backup.py index 952806beb..49722c6a3 100644 --- a/astrbot/dashboard/routes/backup.py +++ b/astrbot/dashboard/routes/backup.py @@ -977,7 +977,17 @@ class BackupRoute(Route): if not jwt_secret: return Response().error("服务器配置错误").__dict__ - jwt.decode(token, jwt_secret, algorithms=["HS256"]) + # Verify JWT token with strict security options + jwt.decode( + token, + jwt_secret, + algorithms=["HS256"], + options={ + "require": ["exp"], # Require expiration claim + "verify_signature": True, # Explicitly verify signature + "verify_exp": True, # Verify expiration + } + ) except jwt.ExpiredSignatureError: return Response().error("Token 已过期,请刷新页面后重试").__dict__ except jwt.InvalidTokenError: From 7b43448ce4d414f558439ff8fdbb6833ebee1bae Mon Sep 17 00:00:00 2001 From: Stable Genius Date: Thu, 12 Mar 2026 08:57:45 -0700 Subject: [PATCH 06/62] fix: prefer named weekday cron examples (#6091) Co-authored-by: stablegenius49 <185121704+stablegenius49@users.noreply.github.com> --- astrbot/core/tools/cron_tools.py | 2 +- tests/unit/test_cron_tools.py | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 tests/unit/test_cron_tools.py diff --git a/astrbot/core/tools/cron_tools.py b/astrbot/core/tools/cron_tools.py index d504f128a..b939b53fa 100644 --- a/astrbot/core/tools/cron_tools.py +++ b/astrbot/core/tools/cron_tools.py @@ -30,7 +30,7 @@ class CreateActiveCronTool(FunctionTool[AstrAgentContext]): "properties": { "cron_expression": { "type": "string", - "description": "Cron expression defining recurring schedule (e.g., '0 8 * * *').", + "description": "Cron expression defining recurring schedule (e.g., '0 8 * * *' or '0 23 * * mon-fri'). Prefer named weekdays like 'mon-fri' or 'sat,sun' instead of numeric day-of-week ranges such as '1-5' to avoid ambiguity across cron implementations.", }, "run_at": { "type": "string", diff --git a/tests/unit/test_cron_tools.py b/tests/unit/test_cron_tools.py new file mode 100644 index 000000000..25f721248 --- /dev/null +++ b/tests/unit/test_cron_tools.py @@ -0,0 +1,15 @@ +"""Tests for cron tool metadata.""" + +from astrbot.core.tools.cron_tools import CreateActiveCronTool + + +def test_create_future_task_cron_description_prefers_named_weekdays(): + """The cron tool should steer users toward unambiguous named weekdays.""" + tool = CreateActiveCronTool() + + description = tool.parameters["properties"]["cron_expression"]["description"] + + assert "mon-fri" in description + assert "sat,sun" in description + assert "1-5" in description + assert "avoid ambiguity" in description From c0e4f1e11469370621fb37fee013799e6ac29f07 Mon Sep 17 00:00:00 2001 From: Stable Genius Date: Thu, 12 Mar 2026 09:02:45 -0700 Subject: [PATCH 07/62] fix(dashboard): restore README dialog anchor navigation (#6083) Co-authored-by: stablegenius49 <185121704+stablegenius49@users.noreply.github.com> --- .../src/components/shared/ReadmeDialog.vue | 70 ++++++++++++++++--- 1 file changed, 59 insertions(+), 11 deletions(-) diff --git a/dashboard/src/components/shared/ReadmeDialog.vue b/dashboard/src/components/shared/ReadmeDialog.vue index ddc27cd90..f7c2d2faf 100644 --- a/dashboard/src/components/shared/ReadmeDialog.vue +++ b/dashboard/src/components/shared/ReadmeDialog.vue @@ -48,6 +48,24 @@ const loading = ref(false); const isEmpty = ref(false); const copyFeedbackTimer = ref(null); const lastRequestId = ref(0); +const scrollContainer = ref(null); + +function slugifyHeading(text, slugCounts) { + const base = (text || "") + .trim() + .toLowerCase() + .normalize("NFKD") + .replace(/[\u0300-\u036f]/g, "") + .replace(/[^\p{Letter}\p{Number}\s-]/gu, "") + .replace(/\s+/g, "-") + .replace(/-+/g, "-"); + + if (!base) return ""; + + const count = slugCounts.get(base) || 0; + slugCounts.set(base, count + 1); + return count === 0 ? base : `${base}-${count}`; +} onUnmounted(() => { if (copyFeedbackTimer.value) clearTimeout(copyFeedbackTimer.value); @@ -153,6 +171,18 @@ const renderedHtml = computed(() => { // 3. 后处理方案:完全隔离,安全性最高 const tempDiv = document.createElement("div"); tempDiv.innerHTML = cleanHtml; + + const slugCounts = new Map(); + tempDiv.querySelectorAll("h1, h2, h3, h4, h5, h6").forEach((heading) => { + if (heading.id) { + slugCounts.set(heading.id, (slugCounts.get(heading.id) || 0) + 1); + return; + } + + const slug = slugifyHeading(heading.textContent, slugCounts); + if (slug) heading.id = slug; + }); + tempDiv.querySelectorAll("a").forEach((link) => { const href = link.getAttribute("href"); // 强制所有外部链接使用安全的 _blank 策略 @@ -251,18 +281,35 @@ watch( function handleContainerClick(event) { const btn = event.target.closest(".copy-code-btn"); - if (!btn) return; - const code = btn.closest(".code-block-wrapper")?.querySelector("code"); - if (code) { - if (navigator.clipboard?.writeText) { - navigator.clipboard - .writeText(code.textContent) - .then(() => showCopyFeedback(btn, true)) - .catch(() => tryFallbackCopy(code.textContent, btn)); - } else { - tryFallbackCopy(code.textContent, btn); + if (btn) { + const code = btn.closest(".code-block-wrapper")?.querySelector("code"); + if (code) { + if (navigator.clipboard?.writeText) { + navigator.clipboard + .writeText(code.textContent) + .then(() => showCopyFeedback(btn, true)) + .catch(() => tryFallbackCopy(code.textContent, btn)); + } else { + tryFallbackCopy(code.textContent, btn); + } } + return; } + + const anchor = event.target.closest('a[href^="#"]'); + if (!anchor) return; + + const rawHref = anchor.getAttribute("href"); + const targetId = rawHref ? decodeURIComponent(rawHref.slice(1)) : ""; + if (!targetId) return; + + const target = scrollContainer.value?.querySelector( + `#${CSS.escape(targetId)}`, + ); + if (!target) return; + + event.preventDefault(); + target.scrollIntoView({ behavior: "smooth", block: "start" }); } function tryFallbackCopy(text, btn) { @@ -326,7 +373,7 @@ const showActionArea = computed(() => { mdi-close - +
{ margin-bottom: 16px; font-weight: 600; line-height: 1.25; + scroll-margin-top: 12px; } :deep(.markdown-body h1) { From 89cc8a1a65ed1e1f3b45d9ce7852f75b9ecefe73 Mon Sep 17 00:00:00 2001 From: Aleksandr Date: Thu, 12 Mar 2026 19:08:37 +0300 Subject: [PATCH 08/62] feat: add Russian translation (#6081) * feat: add Russian translation * revert: remove auth route changes from PR --- dashboard/src/i18n/composables.ts | 53 +- .../en-US/features/config-metadata.json | 1 + .../src/i18n/locales/ru-RU/core/actions.json | 24 + .../src/i18n/locales/ru-RU/core/common.json | 133 ++ .../src/i18n/locales/ru-RU/core/header.json | 108 ++ .../i18n/locales/ru-RU/core/navigation.json | 49 + .../src/i18n/locales/ru-RU/core/shared.json | 111 ++ .../src/i18n/locales/ru-RU/core/status.json | 22 + .../i18n/locales/ru-RU/features/about.json | 17 + .../locales/ru-RU/features/alkaid/index.json | 44 + .../ru-RU/features/alkaid/knowledge-base.json | 155 ++ .../locales/ru-RU/features/alkaid/memory.json | 97 ++ .../src/i18n/locales/ru-RU/features/auth.json | 14 + .../i18n/locales/ru-RU/features/chart.json | 4 + .../src/i18n/locales/ru-RU/features/chat.json | 146 ++ .../i18n/locales/ru-RU/features/command.json | 95 + .../ru-RU/features/config-metadata.json | 1526 +++++++++++++++++ .../i18n/locales/ru-RU/features/config.json | 129 ++ .../i18n/locales/ru-RU/features/console.json | 18 + .../locales/ru-RU/features/conversation.json | 102 ++ .../src/i18n/locales/ru-RU/features/cron.json | 66 + .../locales/ru-RU/features/dashboard.json | 65 + .../locales/ru-RU/features/extension.json | 358 ++++ .../ru-RU/features/knowledge-base/detail.json | 118 ++ .../features/knowledge-base/document.json | 55 + .../ru-RU/features/knowledge-base/index.json | 67 + .../locales/ru-RU/features/migration.json | 18 + .../i18n/locales/ru-RU/features/persona.json | 146 ++ .../i18n/locales/ru-RU/features/platform.json | 135 ++ .../i18n/locales/ru-RU/features/provider.json | 151 ++ .../ru-RU/features/session-management.json | 130 ++ .../i18n/locales/ru-RU/features/settings.json | 180 ++ .../i18n/locales/ru-RU/features/subagent.json | 65 + .../i18n/locales/ru-RU/features/tool-use.json | 195 +++ .../i18n/locales/ru-RU/features/trace.json | 10 + .../i18n/locales/ru-RU/features/welcome.json | 37 + .../i18n/locales/ru-RU/messages/errors.json | 39 + .../i18n/locales/ru-RU/messages/success.json | 23 + .../locales/ru-RU/messages/validation.json | 25 + dashboard/src/i18n/translations.ts | 92 +- 40 files changed, 4796 insertions(+), 27 deletions(-) create mode 100644 dashboard/src/i18n/locales/ru-RU/core/actions.json create mode 100644 dashboard/src/i18n/locales/ru-RU/core/common.json create mode 100644 dashboard/src/i18n/locales/ru-RU/core/header.json create mode 100644 dashboard/src/i18n/locales/ru-RU/core/navigation.json create mode 100644 dashboard/src/i18n/locales/ru-RU/core/shared.json create mode 100644 dashboard/src/i18n/locales/ru-RU/core/status.json create mode 100644 dashboard/src/i18n/locales/ru-RU/features/about.json create mode 100644 dashboard/src/i18n/locales/ru-RU/features/alkaid/index.json create mode 100644 dashboard/src/i18n/locales/ru-RU/features/alkaid/knowledge-base.json create mode 100644 dashboard/src/i18n/locales/ru-RU/features/alkaid/memory.json create mode 100644 dashboard/src/i18n/locales/ru-RU/features/auth.json create mode 100644 dashboard/src/i18n/locales/ru-RU/features/chart.json create mode 100644 dashboard/src/i18n/locales/ru-RU/features/chat.json create mode 100644 dashboard/src/i18n/locales/ru-RU/features/command.json create mode 100644 dashboard/src/i18n/locales/ru-RU/features/config-metadata.json create mode 100644 dashboard/src/i18n/locales/ru-RU/features/config.json create mode 100644 dashboard/src/i18n/locales/ru-RU/features/console.json create mode 100644 dashboard/src/i18n/locales/ru-RU/features/conversation.json create mode 100644 dashboard/src/i18n/locales/ru-RU/features/cron.json create mode 100644 dashboard/src/i18n/locales/ru-RU/features/dashboard.json create mode 100644 dashboard/src/i18n/locales/ru-RU/features/extension.json create mode 100644 dashboard/src/i18n/locales/ru-RU/features/knowledge-base/detail.json create mode 100644 dashboard/src/i18n/locales/ru-RU/features/knowledge-base/document.json create mode 100644 dashboard/src/i18n/locales/ru-RU/features/knowledge-base/index.json create mode 100644 dashboard/src/i18n/locales/ru-RU/features/migration.json create mode 100644 dashboard/src/i18n/locales/ru-RU/features/persona.json create mode 100644 dashboard/src/i18n/locales/ru-RU/features/platform.json create mode 100644 dashboard/src/i18n/locales/ru-RU/features/provider.json create mode 100644 dashboard/src/i18n/locales/ru-RU/features/session-management.json create mode 100644 dashboard/src/i18n/locales/ru-RU/features/settings.json create mode 100644 dashboard/src/i18n/locales/ru-RU/features/subagent.json create mode 100644 dashboard/src/i18n/locales/ru-RU/features/tool-use.json create mode 100644 dashboard/src/i18n/locales/ru-RU/features/trace.json create mode 100644 dashboard/src/i18n/locales/ru-RU/features/welcome.json create mode 100644 dashboard/src/i18n/locales/ru-RU/messages/errors.json create mode 100644 dashboard/src/i18n/locales/ru-RU/messages/success.json create mode 100644 dashboard/src/i18n/locales/ru-RU/messages/validation.json diff --git a/dashboard/src/i18n/composables.ts b/dashboard/src/i18n/composables.ts index 131010bc3..c67d74903 100644 --- a/dashboard/src/i18n/composables.ts +++ b/dashboard/src/i18n/composables.ts @@ -11,7 +11,7 @@ const translations = ref>({}); */ export async function initI18n(locale: Locale = 'zh-CN') { currentLocale.value = locale; - + // 加载静态翻译数据 loadTranslations(locale); } @@ -50,7 +50,7 @@ export function useI18n() { const t = (key: string, params?: Record): string => { const keys = key.split('.'); let value: any = translations.value; - + // 遍历键路径 for (const k of keys) { if (value && typeof value === 'object' && k in value) { @@ -61,35 +61,35 @@ export function useI18n() { return `[MISSING: ${key}]`; } } - + if (typeof value !== 'string') { console.warn(`Translation value is not string: ${key}`, value); // 返回带括号的键名,便于在开发时识别类型错误的翻译 return `[INVALID: ${key}]`; } - + // 此时value确定是string类型 let result: string = value; - + // 处理参数插值 if (params) { result = result.replace(/\{(\w+)\}/g, (match: string, paramKey: string) => { return params[paramKey]?.toString() || match; }); } - + return result; }; - + // 切换语言 const setLocale = async (newLocale: Locale) => { if (newLocale !== currentLocale.value) { currentLocale.value = newLocale; loadTranslations(newLocale); - + // 保存到localStorage localStorage.setItem('astrbot-locale', newLocale); - + // 触发自定义事件,通知相关页面重新加载配置数据 // 这是因为插件适配器的 i18n 数据是通过后端 API 注入的, // 需要根据 Accept-Language 头重新获取 @@ -98,16 +98,16 @@ export function useI18n() { })); } }; - + // 获取当前语言 const locale = computed(() => currentLocale.value); - + // 获取可用语言列表 - const availableLocales: Locale[] = ['zh-CN', 'en-US']; - + const availableLocales: Locale[] = ['zh-CN', 'en-US', 'ru-RU']; + // 检查是否已加载 const isLoaded = computed(() => Object.keys(translations.value).length > 0); - + return { t, locale, @@ -122,13 +122,13 @@ export function useI18n() { */ export function useModuleI18n(moduleName: string) { const { t } = useI18n(); - + const tm = (key: string, params?: Record): string => { // 将斜杠转换为点号以匹配嵌套对象结构 const normalizedModuleName = moduleName.replace(/\//g, '.'); return t(`${normalizedModuleName}.${key}`, params); }; - + // 获取原始翻译值(可能是字符串、数组或对象) const getRaw = (key: string): any => { const normalizedModuleName = moduleName.replace(/\//g, '.'); @@ -143,10 +143,10 @@ export function useModuleI18n(moduleName: string) { return null; } } - + return value; }; - + return { tm, getRaw }; } @@ -155,20 +155,21 @@ export function useModuleI18n(moduleName: string) { */ export function useLanguageSwitcher() { const { locale, setLocale, availableLocales } = useI18n(); - + const languageOptions = computed(() => [ { value: 'zh-CN', label: '简体中文', flag: '🇨🇳' }, - { value: 'en-US', label: 'English', flag: '🇺🇸' } + { value: 'en-US', label: 'English', flag: '🇺🇸' }, + { value: 'ru-RU', label: 'Русский', flag: '🇷🇺' } ]); - + const currentLanguage = computed(() => { return languageOptions.value.find(lang => lang.value === locale.value); }); - + const switchLanguage = async (newLocale: Locale) => { await setLocale(newLocale); }; - + return { locale, languageOptions, @@ -220,9 +221,9 @@ function deepMerge(target: Record, source: Record) { export async function setupI18n() { // 从localStorage获取保存的语言设置 const savedLocale = localStorage.getItem('astrbot-locale') as Locale; - const initialLocale = savedLocale && ['zh-CN', 'en-US'].includes(savedLocale) - ? savedLocale + const initialLocale = savedLocale && ['zh-CN', 'en-US', 'ru-RU'].includes(savedLocale) + ? savedLocale : 'zh-CN'; - + await initI18n(initialLocale); } \ No newline at end of file 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 3a8251cb9..5688b4e45 100644 --- a/dashboard/src/i18n/locales/en-US/features/config-metadata.json +++ b/dashboard/src/i18n/locales/en-US/features/config-metadata.json @@ -78,6 +78,7 @@ }, "persona": { "description": "Persona", + "hint": "Set the default persona for AI conversations. Personas can be managed in the Persona tab.", "provider_settings": { "default_personality": { "description": "Default Persona" diff --git a/dashboard/src/i18n/locales/ru-RU/core/actions.json b/dashboard/src/i18n/locales/ru-RU/core/actions.json new file mode 100644 index 000000000..9e91e1143 --- /dev/null +++ b/dashboard/src/i18n/locales/ru-RU/core/actions.json @@ -0,0 +1,24 @@ +{ + "create": "Создать", + "read": "Чтение", + "update": "Обновить", + "delete": "Удалить", + "search": "Поиск", + "filter": "Фильтр", + "sort": "Сортировка", + "export": "Экспорт", + "import": "Импорт", + "backup": "Резервное копирование", + "restore": "Восстановление", + "copy": "Копировать", + "paste": "Вставить", + "cut": "Вырезать", + "undo": "Отменить", + "redo": "Повторить", + "refresh": "Обновить", + "submit": "Отправить", + "reset": "Сбросить", + "clear": "Очистить", + "save": "Сохранить", + "close": "Закрыть" +} \ No newline at end of file diff --git a/dashboard/src/i18n/locales/ru-RU/core/common.json b/dashboard/src/i18n/locales/ru-RU/core/common.json new file mode 100644 index 000000000..e9f0d4a11 --- /dev/null +++ b/dashboard/src/i18n/locales/ru-RU/core/common.json @@ -0,0 +1,133 @@ +{ + "save": "Сохранить", + "cancel": "Отмена", + "close": "Закрыть", + "copy": "Копировать", + "copied": "Скопировано", + "copyFailed": "Ошибка копирования", + "delete": "Удалить", + "edit": "Редактировать", + "add": "Добавить", + "confirm": "Подтвердить", + "loading": "Загрузка...", + "success": "Успешно", + "error": "Ошибка", + "warning": "Внимание", + "info": "Информация", + "name": "Имя", + "description": "Описание", + "author": "Автор", + "status": "Статус", + "actions": "Действия", + "enable": "Включить", + "disable": "Выключить", + "enabled": "Включено", + "disabled": "Выключено", + "reload": "Перезагрузить", + "configure": "Настроить", + "install": "Установить", + "uninstall": "Удалить", + "update": "Обновить", + "language": "Язык", + "settings": "Настройки", + "locale": "JSON", + "type": "Тип", + "press": "Нажмите", + "longPress": "Долгое нажатие", + "yes": "Да", + "no": "Нет", + "imagePreview": "Предпросмотр изображения", + "autoDetect": "Автоопределение", + "dialog": { + "confirmTitle": "Подтверждение", + "confirmMessage": "Вы уверены, что хотите выполнить это действие?", + "confirmButton": "ОК", + "cancelButton": "Отмена" + }, + "restart": { + "waiting": "Ожидание перезагрузки AstrBot...", + "maxRetriesReached": "Превышено количество попыток проверки статуса. Пожалуйста, проверьте вручную." + }, + "readme": { + "title": "Документация плагина", + "buttons": { + "viewOnGithub": "Открыть репозиторий на GitHub", + "refresh": "Обновить" + }, + "loading": "Загрузка README...", + "errors": { + "fetchFailed": "Не удалось загрузить README", + "fetchError": "Произошла ошибка при загрузке README" + }, + "empty": { + "title": "У этого плагина нет ссылки на документацию или репозиторий GitHub.", + "subtitle": "Пожалуйста, посетите магазин плагинов или свяжитесь с автором для получения дополнительной информации." + } + }, + "changelog": { + "title": "Журнал изменений", + "loading": "Загрузка журнала изменений...", + "empty": { + "title": "У этого плагина нет журнала изменений", + "subtitle": "Разработчики могут добавить файл CHANGELOG.md в директорию плагина" + } + }, + "editor": { + "fullscreen": "На весь экран", + "editingTitle": "Редактирование содержимого" + }, + "templateList": { + "addEntry": "Добавить запись", + "empty": "Записей нет, выберите шаблон для добавления", + "missingTemplate": "Шаблон не найден, пожалуйста, удалите и добавьте заново.", + "unknownTemplate": "Неизвестный шаблон" + }, + "list": { + "addItemPlaceholder": "Добавьте новый элемент и нажмите Enter", + "addButton": "Добавить", + "addMore": "Добавить еще", + "batchImport": "Массовый импорт", + "batchImportTitle": "Массовый импорт", + "batchImportLabel": "Один элемент на строку", + "batchImportPlaceholder": "Например:\nЭлемент 1\nЭлемент 2\nЭлемент 3", + "batchImportHint": "Каждая строка будет считаться отдельным элементом. Пустые строки игнорируются.", + "batchImportButton": "Импортировать {count} эл.", + "noItems": "Список пуст", + "noItemsHint": "Элементов нет. Напишите что-нибудь выше и нажмите Enter.", + "inputPlaceholder": "Введите текст и нажмите Enter", + "editTitle": "Изменить элемент", + "modifyButton": "Изменить" + }, + "itemCard": { + "enabled": "Включено", + "disabled": "Выключено", + "delete": "Удалить", + "edit": "Изменить", + "copy": "Копировать", + "noData": "Нет данных" + }, + "objectEditor": { + "dialogTitle": "Изменение пар ключ-значение", + "noItems": "Нет элементов", + "noParams": "Нет параметров", + "presets": "Пресеты", + "newKeyLabel": "Имя ключа", + "valueTypeLabel": "Тип значения", + "keyExists": "Ключ уже существует", + "invalidJson": "Некорректный формат JSON", + "placeholders": { + "keyName": "Ключ", + "stringValue": "Строка", + "numberValue": "Число", + "jsonValue": "JSON" + } + }, + "firstNotice": { + "title": "Первичная информация", + "loading": "Загрузка информации...", + "empty": { + "title": "Нет информации для отображения", + "subtitle": "Файл FIRST_NOTICE.md не найден или пуст." + } + } +} \ No newline at end of file diff --git a/dashboard/src/i18n/locales/ru-RU/core/header.json b/dashboard/src/i18n/locales/ru-RU/core/header.json new file mode 100644 index 000000000..a23f3dfd3 --- /dev/null +++ b/dashboard/src/i18n/locales/ru-RU/core/header.json @@ -0,0 +1,108 @@ +{ + "logoTitle": "Панель управления AstrBot", + "version": { + "hasNewVersion": "Доступна новая версия AstrBot!", + "dashboardHasNewVersion": "Доступна новая версия WebUI!" + }, + "buttons": { + "update": "Обновить", + "account": "Аккаунт", + "theme": { + "light": "Светлая тема", + "dark": "Темная тема" + } + }, + "updateDialog": { + "title": "Обновить AstrBot", + "currentVersion": "Текущая версия", + "status": { + "checking": "Проверка обновлений...", + "switching": "Переключение версии...", + "updating": "Обновление..." + }, + "tabs": { + "release": "😊 Релиз" + }, + "updateToLatest": "Обновить до последней версии", + "preRelease": "Предварительная версия", + "preReleaseWarning": { + "title": "Внимание: предварительная версия", + "description": "Версии с меткой Pre-release могут содержать неизвестные ошибки. Не рекомендуется использовать в рабочих средах. Если вы обнаружили ошибку, пожалуйста, сообщите о ней в ", + "issueLink": "GitHub Issues" + }, + "tip": "💡 ПОДСКАЗКА: ", + "tipContinue": "По умолчанию при переключении версии загружаются соответствующие файлы WebUI. Код WebUI находится в директории dashboard, вы можете собрать его самостоятельно с помощью npm.", + "dockerTip": "При переключении версии будет предпринята попытка обновить как основной процесс бота, так и панель управления. Если вы используете Docker, вы также можете обновить образ или использовать", + "dockerTipLink": "watchtower", + "dockerTipContinue": "для автоматического мониторинга и обновления.", + "table": { + "tag": "Тег", + "publishDate": "Дата публикации", + "content": "Содержание", + "sourceUrl": "Исходный код", + "actions": "Действия", + "view": "Просмотр", + "switch": "Переключить" + }, + "releaseNotes": { + "title": "Журнал изменений" + }, + "redirectConfirm": { + "title": "Переход по ссылке", + "message": "Вы будете перенаправлены на страницу GitHub Releases. Продолжить?", + "latestLabel": "Последняя версия", + "targetVersion": "Целевая версия:", + "currentVersion": "Текущая версия:", + "guideTitle": "Рекомендации после перехода:", + "guideStep1": "Загрузите пакет, соответствующий архитектуре вашей системы.", + "guideStep2": "После завершения установки перезапустите AstrBot.", + "guideStep3": "Если вы используете Docker, отдайте приоритет обновлению через образ." + }, + "desktopApp": { + "title": "Обновить десктопное приложение", + "message": "Проверка и обновление десктопной версии AstrBot.", + "currentVersion": "Текущая версия:", + "latestVersion": "Последняя версия:", + "checking": "Проверка обновлений десктопного приложения...", + "hasNewVersion": "Найдена новая версия. Нажмите для подтверждения обновления.", + "isLatest": "Установлена последняя версия", + "installing": "Загрузка и установка обновления... Приложение будет перезапущено автоматически.", + "checkFailed": "Ошибка проверки обновлений. Попробуйте позже.", + "installFailed": "Ошибка обновления. Попробуйте позже." + }, + "dashboardUpdate": { + "title": "Обновить только панель управления", + "currentVersion": "Текущая версия", + "hasNewVersion": "Доступна новая версия!", + "isLatest": "Установлена последняя версия.", + "downloadAndUpdate": "Скачать и обновить" + } + }, + "accountDialog": { + "title": "Изменить аккаунт", + "securityWarning": "Безопасность: Пожалуйста, смените пароль по умолчанию для защиты аккаунта", + "form": { + "currentPassword": "Текущий пароль", + "newPassword": "Новый пароль", + "confirmPassword": "Подтвердите новый пароль", + "newUsername": "Новое имя пользователя (опционально)", + "passwordHint": "Пароль должен быть не менее 8 символов", + "confirmPasswordHint": "Введите новый пароль еще раз", + "usernameHint": "Оставьте пустым, если не хотите менять имя пользователя", + "defaultCredentials": "Логин и пароль по умолчанию: astrbot" + }, + "validation": { + "passwordRequired": "Введите пароль", + "passwordMinLength": "Пароль должен быть не менее 8 символов", + "passwordMatch": "Паролы не совпадают", + "usernameMinLength": "Имя пользователя должно быть не менее 3 символов" + }, + "actions": { + "save": "Сохранить изменения", + "cancel": "Отмена" + }, + "messages": { + "updateFailed": "Ошибка обновления, попробуйте еще раз" + } + } +} \ No newline at end of file diff --git a/dashboard/src/i18n/locales/ru-RU/core/navigation.json b/dashboard/src/i18n/locales/ru-RU/core/navigation.json new file mode 100644 index 000000000..2abb189f4 --- /dev/null +++ b/dashboard/src/i18n/locales/ru-RU/core/navigation.json @@ -0,0 +1,49 @@ +{ + "welcome": "Добро пожаловать", + "dashboard": "Статистика", + "platforms": "Боты", + "providers": "Провайдеры моделей", + "commands": "Команды", + "persona": "Персонажи", + "subagent": "Субагенты", + "toolUse": "Инструменты MCP", + "extension": "Плагины", + "extensionTabs": { + "installed": "Плагины AstrBot", + "market": "Магазин плагинов", + "mcp": "Серверы MCP", + "skills": "Навыки", + "components": "Управление поведением" + }, + "config": "Конфигурация", + "chat": "Чат", + "cron": "Запланированные задачи", + "conversation": "Данные диалогов", + "sessionManagement": "Пользовательские правила", + "console": "Логи платформы", + "trace": "Трассировка", + "alkaid": "Alkaid Lab", + "knowledgeBase": "База знаний", + "about": "О программе", + "settings": "Настройки", + "changelog": "Журнал изменений", + "documentation": "Документация", + "faq": "FAQ", + "github": "GitHub", + "drag": "Перетащить", + "groups": { + "more": "Дополнительно" + }, + "changelogDialog": { + "title": "Журнал изменений", + "loading": "Загрузка...", + "error": "Ошибка загрузки", + "notFound": "Журнал изменений для этой версии не найден", + "selectVersion": "Выберите версию", + "current": "Текущая" + }, + "configTabs": { + "normal": "Обычная конфигурация", + "system": "Системная конфигурация" + } +} \ No newline at end of file diff --git a/dashboard/src/i18n/locales/ru-RU/core/shared.json b/dashboard/src/i18n/locales/ru-RU/core/shared.json new file mode 100644 index 000000000..e59705a4b --- /dev/null +++ b/dashboard/src/i18n/locales/ru-RU/core/shared.json @@ -0,0 +1,111 @@ +{ + "knowledgeBaseSelector": { + "notSelected": "Не выбрано", + "buttonText": "Выбрать базу знаний...", + "dialogTitle": "Выбор базы знаний", + "loading": "Загрузка...", + "noKnowledgeBases": "Базы знаний не найдены", + "createKnowledgeBase": "Создать базу знаний", + "selectedCount": "Выбрано баз знаний: {count}", + "confirmSelection": "ОК", + "cancelSelection": "Отмена", + "noDescription": "Нет описания", + "documentCount": "Документов: {count}", + "chunkCount": "Фрагментов: {count}" + }, + "pluginSetSelector": { + "notSelected": "Плагины не включены", + "allPlugins": "Включить все плагины (*)", + "selectedCount": "Выбрано плагинов: {count}", + "buttonText": "Выбрать набор плагинов...", + "dialogTitle": "Выбор набора плагинов", + "loading": "Загрузка...", + "enableAll": "Включить все", + "enableNone": "Ничего не включать", + "customSelect": "Настроить выбор", + "noPlugins": "Доступных плагинов нет", + "confirmSelection": "ОК", + "cancelSelection": "Отмена", + "noDescription": "Нет описания", + "notActivated": "Не активирован", + "note": "*Системные и уже выключенные в настройках плагины не отображаются.", + "selectedPluginsLabel": "Выбранные плагины:", + "allPluginsLabel": "Все плагины" + }, + "providerSelector": { + "notSelected": "Не выбрано", + "buttonText": "Выбрать провайдера...", + "dialogTitle": "Выбор провайдера", + "loading": "Загрузка...", + "noProviders": "Доступных провайдеров нет", + "confirmSelection": "ОК", + "cancelSelection": "Отмена", + "clearSelection": "Сбросить выбор", + "clearSelectionSubtitle": "Очистить текущий выбор", + "unknownType": "Неизвестный тип", + "createProvider": "Создать провайдера", + "manageProviders": "Управление провайдерами", + "selectProviderPool": "Выбрать пул провайдеров...", + "selectedCount": "Выбрано провайдеров: {count}" + }, + "personaSelector": { + "notSelected": "Не выбрано", + "defaultPersona": "Персонаж по умолчанию", + "buttonText": "Выбрать персонажа...", + "editPersona": "Изменить текущего персонажа", + "dialogTitle": "Выбор персонажа", + "noDescription": "Нет описания", + "noPersonas": "Доступных персонажей нет", + "createPersona": "Создать персонажа", + "cancelSelection": "Отмена", + "confirmSelection": "ОК", + "selectPersonaPool": "Выбрать пул персонажей...", + "rootFolder": "Все персонажи", + "emptyFolder": "Папка пуста" + }, + "personaQuickPreview": { + "title": "Быстрый просмотр", + "loading": "Загрузка...", + "noPersonaSelected": "Персонаж не выбран", + "personaNotFound": "Информация о персонаже не найдена", + "systemPromptLabel": "Системный промпт", + "toolsLabel": "Инструменты", + "skillsLabel": "Навыки (Skills)", + "originLabel": "Источник", + "originNameLabel": "Имя источника", + "toolInactive": "Выключено", + "toolInactiveTooltip": "Этот инструмент выключен. Включите его в Плагины -> Управление поведением -> Функции.", + "allTools": "Доступны все инструменты", + "allToolsWithCount": "Доступны все инструменты ({count})", + "noTools": "Инструменты не настроены", + "allSkills": "Доступны все навыки (Skills)", + "allSkillsWithCount": "Доступны все навыки ({count})", + "noSkills": "Навыки (Skills) не настроены" + }, + "t2iTemplateEditor": { + "buttonText": "Настроить T2I шаблон", + "dialogTitle": "Настройка HTML шаблона Text-to-Image", + "newTemplateNameLabel": "Введите имя нового шаблона", + "nameRequired": "Имя обязательно для заполнения", + "selectTemplateLabel": "Выбрать шаблон", + "applied": "Применено", + "apply": "Применить", + "templateEditor": "Редактор шаблона", + "new": "Создать", + "resetBase": "Сбросить 'base'", + "delete": "Удалить", + "save": "Сохранить", + "livePreview": "Предпросмотр (может отличаться)", + "refreshPreview": "Обновить", + "syntaxHint": "Поддерживается синтаксис jinja2. Переменные: text | safe (текст для рендеринга), version (версия AstrBot)", + "saveAndApply": "Сохранить и применить текущий шаблон", + "confirmReset": "Подтверждение сброса", + "confirmResetMessage": "Вы уверены, что хотите сбросить шаблон 'base' до значений по умолчанию? Все несохраненные изменения будут потеряны. Это действие необратимо.", + "confirmResetButton": "Сбросить", + "confirmDelete": "Подтверждение удаления", + "confirmDeleteMessage": "Вы уверены, что хотите удалить шаблон '{name}'? Это действие необратимо.", + "confirmDeleteButton": "Удалить", + "confirmAction": "Подтверждение действия", + "confirmApplyMessage": "Вы уверены, что хотите сохранить изменения в '{name}' и сделать его активным шаблоном?" + } +} \ No newline at end of file diff --git a/dashboard/src/i18n/locales/ru-RU/core/status.json b/dashboard/src/i18n/locales/ru-RU/core/status.json new file mode 100644 index 000000000..a016dae17 --- /dev/null +++ b/dashboard/src/i18n/locales/ru-RU/core/status.json @@ -0,0 +1,22 @@ +{ + "loading": "Загрузка", + "success": "Успешно", + "error": "Ошибка", + "warning": "Внимание", + "info": "Информация", + "pending": "В ожидании", + "processing": "В процессе", + "completed": "Завершено", + "failed": "Ошибка", + "cancelled": "Отменено", + "timeout": "Тайм-аут", + "connecting": "Подключение", + "connected": "Подключено", + "disconnected": "Отключено", + "online": "В сети", + "offline": "Не в сети", + "active": "Активен", + "inactive": "Неактивен", + "ready": "Готов", + "busy": "Занят" +} \ No newline at end of file diff --git a/dashboard/src/i18n/locales/ru-RU/features/about.json b/dashboard/src/i18n/locales/ru-RU/features/about.json new file mode 100644 index 000000000..d9ef90b4f --- /dev/null +++ b/dashboard/src/i18n/locales/ru-RU/features/about.json @@ -0,0 +1,17 @@ +{ + "hero": { + "title": "AstrBot", + "subtitle": "Проект, рожденный из интереса и любви ❤️", + "starButton": "Star этот проект! 🌟", + "issueButton": "Сообщить об ошибке" + }, + "contributors": { + "title": "Контрибьюторы", + "description": "Этот проект поддерживается участниками open-source сообщества. Спасибо каждому за вклад!", + "viewLink": "Посмотреть всех участников" + }, + "stats": { + "title": "Глобальное развертывание", + "license": "AstrBot распространяется по лицензии AGPL v3" + } +} \ No newline at end of file diff --git a/dashboard/src/i18n/locales/ru-RU/features/alkaid/index.json b/dashboard/src/i18n/locales/ru-RU/features/alkaid/index.json new file mode 100644 index 000000000..ece76056a --- /dev/null +++ b/dashboard/src/i18n/locales/ru-RU/features/alkaid/index.json @@ -0,0 +1,44 @@ +{ + "title": "Лаборатория Alkaid", + "subtitle": "Исследуйте передовые возможности AI", + "comingSoon": "Этот мир еще впереди, заходите позже!", + "page": { + "title": "Проект Alkaid.", + "subtitle": "AstrBot Alpha Project", + "navigation": { + "knowledgeBase": "База знаний (Плагин)", + "longTermMemory": "Долгосрочная память", + "other": "..." + } + }, + "features": { + "knowledgeBase": "База знаний", + "longTermMemory": "Долгосрочная память", + "advancedChat": "Продвинутый чат", + "multiModal": "Мультимодальность" + }, + "status": { + "experimental": "Экспериментально", + "beta": "Бета", + "stable": "Стабильно", + "deprecated": "Устарело" + }, + "sigma": { + "subtitle": "Экспериментальный проект AstrBot", + "visualization": "Визуализация", + "filterUserId": "Фильтр по User ID", + "filter": "Фильтр", + "resetFilter": "Сброс", + "refreshGraph": "Обновить граф", + "nodeDetails": "Детали узла", + "id": "ID", + "type": "Тип", + "name": "Имя", + "userId": "ID пользователя", + "timestamp": "Метка времени", + "graphStats": "Статистика графа", + "nodeCount": "Узлов", + "edgeCount": "Связей", + "inDevelopment": "В разработке" + } +} \ No newline at end of file diff --git a/dashboard/src/i18n/locales/ru-RU/features/alkaid/knowledge-base.json b/dashboard/src/i18n/locales/ru-RU/features/alkaid/knowledge-base.json new file mode 100644 index 000000000..3d52f2614 --- /dev/null +++ b/dashboard/src/i18n/locales/ru-RU/features/alkaid/knowledge-base.json @@ -0,0 +1,155 @@ +{ + "title": "База знаний", + "subtitle": "Управление контентом базы знаний и поиск", + "documents": { + "title": "Список документов", + "name": "Имя файла", + "size": "Размер", + "uploadTime": "Дата загрузки", + "status": "Статус", + "actions": "Действия" + }, + "management": { + "delete": "Удалить", + "preview": "Предпросмотр", + "download": "Скачать", + "reindex": "Переиндексировать" + }, + "notInstalled": { + "title": "Плагин базы знаний не установлен", + "install": "Установить сейчас" + }, + "empty": { + "title": "База знаний пуста. Создайте свою первую базу! 🙂", + "create": "Создать базу знаний" + }, + "list": { + "title": "Список баз знаний", + "create": "Создать базу знаний", + "config": "Настройка", + "checkUpdate": "Проверить обновления плагина", + "updatePlugin": "Обновить плагин до версии {version}", + "knowledgeCount": "записей", + "tips": "Совет: используйте команду /kb в чате, чтобы узнать, как пользоваться базой!" + }, + "createDialog": { + "title": "Создание базы знаний", + "nameLabel": "Название", + "descriptionLabel": "Описание", + "descriptionPlaceholder": "Краткое описание...", + "embeddingModelLabel": "Embedding модель", + "rerankModelLabel": "Rerank модель", + "providerInfo": "Провайдер: {id} | Размерность: {dimensions}", + "rerankProviderInfo": "Провайдер: {id}", + "tips": "Совет: после выбора Embedding модели не рекомендуется менять провайдера или размерность векторов, так как это сделает текущий индекс нечитаемым.", + "cancel": "Отмена", + "create": "Создать" + }, + "emojiPicker": { + "title": "Выберите иконку", + "close": "Закрыть", + "categories": { + "emotions": "Смайлы", + "animals": "Животные и природа", + "food": "Еда и напитки", + "activities": "Занятия и вещи", + "travel": "Места и путешествия", + "symbols": "Символы и флаги" + } + }, + "contentDialog": { + "title": "Управление базой знаний", + "embeddingModel": "Embedding модель", + "vectorDimension": "Размерность", + "usage": "Использование: введите «/kb use {name}» в чате", + "tabs": { + "upload": "Загрузка файлов", + "search": "Поиск", + "fromURL": "Импорт из URL" + } + }, + "upload": { + "title": "Загрузка файлов", + "subtitle": "Поддерживаются форматы txt, pdf, word, excel и др.", + "dropzone": "Перетащите файлы сюда или нажмите для выбора", + "chunkSettings": { + "title": "Настройка фрагментации (Chunking)", + "tooltip": "Размер фрагмента определяет объем текста в одном блоке. Перекрытие позволяет сохранить контекст между соседними блоками.\nМаленькие фрагменты точнее, но увеличивают объем базы.", + "chunkSizeLabel": "Размер фрагмента", + "chunkSizeHint": "Длина текста в одном блоке (пусто = по умолчанию)", + "overlapLabel": "Перекрытие", + "overlapHint": "Нахлест между соседними блоками (пусто = по умолчанию)" + }, + "upload": "Начать загрузку", + "uploading": "Загрузка..." + }, + "search": { + "queryLabel": "Поиск по базе знаний", + "queryPlaceholder": "Введите ключевые слова...", + "resultCountLabel": "Количество результатов", + "searching": "Поиск...", + "resultsTitle": "Результаты поиска", + "relevance": "Релевантность", + "noResults": "Совпадений не найдено" + }, + "deleteDialog": { + "title": "Подтверждение удаления", + "confirmText": "Вы уверены, что хотите удалить базу знаний «{name}»?", + "warning": "Это действие необратимо. Весь контент базы знаний будет навсегда удален.", + "cancel": "Отмена", + "delete": "Удалить" + }, + "messages": { + "pluginNotAvailable": "Плагин не установлен или недоступен", + "pluginNotActivated": "Плагин astrbot_plugin_knowledge_base не включен. Пожалуйста, активируйте его в разделе плагинов и перезапустите AstrBot.", + "checkPluginFailed": "Не удалось проверить плагин", + "installFailed": "Ошибка установки", + "installPluginFailed": "Не удалось установить плагин", + "getKnowledgeBaseListFailed": "Ошибка получения списка баз знаний", + "knowledgeBaseCreated": "База знаний создана", + "createFailed": "Ошибка создания", + "createKnowledgeBaseFailed": "Не удалось создать базу знаний", + "pleaseEnterKnowledgeBaseName": "Укажите название базы знаний", + "pleaseSelectFile": "Пожалуйста, сначала выберите файл", + "operationSuccess": "Успешно: {message}", + "uploadFailed": "Ошибка загрузки", + "fileUploadFailed": "Не удалось загрузить файл", + "pleaseEnterSearchContent": "Введите текст для поиска", + "noMatchingContent": "Ничего не найдено", + "searchFailed": "Ошибка поиска", + "searchKnowledgeBaseFailed": "Не удалось выполнить поиск", + "deleteTargetNotExists": "Объект для удаления не найден", + "knowledgeBaseDeleted": "База знаний удалена", + "deleteFailed": "Ошибка удаления", + "deleteKnowledgeBaseFailed": "Не удалось удалить базу знаний", + "getEmbeddingModelListFailed": "Не удалось загрузить список Embedding моделей", + "updateAvailable": "Доступна новая версия: {current} -> {latest}", + "pluginUpToDate": "У вас последняя версия плагина", + "pluginNotFoundInMarket": "Плагин не найден в магазине", + "checkUpdateFailed": "Ошибка проверки обновлений", + "updateSuccess": "Плагин успешно обновлен", + "updateFailed": "Ошибка обновления", + "updatePluginFailed": "Не удалось обновить плагин" + }, + "importFromUrl": { + "title": "Импорт из URL", + "urlLabel": "Адрес страницы", + "urlPlaceholder": "Введите URL для извлечения знаний", + "optionsTitle": "Настройки импорта", + "tooltip": "Эти параметры управляют извлечением текста из URL.\nЕсли оставить пустыми, будут использованы настройки по умолчанию.\nТекстовая очистка через LLM может занять время.", + "useLlmRepairLabel": "Исправление текста через LLM", + "useClusteringSummaryLabel": "Кластеризация и суммаризация", + "repairLlmProviderIdLabel": "Модель для очистки", + "summarizeLlmProviderIdLabel": "Модель для суммаризации", + "embeddingProviderIdLabel": "Embedding модель", + "chunkSizeLabel": "Размер фрагмента", + "chunkOverlapLabel": "Перекрытие", + "startImport": "Начать импорт", + "importing": "Импорт...", + "importSuccess": "Импортировано успешно", + "importFailed": "Ошибка импорта", + "uploadingChunks": "Текст извлечен, загрузка фрагментов...", + "preRequisite": "Примечание: сначала установите плагин astrbot_plugin_url_2_knowledge_base и выполните установку playwright согласно документации.", + "allChunksUploaded": "Все фрагменты успешно загружены" + } +} \ No newline at end of file diff --git a/dashboard/src/i18n/locales/ru-RU/features/alkaid/memory.json b/dashboard/src/i18n/locales/ru-RU/features/alkaid/memory.json new file mode 100644 index 000000000..507c6ae18 --- /dev/null +++ b/dashboard/src/i18n/locales/ru-RU/features/alkaid/memory.json @@ -0,0 +1,97 @@ +{ + "title": "Долгосрочная память", + "subtitle": "Управление памятью вашего AI-помощника", + "memories": { + "title": "Список воспоминаний", + "content": "Содержание", + "importance": "Важность", + "createTime": "Дата создания", + "lastAccess": "Последнее обращение", + "category": "Категория" + }, + "categories": { + "personal": "Личное", + "preferences": "Предпочтения", + "conversations": "История диалогов", + "facts": "Факты", + "skills": "Навыки" + }, + "importance": { + "high": "Высокая", + "medium": "Средняя", + "low": "Низкая" + }, + "actions": { + "view": "Детали", + "edit": "Изменить", + "delete": "Удалить", + "pin": "Закрепить", + "unpin": "Открепить" + }, + "filters": { + "all": "Все", + "category": "По категории", + "importance": "По важности", + "dateRange": "По периоду", + "title": "Фильтр", + "userIdLabel": "Фильтр по User ID", + "filterButton": "Применить", + "resetButton": "Сбросить", + "refreshButton": "Обновить граф" + }, + "search": { + "title": "Поиск по памяти", + "userIdLabel": "ID пользователя", + "queryLabel": "Ключевое слово", + "searchButton": "Поиск", + "resultsTitle": "Результаты поиска", + "noResults": "Ничего не найдено", + "similarity": "Сходство", + "noTextContent": "Нет текста" + }, + "addMemory": { + "title": "Добавить данные в память", + "textLabel": "Текст воспоминания", + "userIdLabel": "ID пользователя", + "summarizeLabel": "Нужна суммаризация", + "addButton": "Добавить" + }, + "nodeDetails": { + "title": "Детали узла", + "id": "ID", + "type": "Тип", + "name": "Имя", + "userId": "ID пользователя", + "timestamp": "Метка времени" + }, + "graphStats": { + "title": "Статистика графа", + "nodeCount": "Узлов", + "edgeCount": "Связей" + }, + "factDialog": { + "title": "Факт из памяти", + "id": "ID", + "docId": "ID документа", + "createdAt": "Создано", + "updatedAt": "Обновлено", + "metadata": "Метаданные", + "metadataKey": "Ключ", + "metadataValue": "Значение", + "loading": "Загрузка...", + "close": "Закрыть", + "noValue": "нет", + "unknown": "неизвестно" + }, + "messages": { + "searchQueryRequired": "Пожалуйста, введите запрос", + "searchSuccess": "Найдено записей: {count}", + "searchNoResults": "В памяти ничего не найдено", + "searchError": "Ошибка поиска", + "addSuccess": "Данные успешно добавлены в память!", + "addError": "Не удалось добавить данные", + "factDetailsError": "Ошибка загрузки деталей", + "metadataParseError": "Не удалось разобрать метаданные", + "relationNoMemoryData": "У этой связи нет ассоциированных данных" + } +} \ No newline at end of file diff --git a/dashboard/src/i18n/locales/ru-RU/features/auth.json b/dashboard/src/i18n/locales/ru-RU/features/auth.json new file mode 100644 index 000000000..d6ba05dc3 --- /dev/null +++ b/dashboard/src/i18n/locales/ru-RU/features/auth.json @@ -0,0 +1,14 @@ +{ + "login": "Вход", + "username": "Имя пользователя", + "password": "Пароль", + "defaultHint": "Логин и пароль по умолчанию: astrbot", + "logo": { + "title": "Панель управления AstrBot", + "subtitle": "Добро пожаловать" + }, + "theme": { + "switchToDark": "Перейти на темную тему", + "switchToLight": "Перейти на светлую тему" + } +} \ No newline at end of file diff --git a/dashboard/src/i18n/locales/ru-RU/features/chart.json b/dashboard/src/i18n/locales/ru-RU/features/chart.json new file mode 100644 index 000000000..d5d20474d --- /dev/null +++ b/dashboard/src/i18n/locales/ru-RU/features/chart.json @@ -0,0 +1,4 @@ +{ + "messageCount": "Количество сообщений", + "time": "Время" +} \ No newline at end of file diff --git a/dashboard/src/i18n/locales/ru-RU/features/chat.json b/dashboard/src/i18n/locales/ru-RU/features/chat.json new file mode 100644 index 000000000..fa434bb7d --- /dev/null +++ b/dashboard/src/i18n/locales/ru-RU/features/chat.json @@ -0,0 +1,146 @@ +{ + "title": "Давай пообщаемся!", + "subtitle": "Общение с AI-помощником", + "input": { + "placeholder": "Введите сообщение...", + "send": "Отправить", + "clear": "Очистить", + "upload": "Загрузить файл", + "voice": "Голосовой ввод", + "recordingPrompt": "Запись... говорите", + "chatPrompt": "Давай пообщаемся!", + "dropToUpload": "Отпустите, чтобы загрузить файл", + "stopGenerating": "Остановить генерацию" + }, + "message": { + "user": "Вы", + "assistant": "Ассистент", + "system": "Система", + "error": "Ошибка в сообщении", + "loading": "Думаю..." + }, + "voice": { + "start": "Начать запись", + "stop": "Стоп", + "recording": "Запись", + "processing": "Обработка...", + "error": "Ошибка записи", + "listening": "Слушаю...", + "speaking": "Говорю", + "startRecording": "Начать голосовой ввод", + "liveMode": "Общение в реальном времени" + }, + "welcome": { + "title": "Добро пожаловать в AstrBot", + "subtitle": "Ваш умный помощник", + "quickActions": "Быстрые действия", + "examples": "Примеры вопросов" + }, + "actions": { + "copy": "Копировать", + "regenerate": "Перегенерировать", + "like": "Нравится", + "dislike": "Не нравится", + "share": "Поделиться", + "newChat": "Новый чат", + "deleteChat": "Удалить чат", + "editTitle": "Изменить заголовок", + "fullscreen": "На весь экран", + "exitFullscreen": "Выход из полноэкранного режима", + "reply": "Ответить", + "providerConfig": "Настройки AI", + "toolsUsed": "Использованные инструменты", + "toolCallUsed": "Использован инструмент {name}", + "pythonCodeAnalysis": "Использован анализ кода Python" + }, + "ipython": { + "output": "Вывод" + }, + "conversation": { + "newConversation": "Новый чат", + "noHistory": "История диалогов пуста", + "systemStatus": "Статус системы", + "llmService": "Сервис LLM", + "speechToText": "Преобразование речи", + "editDisplayName": "Изменить имя чата", + "displayName": "Имя чата", + "displayNameUpdated": "Имя чата обновлено", + "displayNameUpdateFailed": "Не удалось обновить имя чата", + "confirmDelete": "Вы уверены, что хотите удалить «{name}»? Это действие необратимо." + }, + "modes": { + "darkMode": "Темная тема", + "lightMode": "Светлая тема" + }, + "shortcuts": { + "help": "Справка", + "voiceRecord": "Запись голоса", + "pasteImage": "Вставить изображение" + }, + "streaming": { + "enabled": "Потоковый ответ включен", + "disabled": "Потоковый ответ выключен", + "on": "Поток", + "off": "Обычный" + }, + "transport": { + "title": "Протокол передачи", + "sse": "SSE", + "websocket": "WebSocket" + }, + "config": { + "title": "Конфигурация" + }, + "reasoning": { + "thinking": "Рассуждение" + }, + "reply": { + "replyTo": "В ответ на", + "notFound": "Сообщение не найдено" + }, + "project": { + "title": "Проект", + "create": "Создать проект", + "edit": "Изменить проект", + "name": "Имя проекта", + "emoji": "Иконка (Emoji)", + "description": "Описание проекта (опционально)", + "noSessions": "В этом проекте пока нет диалогов", + "confirmDelete": "Вы уверены, что хотите удалить проект «{title}»? Диалоги внутри проекта не будут удалены." + }, + "time": { + "today": "Сегодня", + "yesterday": "Вчера" + }, + "stats": { + "tokens": "Токены", + "inputTokens": "Входящие", + "outputTokens": "Исходящие", + "cachedTokens": "Кэшированные", + "duration": "Время", + "ttft": "Время до первого токена" + }, + "refs": { + "title": "Ссылки", + "sources": "Источники" + }, + "connection": { + "title": "Статус подключения", + "message": "Системе необходимо переустановить соединение с чатом.", + "reasons": "Это может быть вызвано следующими причинами:", + "reasonWindowResize": "Изменение размера окна (нормально)", + "reasonMultipleTabs": "Страница чата открыта в другой вкладке", + "reasonNetworkIssue": "Временная проблема с сетью", + "notice": "Примечание: для стабильной работы допускается только одно активное соединение. Если вы используете чат в нескольких вкладках, рекомендуем оставить только одну.", + "understand": "Понятно", + "status": { + "reconnecting": "Переподключение...", + "reconnected": "Соединение восстановлено", + "failed": "Ошибка подключения, обновите страницу" + } + }, + "errors": { + "sendMessageFailed": "Ошибка отправки сообщения, попробуйте еще раз", + "createSessionFailed": "Ошибка создания сессии, обновите страницу" + } +} \ No newline at end of file diff --git a/dashboard/src/i18n/locales/ru-RU/features/command.json b/dashboard/src/i18n/locales/ru-RU/features/command.json new file mode 100644 index 000000000..7d887c8ef --- /dev/null +++ b/dashboard/src/i18n/locales/ru-RU/features/command.json @@ -0,0 +1,95 @@ +{ + "title": "Управление командами", + "summary": { + "total": "Всего команд", + "disabled": "Отключено", + "conflicts": "Конфликты" + }, + "conflictAlert": { + "title": "Обнаружены конфликты команд", + "description": "Сейчас конфликтуют {count} пары команд. Это может привести к одновременному срабатыванию нескольких плагинов и непредсказуемому поведению.", + "hint": "Нажмите «Переименовать», чтобы изменить название конфликтующей команды." + }, + "table": { + "headers": { + "command": "Команда", + "type": "Тип", + "plugin": "Плагин", + "description": "Описание", + "permission": "Доступ", + "status": "Статус", + "actions": "Действия" + } + }, + "type": { + "command": "Команда", + "group": "Группа команд", + "subCommand": "Под-команда" + }, + "status": { + "enabled": "Активна", + "disabled": "Отключена", + "conflict": "Конфликт" + }, + "permission": { + "everyone": "Все", + "admin": "Админ" + }, + "tooltips": { + "enable": "Включить", + "disable": "Выключить", + "rename": "Переименовать", + "viewDetails": "Подробности" + }, + "dialogs": { + "rename": { + "title": "Переименование команды", + "newName": "Новое название", + "aliases": "Управление алиасами", + "addAlias": "Добавить алиас", + "cancel": "Отмена", + "confirm": "Подтвердить" + }, + "details": { + "title": "Детали команды", + "type": "Тип команды", + "handler": "Обработчик (Handler)", + "module": "Путь к модулю", + "originalCommand": "Исходная команда", + "effectiveCommand": "Действующая команда", + "parentGroup": "Родительская группа", + "subCommands": "Под-команды", + "aliases": "Алиасы (Синонимы)", + "permission": "Требования прав", + "conflictStatus": "Статус конфликта" + } + }, + "messages": { + "toggleSuccess": "Статус команды обновлен", + "toggleFailed": "Не удалось изменить статус команды", + "renameSuccess": "Команда переименована", + "renameFailed": "Ошибка переименования", + "loadFailed": "Ошибка загрузки списка команд", + "updateSuccess": "Обновлено успешно", + "updateFailed": "Ошибка обновления" + }, + "search": { + "placeholder": "Поиск команд..." + }, + "empty": { + "noCommands": "Команд не найдено", + "noCommandsDesc": "По вашему запросу не найдено ни одной команды" + }, + "filters": { + "all": "Все", + "enabled": "Активные", + "disabled": "Отключенные", + "conflict": "Конфликтующие", + "byPlugin": "По плагину", + "byType": "По типу", + "byPermission": "По правам", + "byStatus": "По статусу", + "showSystemPlugins": "Показывать системные плагины", + "systemPluginConflictHint": "Конфликт затрагивает системный плагин, его нельзя скрыть до разрешения конфликта" + } +} \ No newline at end of file diff --git a/dashboard/src/i18n/locales/ru-RU/features/config-metadata.json b/dashboard/src/i18n/locales/ru-RU/features/config-metadata.json new file mode 100644 index 000000000..56d12c983 --- /dev/null +++ b/dashboard/src/i18n/locales/ru-RU/features/config-metadata.json @@ -0,0 +1,1526 @@ +{ + "ai_group": { + "name": "AI", + "agent_runner": { + "description": "Запуск агентов (Agent Runner)", + "hint": "Выберите среду для работы AI-диалогов. По умолчанию используется встроенный агент AstrBot, поддерживающий базу знаний, персонализацию и вызов технических инструментов. Не изменяйте этот раздел, если не планируете использовать сторонние платформы, такие как Dify, Coze или DeerFlow.", + "provider_settings": { + "enable": { + "description": "Включить", + "hint": "Главный переключатель для AI-диалогов" + }, + "agent_runner_type": { + "description": "Запуск (Runner)", + "labels": [ + "Встроенный агент", + "Dify", + "Coze", + "Приложение Alibaba Cloud Bailian", + "DeerFlow" + ] + }, + "coze_agent_runner_provider_id": { + "description": "ID провайдера Coze Agent Runner" + }, + "dify_agent_runner_provider_id": { + "description": "ID провайдера Dify Agent Runner" + }, + "dashscope_agent_runner_provider_id": { + "description": "ID провайдера Alibaba Cloud Bailian" + }, + "deerflow_agent_runner_provider_id": { + "description": "ID провайдера DeerFlow Agent Runner" + } + } + }, + "ai": { + "description": "Модель", + "hint": "При использовании сторонних сред запуска модель чата и модель описания изображений могут не работать напрямую, но некоторые плагины всё равно зависят от этих настроек.", + "provider_settings": { + "default_provider_id": { + "description": "Модель чата по умолчанию", + "hint": "Если пусто, используется первая доступная модель" + }, + "fallback_chat_models": { + "description": "Резервные модели чата (ID)", + "hint": "Если текущая модель недоступна, запрос будет перенаправлен на эти модели по порядку." + }, + "default_image_caption_provider_id": { + "description": "Модель описания изображений", + "hint": "Оставьте пустым для отключения; полезно для моделей без поддержки мультимодальности" + }, + "image_caption_prompt": { + "description": "Промпт для описания изображений" + } + }, + "provider_stt_settings": { + "enable": { + "description": "Включить преобразование речи в текст (STT)", + "hint": "Главный переключатель для STT" + }, + "provider_id": { + "description": "Модель STT по умолчанию", + "hint": "Пользователи могут выбирать другие модели STT через команду /provider." + } + }, + "provider_tts_settings": { + "enable": { + "description": "Включить преобразование текста в речь (TTS)", + "hint": "Главный переключатель для TTS" + }, + "provider_id": { + "description": "Модель TTS по умолчанию" + }, + "trigger_probability": { + "description": "Вероятность срабатывания TTS" + } + } + }, + "persona": { + "description": "Персонаж", + "hint": "Установите персонажа по умолчанию для AI-диалогов. Управлять персонажами можно на вкладке «Персонажи».", + "provider_settings": { + "default_personality": { + "description": "Персонаж по умолчанию" + } + } + }, + "knowledgebase": { + "description": "База знаний", + "kb_names": { + "description": "Список баз знаний", + "hint": "Поддерживается выбор нескольких баз" + }, + "kb_fusion_top_k": { + "description": "Кол-во результатов (Fusion Search)", + "hint": "Количество результатов после объединения данных из нескольких баз знаний" + }, + "kb_final_top_k": { + "description": "Итоговое кол-во результатов", + "hint": "Количество результатов, извлекаемых из базы знаний. Высокие значения дают больше данных, но могут добавить шума. Настройте по необходимости." + }, + "kb_agentic_mode": { + "description": "Агентский режим извлечения (Agentic Retrieval)", + "hint": "Если включено, извлечение из базы знаний становится инструментом (Tool) для LLM, позволяя модели самой решать, когда обращаться к базе. Требует поддержки вызова функций (function calling) в модели." + } + }, + "websearch": { + "description": "Поиск в сети", + "provider_settings": { + "web_search": { + "description": "Включить поиск в сети" + }, + "websearch_provider": { + "description": "Провайдер поиска" + }, + "websearch_tavily_key": { + "description": "API-ключ Tavily", + "hint": "Можно добавить несколько ключей для ротации." + }, + "websearch_bocha_key": { + "description": "API-ключ BoCha", + "hint": "Можно добавить несколько ключей для ротации." + }, + "websearch_baidu_app_builder_key": { + "description": "API-ключ Baidu Qianfan APP Builder", + "hint": "Ссылка: [https://console.bce.baidu.com/iam/#/iam/apikey/list](https://console.bce.baidu.com/iam/#/iam/apikey/list)" + }, + "web_search_link": { + "description": "Показывать ссылки на источники" + } + } + }, + "file_extract": { + "description": "Извлечение из файлов", + "provider_settings": { + "file_extract": { + "enable": { + "description": "Включить извлечение из файлов" + }, + "provider": { + "description": "Провайдер извлечения" + }, + "moonshotai_api_key": { + "description": "API-ключ Moonshot AI" + } + } + } + }, + "agent_computer_use": { + "description": "Использование компьютера (Agent Computer Use)", + "hint": "Позволяет AstrBot получать доступ к вашему компьютеру или песочнице для выполнения сложных задач. См. [Режим песочницы](https://docs.astrbot.app/use/astrbot-agent-sandbox.html), [Навыки](https://docs.astrbot.app/use/skills.html)", + "provider_settings": { + "computer_use_runtime": { + "description": "Среда выполнения (Runtime)", + "hint": "'sandbox' означает запуск в изолированной среде, 'local' — локально на вашем ПК, 'none' — отключено. Если навыки загружены, выбор 'none' сделает их недоступными для агента." + }, + "computer_use_require_admin": { + "description": "Требовать права администратора AstrBot", + "hint": "Если включено, только администраторы смогут использовать возможности управления компьютером. Добавить администраторов можно в конфиге платформы." + }, + "sandbox": { + "booter": { + "description": "Драйвер среды песочницы" + }, + "shipyard_neo_endpoint": { + "description": "Эндпоинт Shipyard Neo API", + "hint": "Адрес Bay API, по умолчанию http://127.0.0.1:8114." + }, + "shipyard_neo_access_token": { + "description": "Токен доступа Shipyard Neo", + "hint": "Ключ Bay API (sk-bay-...). Оставьте пустым для автопоиска в credentials.json." + }, + "shipyard_neo_profile": { + "description": "Профиль Shipyard Neo", + "hint": "Профиль песочницы, например, python-default." + }, + "shipyard_neo_ttl": { + "description": "TTL песочницы Shipyard Neo", + "hint": "Время жизни песочницы в секундах." + }, + "shipyard_endpoint": { + "description": "Эндпоинт Shipyard API", + "hint": "Адрес API для доступа к сервису Shipyard." + }, + "shipyard_access_token": { + "description": "Токен доступа Shipyard", + "hint": "Токен доступа для работы с сервисом Shipyard." + }, + "shipyard_ttl": { + "description": "TTL сессии Shipyard", + "hint": "Время жизни сессии в секундах." + }, + "shipyard_max_sessions": { + "description": "Макс. количество сессий Shipyard", + "hint": "Максимальное количество сессий Shipyard, которое может поддерживать экземпляр." + } + } + } + }, + "proactive_capability": { + "description": "Проактивный агент", + "hint": "AstrBot будет просыпаться, выполнять ваши задачи и сообщать о результатах. См. [Проактивный агент](https://docs.astrbot.app/en/use/proactive-agent.html)", + "provider_settings": { + "proactive_capability": { + "add_cron_tools": { + "description": "Включить", + "hint": "Если включено, агенту будут переданы инструменты для проактивной работы. Вы сможете поручать задачи на будущее, и они будут выполнены по расписанию." + } + } + } + }, + "truncate_and_compress": { + "hint": "[Управление контекстом](https://docs.astrbot.app/en/use/context-compress.html)", + "description": "Стратегия управления контекстом", + "provider_settings": { + "max_context_length": { + "description": "Макс. количество раундов диалога", + "hint": "При превышении удаляются старые сообщения. 1 раунд = 1 пара запрос-ответ. -1 означает без ограничений." + }, + "dequeue_context_length": { + "description": "Кол-во удаляемых раундов", + "hint": "Сколько раундов удалять за один раз при достижении лимита." + }, + "context_limit_reached_strategy": { + "description": "Действие при переполнении окна контекста", + "labels": [ + "Обрезать по раундам", + "Сжать с помощью LLM" + ], + "hint": "При выборе 'Обрезать' удаляются старые сообщения. При выборе 'Сжать' используется модель для суммаризации контекста." + }, + "llm_compress_instruction": { + "description": "Инструкция для сжатия контекста", + "hint": "Если пусто, используется промпт по умолчанию." + }, + "llm_compress_keep_recent": { + "description": "Сохранять последние раунды при сжатии", + "hint": "Всегда оставлять последние N раундов диалога без изменений при сжатии." + }, + "llm_compress_provider_id": { + "description": "Модель для сжатия контекста", + "hint": "Если не выбрано, произойдет откат к стратегии удаления сообщений." + } + } + }, + "others": { + "description": "Прочие настройки", + "provider_settings": { + "display_reasoning_text": { + "description": "Отображать процесс рассуждения (Reasoning)" + }, + "llm_safety_mode": { + "description": "Безопасный режим", + "hint": "Добавляет защитные фильтры к ответам модели." + }, + "safety_mode_strategy": { + "description": "Стратегия безопасного режима", + "hint": "Как применять защитные фильтры." + }, + "identifier": { + "description": "Идентификация пользователя", + "hint": "Если включено, информация об ID пользователя будет включена в промпт." + }, + "group_name_display": { + "description": "Отображать название группы", + "hint": "Если включено, название группы будет включено в промпт на поддерживаемых платформах (OneBot v11)." + }, + "datetime_system_prompt": { + "description": "Осведомленность о реальном времени", + "hint": "Если включено, информация о текущем времени будет добавлена в системный промпт." + }, + "show_tool_use_status": { + "description": "Выводить статус вызова функций" + }, + "show_tool_call_result": { + "description": "Выводить результаты работы инструментов", + "hint": "Работает только при включенном статусе вызова функций. Показывает макс. 70 символов." + }, + "sanitize_context_by_modalities": { + "description": "Очистка истории по модальностям", + "hint": "Если включено, очищает контекст перед запросом, удаляя блоки (например, изображения), которые не поддерживаются выбранным провайдером." + }, + "max_quoted_fallback_images": { + "description": "Лимит загрузки изображений из пересланных сообщений", + "hint": "Максимальное количество изображений при парсинге цитируемых сообщений." + }, + "quoted_message_parser": { + "max_component_chain_depth": { + "description": "Глубина парсинга богатого текста", + "hint": "Максимальная глубина рекурсии при парсинге сложных компонентов в пересланных сообщениях." + }, + "max_forward_node_depth": { + "description": "Глубина вложенности пересылок", + "hint": "Максимальная глубина парсинга вложенных пересланных узлов." + }, + "max_forward_fetch": { + "description": "Лимит рекурсивного получения пересылок", + "hint": "Максимальное количество операций get_forward_msg." + }, + "warn_on_action_failure": { + "description": "Предупреждать при ошибке парсинга пересылок", + "hint": "Если включено, логирует предупреждения при неудачных попытках получения сообщений." + } + }, + "max_agent_step": { + "description": "Макс. количество раундов вызова инструментов" + }, + "tool_call_timeout": { + "description": "Таймаут вызова инструмента (сек)" + }, + "tool_schema_mode": { + "description": "Режим схемы инструментов", + "hint": "Skills-like сначала отправляет имя/описание и дозапрашивает параметры; Full отправляет полную схему сразу.", + "labels": [ + "Skills-like (двухэтапный)", + "Полная схема (Full)" + ] + }, + "streaming_response": { + "description": "Потоковый вывод (Streaming)" + }, + "unsupported_streaming_strategy": { + "description": "Для платформ без поддержки стриминга", + "hint": "Выберите метод обработки, если платформа не поддерживает потоковые ответы. 'Сегментированный ответ' отправляет части текста по мере появления знаков препинания.", + "labels": [ + "Сегментированный ответ в реальном времени", + "Отключить стриминг" + ] + }, + "wake_prefix": { + "description": "Дополнительный префикс пробуждения LLM", + "hint": "Если префикс пробуждения '/', а дополнительные — 'chat', то потребуется '/chat' для активации LLM." + }, + "prompt_prefix": { + "description": "Промпт пользователя", + "hint": "Вы можете использовать {{prompt}} как заполнитель для ввода. Если заполнитель не указан, он будет добавлен перед текстом пользователя." + }, + "reachability_check": { + "description": "Проверка доступности провайдеров", + "hint": "При выполнении команды /provider проверяет связь со всеми моделями. Это может расходовать токены." + } + }, + "provider_tts_settings": { + "dual_output": { + "description": "Выводить и голос, и текст при включенном TTS" + } + } + } + }, + "platform_group": { + "name": "Платформы", + "platform": { + "description": "Адаптеры сообщений", + "active_send_mode": { + "description": "Использовать API активной отправки" + }, + "appid": { + "description": "ID приложения", + "hint": "Обязательно для QQ Official Bot. См. документацию." + }, + "callback_server_host": { + "description": "Хост callback-сервера", + "hint": "Оставьте пустым для отключения." + }, + "card_template_id": { + "description": "ID шаблона карточки", + "hint": "Необязательно. Для интерактивных карточек DingTalk." + }, + "discord_activity_name": { + "description": "Название активности Discord", + "hint": "Статус бота в Discord. Оставьте пустым для отключения." + }, + "discord_command_register": { + "description": "Регистрировать слэш-команды Discord", + "hint": "Если включено, команды плагинов будут зарегистрированы как /команды Discord." + }, + "discord_proxy": { + "description": "Proxy URL для Discord", + "hint": "Формат: http://ip:port" + }, + "discord_token": { + "description": "Токен бота Discord", + "hint": "Введите сюда токен вашего Discord-бота." + }, + "enable": { + "description": "Включить", + "hint": "Включить этот адаптер. Отключенные адаптеры не будут получать сообщения." + }, + "enable_group_c2c": { + "description": "Включить личные сообщения из списка", + "hint": "Позволяет боту получать личные сообщения из списка QQ (может потребоваться добавление в друзья)." + }, + "enable_guild_direct_message": { + "description": "Включить ЛС в гильдиях", + "hint": "Позволяет боту получать прямые сообщения в рамках гильдий." + }, + "id": { + "description": "Имя бота", + "hint": "Отображаемое имя бота" + }, + "is_sandbox": { + "description": "Режим песочницы" + }, + "kf_name": { + "description": "Имя аккаунта службы поддержки WeChat", + "hint": "Необязательно. См. https://kf.weixin.qq.com/kf/frame#/accounts" + }, + "lark_bot_name": { + "description": "Имя бота Lark", + "hint": "Должно быть точным для работы через @ упоминания." + }, + "lark_connection_mode": { + "description": "Режим подписки", + "labels": [ + "Режим длинного соединения", + "Режим Webhook" + ] + }, + "lark_encrypt_key": { + "description": "Ключ шифрования", + "hint": "Для расшифровки данных обратного вызова Lark." + }, + "lark_verification_token": { + "description": "Токен верификации", + "hint": "Для проверки запросов обратного вызова Lark." + }, + "misskey_allow_insecure_downloads": { + "description": "Разрешить небезопасные загрузки (без SSL)", + "hint": "Отключает проверку SSL если у удаленного сервера проблемы с сертификатами. Используйте с осторожностью." + }, + "misskey_default_visibility": { + "description": "Видимость постов по умолчанию", + "hint": "public — всем, home — в ленте, followers — только подписчикам." + }, + "misskey_download_chunk_size": { + "description": "Размер чанка загрузки (байт)", + "hint": "Влияет на потребление памяти при загрузке файлов." + }, + "misskey_download_timeout": { + "description": "Таймаут загрузки (сек)", + "hint": "Максимльное время на загрузку файлов из сети." + }, + "misskey_enable_chat": { + "description": "Включить ответы в чатах", + "hint": "Бот будет отвечать на личные сообщения в Misskey." + }, + "misskey_enable_file_upload": { + "description": "Включить загрузку файлов в Misskey", + "hint": "Бот будет загружать файлы из сообщений в хранилище Misskey." + }, + "misskey_instance_url": { + "description": "URL инстанса Misskey", + "hint": "Например, https://misskey.example" + }, + "misskey_local_only": { + "description": "Только локально (без федерации)", + "hint": "Посты бота будут видны только на текущем инстансе." + }, + "misskey_max_download_bytes": { + "description": "Макс. размер загрузки (байт)", + "hint": "Лимит на размер загружаемых файлов для предотвращения нехватки памяти." + }, + "misskey_token": { + "description": "Токен доступа Misskey", + "hint": "Токен доступа к API, созданный в настройках сервиса подключения." + }, + "misskey_upload_concurrency": { + "description": "Лимит одновременных загрузок", + "hint": "По умолчанию 3." + }, + "misskey_upload_folder": { + "description": "ID папки в хранилище", + "hint": "Необязательно. Если пусто — в корень." + }, + "port": { + "description": "Порт callback-сервера", + "hint": "Оставьте пустым для отключения." + }, + "satori_api_base_url": { + "description": "Эндпоинт Satori API", + "hint": "Базовый URL для Satori API." + }, + "satori_auto_reconnect": { + "description": "Автоматическое переподключение", + "hint": "Переподключать WebSocket при разрыве." + }, + "satori_endpoint": { + "description": "WebSocket эндпоинт Satori", + "hint": "WebSocket-эндпоинт для событий Satori." + }, + "satori_heartbeat_interval": { + "description": "Интервал сердцебиения Satori", + "hint": "Интервал в секундах между отправкой heartbeat-сообщений." + }, + "satori_reconnect_delay": { + "description": "Задержка переподключения Satori", + "hint": "Задержка перед попыткой переподключения (в секундах)." + }, + "satori_token": { + "description": "Токен Satori", + "hint": "Токен для аутентификации в Satori API." + }, + "secret": { + "description": "Секрет (Secret)", + "hint": "Обязательно." + }, + "slack_connection_mode": { + "description": "Режим соединения Slack", + "hint": "webhook — через вебхуки, socket — через Socket Mode." + }, + "slack_webhook_host": { + "description": "Хост вебхука Slack", + "hint": "Действительно только если режим подключения Slack установлен как `webhook`." + }, + "slack_webhook_path": { + "description": "Путь вебхука Slack", + "hint": "Действительно только если режим подключения Slack установлен как `webhook`." + }, + "slack_webhook_port": { + "description": "Порт вебхука Slack", + "hint": "Действительно только если режим подключения Slack установлен как `webhook`." + }, + "telegram_command_auto_refresh": { + "description": "Автообновление команд Telegram", + "hint": "Если включено, AstrBot автоматически обновляет команды Telegram во время выполнения. (Одна эта настройка не имеет эффекта)" + }, + "telegram_command_register": { + "description": "Регистрация команд Telegram", + "hint": "Если включено, AstrBot автоматически регистрирует команды Telegram." + }, + "telegram_command_register_interval": { + "description": "Интервал автообновления команд Telegram", + "hint": "Интервал автоматического обновления команд Telegram в секундах." + }, + "telegram_token": { + "description": "Токен бота", + "hint": "Если вы находитесь в материковом Китае, установите прокси или измените api_base в разделе «Другие настройки»." + }, + "type": { + "description": "Тип адаптера" + }, + "unified_webhook_mode": { + "description": "Единый режим Webhook", + "hint": "Использовать общий вход вебхуков AstrBot без открытия новых портов. URL: /api/platform/webhook/{uuid}." + }, + "webhook_uuid": { + "description": "UUID вебхука", + "hint": "Создается автоматически при добавлении платформы." + }, + "wecom_ai_bot_name": { + "description": "Имя бота WeCom AI", + "hint": "Должно быть указано верно, иначе некоторые команды не будут работать." + }, + "wecom_ai_bot_connection_mode": { + "description": "Режим соединения WeCom AI", + "hint": "webhook требует Token/AESKey; long_connection требует BotID/Secret." + }, + "wecomaibot_friend_message_welcome_text": { + "description": "Приветствие в ЛС WeCom AI", + "hint": "Отправляется при первом входе пользователя в чат за день." + }, + "wecomaibot_init_respond_text": { + "description": "Начальный текст ответа WeCom AI", + "hint": "Первое сообщение, которое отправляет бот при получении запроса." + }, + "wecomaibot_token": { + "description": "Токен WeCom AI", + "hint": "Используется для аутентификации в режиме обратного вызова webhook." + }, + "wecomaibot_encoding_aes_key": { + "description": "EncodingAESKey для ИИ-бота WeCom", + "hint": "Используется для шифрования/дешифрования сообщений в режиме обратного вызова webhook." + }, + "wecomaibot_ws_bot_id": { + "description": "BotID для длинного соединения", + "hint": "Учетные данные BotID для режима длительного соединения ИИ-бота WeCom." + }, + "wecomaibot_ws_secret": { + "description": "Secret для длинного соединения", + "hint": "Учетные данные Secret для режима длительного соединения ИИ-бота WeCom." + }, + "wecomaibot_ws_url": { + "description": "WebSocket URL для длинного соединения", + "hint": "По умолчанию используется wss://openws.work.weixin.qq.com, обычно не требует изменений." + }, + "wecomaibot_heartbeat_interval": { + "description": "Интервал сердцебиения соединения", + "hint": "Интервал пульса (в секундах) в режиме длительного соединения. Рекомендуется 30 секунд." + }, + "wpp_active_message_poll": { + "description": "Включить активный опрос сообщений", + "hint": "Включите, только если сообщения WeChat приходят с задержкой." + }, + "wpp_active_message_poll_interval": { + "description": "Интервал опроса сообщений", + "hint": "По умолчанию 3 сек." + }, + "ws_reverse_host": { + "description": "Хост реверсивного WebSocket", + "hint": "AstrBot выступает в роли сервера." + }, + "ws_reverse_port": { + "description": "Порт реверсивного WebSocket" + }, + "ws_reverse_token": { + "description": "Токен реверсивного WebSocket", + "hint": "Токен обратного WebSocket (Reverse WebSocket). Если не задан, проверка токена будет отключена." + }, + "msg_push_webhook_url": { + "description": "URL вебхука для пуш-сообщений WeCom", + "hint": "Рекомендуется для корректной отправки всех типов сообщений." + }, + "only_use_webhook_url_to_send": { + "description": "Отправлять ответы только через Webhook", + "hint": "Все ответы WeCom AI Bot будут идти через вебхук пуш-сообщений. Поддерживает больше типов контента." + }, + "kook_bot_token": { + "description": "Токен бота", + "type": "string", + "hint": "Обязательно. Токен бота, полученный на платформе разработчиков KOOK." + }, + "kook_bot_nickname": { + "description": "Никнейм бота", + "type": "string", + "hint": "Сообщения от пользователя с таким никнеймом будут игнорироваться." + }, + "kook_reconnect_delay": { + "description": "Задержка переподключения", + "type": "int", + "hint": "Задержка перед повторным подключением (в секундах), используется стратегия экспоненциальной задержки (exponential backoff)." + }, + "kook_max_reconnect_delay": { + "description": "Макс. задержка переподключения", + "type": "int", + "hint": "Максимальное значение задержки для повторного подключения (в секундах)." + }, + "kook_max_retry_delay": { + "description": "Макс. задержка повтора", + "type": "int", + "hint": "Максимальное время задержки для повторных попыток (в секундах)." + }, + "kook_heartbeat_interval": { + "description": "Интервал сердцебиения", + "type": "int", + "hint": "Интервал времени для проверки пульса (в секундах)." + }, + "kook_heartbeat_timeout": { + "description": "Таймаут сердцебиения", + "type": "int", + "hint": "Длительность ожидания (таймаут) для проверки пульса (в секундах)." + }, + "kook_max_heartbeat_failures": { + "description": "Макс. количество сбоев сердцебиения", + "type": "int", + "hint": "Максимально допустимое количество сбоев пульса; если лимит превышен, соединение будет разорвано." + }, + "kook_max_consecutive_failures": { + "description": "Макс. количество последовательных сбоев", + "type": "int", + "hint": "Максимально допустимое количество последовательных сбоев; если лимит превышен, повторные попытки будут остановлены." + } + }, + "general": { + "description": "Общие", + "admins_id": { + "description": "ID администраторов" + }, + "platform_settings": { + "unique_session": { + "description": "Изолировать сессии", + "hint": "У каждого участника группы будет свой независимый контекст." + }, + "friend_message_needs_wake_prefix": { + "description": "Личные сообщения требуют префикс пробуждения" + }, + "reply_prefix": { + "description": "Префикс текста ответа" + }, + "reply_with_mention": { + "description": "Упоминать отправителя в ответе" + }, + "reply_with_quote": { + "description": "Цитировать сообщение отправителя в ответе" + }, + "forward_threshold": { + "description": "Порог количества слов для пересылки" + }, + "empty_mention_waiting": { + "description": "Реагировать на пустое упоминание (@бота)" + } + }, + "wake_prefix": { + "description": "Префикс пробуждения" + }, + "disable_builtin_commands": { + "description": "Отключить встроенные команды", + "hint": "Отключает help, provider, model и другие базовые команды." + } + }, + "whitelist": { + "description": "Белый список", + "platform_settings": { + "enable_id_white_list": { + "description": "Включить белый список", + "hint": "Бот будет отвечать только в разрешенных сессиях." + }, + "id_whitelist": { + "description": "Список разрешенных ID", + "hint": "Используйте /sid для получения ID." + }, + "id_whitelist_log": { + "description": "Выводить логи", + "hint": "Логировать попытки доступа от пользователей не из белого списка." + }, + "wl_ignore_admin_on_group": { + "description": "Администраторы в группах обходят белый список" + }, + "wl_ignore_admin_on_friend": { + "description": "Администраторы в ЛС обходят белый список" + } + } + }, + "rate_limit": { + "description": "Лимит частоты", + "platform_settings": { + "rate_limit": { + "time": { + "description": "Время лимита сообщений (сек)" + }, + "count": { + "description": "Количество сообщений для лимита" + }, + "strategy": { + "description": "Стратегия лимитирования" + } + } + } + }, + "content_safety": { + "description": "Безопасность контента", + "content_safety": { + "also_use_in_response": { + "description": "Проверять также ответы модели" + }, + "baidu_aip": { + "enable": { + "description": "Использовать Baidu Content Safety", + "hint": "Требуется установка библиотеки baidu-aip." + }, + "app_id": { + "description": "App ID" + }, + "api_key": { + "description": "API Key" + }, + "secret_key": { + "description": "Secret Key" + } + }, + "internal_keywords": { + "enable": { + "description": "Проверка по ключевым словам" + }, + "extra_keywords": { + "description": "Дополнительные слова", + "hint": "Список запрещенных слов, поддерживает регулярные выражения." + } + } + } + }, + "t2i": { + "description": "Текст в изображение", + "t2i": { + "description": "Вывод текста картинкой" + }, + "t2i_word_threshold": { + "description": "Порог количества слов для перевода в картинку" + } + }, + "others": { + "description": "Прочие настройки", + "platform_settings": { + "ignore_bot_self_message": { + "description": "Игнорировать собственные сообщения бота" + }, + "ignore_at_all": { + "description": "Игнорировать события @all" + }, + "no_permission_reply": { + "description": "Ответ при отсутствии прав у пользователя" + } + }, + "platform_specific": { + "lark": { + "pre_ack_emoji": { + "enable": { + "description": "[Lark] Пре-эмодзи подтверждения" + }, + "emojis": { + "description": "Список эмодзи (Lark Enum Names)", + "hint": "Справка по именам эмодзи Lark." + } + } + }, + "telegram": { + "pre_ack_emoji": { + "enable": { + "description": "[Telegram] Пре-эмодзи подтверждения" + }, + "emojis": { + "description": "Список эмодзи (Unicode)", + "hint": "Telegram поддерживает ограниченный набор реакций." + } + } + }, + "discord": { + "pre_ack_emoji": { + "enable": { + "description": "[Discord] Пре-эмодзи подтверждения" + }, + "emojis": { + "description": "Список эмодзи (Unicode или имя эмодзи)", + "hint": "Введите Unicode эмодзи, напр. 👍, 🤔, ⏳" + } + } + } + } + } + }, + "plugin_group": { + "name": "Плагины", + "plugin": { + "description": "Управление плагинами", + "plugin_set": { + "description": "Доступные плагины", + "hint": "Все невыключенные плагины включены по умолчанию. Если плагин отключен на странице плагинов, выбор здесь не будет иметь силы." + } + } + }, + "ext_group": { + "name": "Расширения (Extensions)", + "segmented_reply": { + "description": "Сегментированный ответ", + "platform_settings": { + "segmented_reply": { + "enable": { + "description": "Включить сегментированные ответы" + }, + "only_llm_result": { + "description": "Сегментировать только ответы LLM" + }, + "interval_method": { + "description": "Метод интервала", + "hint": "random — случайное время, log — расчет на основе длины сообщения: $y=log_{log\\_base}(x)$, где x — количество знаков, y — секунды." + }, + "interval": { + "description": "Случайный интервал времени", + "hint": "Формат: минимум,максимум (напр., 1.5,3.5)" + }, + "log_base": { + "description": "Основание логарифма", + "hint": "Основание для логарифмических интервалов, по умолчанию 2.6. Диапазон: 1.0-10.0." + }, + "words_count_threshold": { + "description": "Порог сегментации (количество знаков)", + "hint": "Порог количества знаков для сегментированного ответа. Сообщения короче этого лимита будут сегментированы, длиннее — отправлены целиком." + }, + "split_mode": { + "description": "Режим разделения", + "hint": "Используется для сегментации сообщения. По умолчанию разделяется знаками препинания (точка, вопрос и т.д.). Например, `[。?!]` удалит все точки, вопросительные и восклицательные знаки. re.findall(r'', text)", + "labels": [ + "Регулярное выражение", + "Список слов" + ] + }, + "regex": { + "description": "Регулярное выражение для сегментации", + "hint": "Используется для поиска точек разделения с помощью регулярного выражения. Рекомендуется использовать паттерны, соответствующие разделителям." + }, + "split_words": { + "description": "Список слов-разделителей", + "hint": "Разделять при обнаружении любого слова из списка" + }, + "content_cleanup_rule": { + "description": "Регулярное выражение для фильтрации контента", + "hint": "Удаляет указанный контент из сегментированных частей. Например, `[。?!]` удалит точки, вопросы и восклицательные знаки." + } + } + } + }, + "ltm": { + "description": "Контекстная осведомленность в группах (ранее — Улучшение памяти чата)", + "provider_ltm_settings": { + "group_icl_enable": { + "description": "Включить осведомленность о контексте группы" + }, + "group_message_max_cnt": { + "description": "Максимальное количество сообщений" + }, + "image_caption": { + "description": "Автоматическое понимание изображений", + "hint": "Требуется настройка модели описания изображений для группового чата." + }, + "image_caption_provider_id": { + "description": "Модель описания изображений для групп", + "hint": "Используется для понимания изображений в контексте группового чата, настраивается отдельно от основной модели." + }, + "active_reply": { + "enable": { + "description": "Активный ответ" + }, + "method": { + "description": "Метод активного ответа" + }, + "possibility_reply": { + "description": "Вероятность ответа", + "hint": "Значение от 0.0 до 1.0" + }, + "whitelist": { + "description": "Белый список для активных ответов", + "hint": "Фильтрация отключена, если список пуст. Используйте /sid для получения ID." + } + } + } + } + }, + "system_group": { + "name": "Система", + "system": { + "description": "Системные настройки", + "t2i_strategy": { + "description": "Стратегия текст-в-изображение", + "hint": "Стратегия текст-в-изображение. `remote` — удаленный рендеринг, `local` — библиотека PIL. Для локального режима положите font.ttf в data/." + }, + "t2i_endpoint": { + "description": "Эндпоинт API сервиса текст-в-изображение", + "hint": "Использовать API AstrBot, если пусто" + }, + "t2i_template": { + "description": "Пользовательский шаблон текст-в-изображение", + "hint": "Если включено, можно использовать свои HTML-шаблоны." + }, + "t2i_active_template": { + "description": "Текущий активный шаблон рендеринга", + "hint": "Значение управляется на странице шаблонов." + }, + "log_level": { + "description": "Уровень логирования консоли", + "hint": "Уровень логирования в консоли." + }, + "log_file_enable": { + "description": "Включить логирование в файл", + "hint": "Записывать логи в файл." + }, + "log_file_path": { + "description": "Путь к файлу логов", + "hint": "Пути относительно data/, напр. logs/astrbot.log." + }, + "log_file_max_mb": { + "description": "Макс. размер файла логов (МБ)", + "hint": "Ротация при достижении размера. По умолчанию 20МБ." + }, + "temp_dir_max_size": { + "description": "Лимит размера временной директории (МБ)", + "hint": "Лимит временной папки (МБ). Система проверяет каждые 10 минут и удаляет старое при переполнении." + }, + "trace_log_enable": { + "description": "Включить логирование трассировки", + "hint": "Записывать трассировку в отдельный файл." + }, + "trace_log_path": { + "description": "Путь к файлу логов трассировки", + "hint": "Относительные пути определяются относительно директории данных, например, logs/astrbot.trace.log; абсолютные пути также поддерживаются." + }, + "trace_log_max_mb": { + "description": "Макс. размер файла логов трассировки (МБ)", + "hint": "Ротация при достижении размера. По умолчанию 20МБ." + }, + "pip_install_arg": { + "description": "Дополнительные аргументы установки pip", + "hint": "При установке зависимостей можно указать аргументы pip, напр. --break-system-package." + }, + "pypi_index_url": { + "description": "URL репозитория PyPI", + "hint": "URL репозитория PyPI. По умолчанию: [https://mirrors.aliyun.com/pypi/simple/](https://mirrors.aliyun.com/pypi/simple/)" + }, + "callback_api_base": { + "description": "Внешний адрес для Callback API", + "hint": "Используется для сервисов, требующих обратных выливов (например, в песочнице)." + }, + "dashboard": { + "ssl": { + "enable": { + "description": "Включить HTTPS для WebUI", + "hint": "Если включено, панель управления работает по HTTPS." + }, + "cert_file": { + "description": "Путь к файлу сертификата SSL", + "hint": "Путь к файлу сертификата (PEM)." + }, + "key_file": { + "description": "Путь к ключу SSL", + "hint": "Путь к приватному ключу (PEM)." + }, + "ca_certs": { + "description": "Путь к сертификату CA SSL", + "hint": "Опционально. Путь к сертификату CA." + } + } + }, + "timezone": { + "description": "Часовой пояс", + "hint": "Например, Europe/Moscow." + }, + "http_proxy": { + "description": "HTTP прокси", + "hint": "Формат: http://user:pass@ip:port" + }, + "no_proxy": { + "description": "Список исключений прокси" + } + } + }, + "provider_group": { + "provider": { + "genie_onnx_model_dir": { + "description": "Директория моделей ONNX", + "hint": "Путь к директории с файлами моделей ONNX" + }, + "genie_language": { + "description": "Язык" + }, + "xai_native_search": { + "description": "Включить нативный поиск", + "hint": "Если включено, использует Live Search от xAI для веб-запросов (тарифицируется отдельно). Применимо только к провайдерам xAI." + }, + "rerank_api_base": { + "description": "Base URL API модели Rerank", + "hint": "AstrBot добавляет /v1/rerank к URL запроса." + }, + "rerank_api_key": { + "description": "API Key", + "hint": "Оставьте пустым, если API key не требуется." + }, + "rerank_model": { + "description": "Имя модели Rerank" + }, + "return_documents": { + "description": "Возвращать исходные документы в результатах Rerank", + "hint": "По умолчанию false для снижения сетевой нагрузки." + }, + "instruct": { + "description": "Описание задачи для Rerank", + "hint": "Эффективно только для моделей qwen3-rerank. Рекомендуется писать на английском." + }, + "launch_model_if_not_running": { + "description": "Автозапуск модели", + "hint": "Если модель не запущена в Xinference, попытаться запустить её автоматически. Рекомендуется отключать в продакшене." + }, + "modalities": { + "description": "Возможности модели", + "hint": "Поддерживаемые модальности. Если модель не поддерживает изображения, снимите галочку с 'Image'.", + "labels": [ + "Текст", + "Изображение", + "Инструменты" + ] + }, + "custom_headers": { + "description": "Заголовки запроса", + "hint": "Пары ключ/значение будут добавлены в заголовки запроса (default_headers). Значения должны быть строками." + }, + "custom_extra_body": { + "description": "Параметры тела запроса", + "hint": "Добавление дополнительных параметров в запрос (temperature, top_p и др.).", + "template_schema": { + "temperature": { + "description": "Температура", + "hint": "Контролирует случайность, обычно от 0 до 2. Чем выше, тем более случайные ответы.", + "name": "Температура" + }, + "top_p": { + "description": "Top-p семплирование", + "hint": "Параметр ядерного семплирования (Nucleus sampling), обычно 0-1.", + "name": "Top-p" + }, + "max_tokens": { + "description": "Макс. токенов", + "hint": "Максимальное количество генерируемых токенов.", + "name": "Макс. токены" + } + } + }, + "gpt_weights_path": { + "description": "Путь к файлу модели GPT", + "hint": "Файл .ckpt. Используйте абсолютный путь без кавычек. Оставьте пустым для использования встроенной модели GPT_SoVITS." + }, + "sovits_weights_path": { + "description": "Путь к файлу модели SoVITS", + "hint": "Файл .pth. Используйте абсолютный путь без кавычек. Оставьте пустым для использования встроенной модели GPT_SoVITS." + }, + "gsv_default_parms": { + "description": "Параметры GPT_SoVITS по умолчанию", + "hint": "Путь к эталонному аудио и текст обязательны; остальные параметры опциональны.", + "gsv_ref_audio_path": { + "description": "Путь к эталонному аудио", + "hint": "Обязательно! Используйте абсолютный путь без кавычек." + }, + "gsv_prompt_text": { + "description": "Текст эталонного аудио", + "hint": "Обязательно! Укажите содержание эталонного аудио." + }, + "gsv_prompt_lang": { + "description": "Язык текста эталонного аудио", + "hint": "Язык текста эталонного аудио; по умолчанию китайский." + }, + "gsv_aux_ref_audio_paths": { + "description": "Пути к вспомогательным эталонным аудио", + "hint": "Вспомогательные файлы эталонного аудио; опционально." + }, + "gsv_text_lang": { + "description": "Язык текста", + "hint": "По умолчанию китайский." + }, + "gsv_top_k": { + "description": "Разнообразие речи", + "hint": "" + }, + "gsv_top_p": { + "description": "Порог ядерного семплирования", + "hint": "" + }, + "gsv_temperature": { + "description": "Случайность речи", + "hint": "" + }, + "gsv_text_split_method": { + "description": "Метод разделения текста", + "hint": "Варианты: `cut0` без разделения, `cut1` каждые 4 предложения, `cut2` каждые 50 знаков, `cut3` по китайской точке, `cut4` по английской точке, `cut5` по знакам препинания." + }, + "gsv_batch_size": { + "description": "Размер пакета (Batch size)", + "hint": "" + }, + "gsv_batch_threshold": { + "description": "Порог пакета (Batch threshold)", + "hint": "API Key" + }, + "gsv_split_bucket": { + "description": "Разделять текст на корзины для параллельной обработки", + "hint": "API Base URL" + }, + "gsv_speed_factor": { + "description": "Скорость воспроизведения речи", + "hint": "1 — исходная скорость." + }, + "gsv_fragment_interval": { + "description": "Интервал между сегментами речи", + "hint": "Скорость речи для синтеза, диапазон [0.5, 2], по умолчанию 1.0. Большее значение — быстрее." + }, + "gsv_streaming_mode": { + "description": "Включить потоковый режим", + "hint": "голос" + }, + "gsv_seed": { + "description": "Случайное число (Seed)", + "hint": "Для воспроизводимости результатов." + }, + "gsv_parallel_infer": { + "description": "Параллельный вывод (Inference)", + "hint": "reference_id" + }, + "gsv_repetition_penalty": { + "description": "Штраф за повторение", + "hint": "API Key" + }, + "gsv_media_type": { + "description": "Тип медиафайла", + "hint": "Рекомендуется: wav" + } + }, + "embedding_dimensions": { + "description": "Размерность эмбеддингов", + "hint": "Размерность векторов эмбеддингов. Зависит от модели. Должно быть указано верно для работы векторной базы данных." + }, + "embedding_model": { + "description": "Модель эмбеддингов", + "hint": "Имя модели эмбеддингов." + }, + "embedding_api_key": { + "description": "API Base URL" + }, + "embedding_api_base": { + "description": "Адрес прокси-сервера" + }, + "openai_embedding": { + "hint": "OpenAI Embedding автоматически добавляет /v1 при запросе." + }, + "gemini_embedding": { + "hint": "Gemini Embedding не требует ручного добавления /v1beta." + }, + "volcengine_cluster": { + "description": "Кластер Volcengine", + "hint": "Для моделей клонирования голоса выберите volcano_icl или volcano_icl_concurr; по умолчанию volcano_tts." + }, + "volcengine_voice_type": { + "description": "Голос Volcengine", + "hint": "Введите ID голоса (Voice_type)." + }, + "volcengine_speed_ratio": { + "description": "Скорость речи", + "hint": "Скорость речи, от 0.2 до 3.0, по умолчанию 1.0." + }, + "volcengine_volume_ratio": { + "description": "Громкость", + "hint": "Громкость, от 0.0 до 2.0, по умолчанию 1.0." + }, + "azure_tts_voice": { + "description": "Стиль голоса", + "hint": "Системное имя голоса" + }, + "azure_tts_style": { + "description": "Стиль", + "hint": "Стиль речи. Может выражать эмоции (счастье, сочувствие, спокойствие)." + }, + "azure_tts_role": { + "description": "Роль (опционально)", + "hint": "Ролевая модель. Голос может имитировать разные возрасты и пол без смены имени голоса. Если роль не поддерживается, атрибут игнорируется." + }, + "azure_tts_rate": { + "description": "Скорость речи", + "hint": "Контролирует скорость речи. От 0.5x до 2x от оригинала." + }, + "azure_tts_volume": { + "description": "Громкость речи", + "hint": "Контролирует громкость. От 0.0 до 100.0. По умолчанию 100.0." + }, + "azure_tts_region": { + "description": "Регион API", + "hint": "Регион обработки данных Azure TTS." + }, + "azure_tts_subscription_key": { + "description": "Ключ подписки (Subscription Key)", + "hint": "Ключ подписки Azure TTS (не токен)." + }, + "dashscope_tts_voice": { + "description": "Голос" + }, + "gm_resp_image_modal": { + "description": "Включить визуальную модальность", + "hint": "Если включено, ответы могут содержать изображения. Требует поддержки моделью. Совет: для генерации изображений отключите 'Распознавание участников'." + }, + "gm_native_search": { + "description": "Включить нативный поиск", + "hint": "Если включено, инструменты функций отключаются. Проверьте лимиты бесплатной квоты." + }, + "gm_native_coderunner": { + "description": "Включить нативный исполнитель кода", + "hint": "Если включено, инструменты функций отключаются." + }, + "gm_url_context": { + "description": "Включить контекст URL", + "hint": "Если включено, инструменты функций отключаются." + }, + "gm_safety_settings": { + "description": "Фильтры безопасности", + "hint": "Настройка уровня фильтрации контента. См. документацию Gemini API.", + "harassment": { + "description": "Харрасмент", + "hint": "Негативные или вредные комментарии" + }, + "hate_speech": { + "description": "Язык вражды", + "hint": "Грубый, неуважительный или нецензурный контент" + }, + "sexually_explicit": { + "description": "Сексуально откровенный контент", + "hint": "Упоминания сексуальных актов или другой непристойный контент" + }, + "dangerous_content": { + "description": "Опасный контент", + "hint": "Контент, поощряющий или способствующий вредному поведению" + } + }, + "gm_thinking_config": { + "description": "Настройки рассуждения (Thinking)", + "budget": { + "description": "Бюджет рассуждения", + "hint": "Указывает модели количество токенов рассуждения. См. документацию Google." + }, + "level": { + "description": "Уровень рассуждения", + "hint": "Рекомендуется для моделей Gemini 3+. Позволяет контролировать процесс рассуждения." + } + }, + "anth_thinking_config": { + "description": "Настройки рассуждения (Thinking)", + "type": { + "description": "Тип рассуждения", + "hint": "Установите 'adaptive' для Opus 4.6+ / Sonnet 4.6+ (рекомендуется)." + }, + "budget": { + "description": "Бюджет рассуждения", + "hint": "Параметр budget_tokens (минимум 1024). Устарело для моделей 4.6+." + }, + "effort": { + "description": "Уровень усилий", + "hint": "Контролирует глубину рассуждений в режиме 'adaptive'." + } + }, + "minimax-group-id": { + "description": "Группа пользователей", + "hint": "Доступно в Управлении аккаунтом -> Основная информация." + }, + "minimax-langboost": { + "description": "Целевой язык/диалект", + "hint": "Улучшает распознавание и качество речи для определенных языков/диалектов." + }, + "minimax-voice-speed": { + "description": "Скорость речи", + "hint": "API Key" + }, + "minimax-voice-vol": { + "description": "Громкость", + "hint": "Громкость синтеза от 0 до 10. Чем выше, тем громче." + }, + "minimax-voice-pitch": { + "description": "Высота голоса", + "hint": "Высота голоса от -12 до 12." + }, + "minimax-is-timber-weight": { + "description": "Включить смешанные голоса", + "hint": "Смешивание до четырех голосов с весами. Если включено, настройки одиночного голоса игнорируются." + }, + "minimax-timber-weight": { + "description": "Смешанные голоса", + "hint": "Список голосов и их весов (JSON-строка). См. документацию API." + }, + "minimax-voice-id": { + "description": "Одиночный голос", + "hint": "ID одиночного голоса; см. официальную документацию." + }, + "minimax-voice-emotion": { + "description": "Эмоция", + "hint": "Эмоция речи. 'auto' выбирает эмоцию на основе текста." + }, + "minimax-voice-latex": { + "description": "Читать формулы LaTeX", + "hint": "Чтение формул LaTeX (требуется правильное форматирование)." + }, + "minimax-voice-english-normalization": { + "description": "Нормализация английского текста", + "hint": "Улучшает чтение чисел, но немного увеличивает задержку." + }, + "rag_options": { + "description": "Опции RAG", + "hint": "Настройки извлечения из базы знаний. В приложениях Bailian отключает многоэтапные диалоги.", + "pipeline_ids": { + "description": "Список ID баз знаний", + "hint": "Извлечение из указанных баз данных Bailian." + }, + "file_ids": { + "description": "ID неструктурированных документов", + "hint": "Извлечение из указанных документов Bailian." + }, + "output_reference": { + "description": "Выводить ссылки на источники", + "hint": "Добавлять источники в конец ответа. По умолчанию False." + } + }, + "sensevoice_hint": { + "description": "Развертывание SenseVoice", + "hint": "Перед включением установите необходимые библиотеки: funasr, torch и др. Также требуется ffmpeg." + }, + "is_emotion": { + "description": "Распознавание эмоций", + "hint": "Включить распознавание эмоций (радость, грусть, гнев и т.д.)." + }, + "stt_model": { + "description": "Имя модели", + "hint": "Имя модели на ModelScope." + }, + "variables": { + "description": "Фиксированные переменные воркфлоу", + "hint": "Входные переменные для воркфлоу. Можно также задавать через /set в чате." + }, + "dashscope_app_type": { + "description": "Тип приложения", + "hint": "Тип приложения Bailian." + }, + "timeout": { + "description": "Таймаут (сек)", + "hint": "Максимальное время ожидания ответа." + }, + "openai-tts-voice": { + "description": "API Base URL", + "hint": "Голоса OpenAI TTS: alloy, echo и др." + }, + "fishaudio-tts-character": { + "description": "Персонаж", + "hint": "Персонаж Fishaudio. По умолчанию Klee." + }, + "fishaudio-tts-reference-id": { + "description": "Coze API Key", + "hint": "ID модели Fishaudio; используется вместо имени роли." + }, + "whisper_hint": { + "description": "Заметки по локальному развертыванию Whisper", + "hint": "Перед включением установите openai-whisper и ffmpeg." + }, + "id": { + "description": "ID провайдера" + }, + "type": { + "description": "Тип провайдера" + }, + "provider_type": { + "description": "Тип возможностей провайдера" + }, + "enable": { + "description": "Включить" + }, + "key": { + "description": "Ключ Coze API для доступа к сервисам Coze." + }, + "api_base": { + "description": "Bot ID" + }, + "proxy": { + "description": "API Base URL", + "hint": "Индивидуальный прокси для этого провайдера." + }, + "model": { + "description": "Имя модели", + "hint": "Имя модели, напр. gpt-4o-mini." + }, + "max_context_tokens": { + "description": "Размер окна контекста модели", + "hint": "Максимальное количество токенов контекста. Если 0 — автозаполнение." + }, + "dify_api_key": { + "description": "Базовый URL для Coze API. По умолчанию: https://api.coze.cn", + "hint": "API-ключ Dify (обязательно)." + }, + "dify_api_base": { + "description": "API Base URL", + "hint": "Base URL API Dify. По умолчанию: https://api.dify.ai/v1" + }, + "dify_api_type": { + "description": "Тип приложения Dify", + "hint": "Тип API Dify (chat, agent, workflow и т.д.)." + }, + "dify_workflow_output_key": { + "description": "Имя выходной переменной воркфлоу Dify", + "hint": "Имя выходной переменной для типа 'workflow'." + }, + "dify_query_input_key": { + "description": "Имя переменной ввода промпта", + "hint": "Имя переменной для текста сообщения." + }, + "coze_api_key": { + "description": "Coze API Key", + "hint": "Coze API key for accessing Coze services." + }, + "bot_id": { + "description": "Bot ID", + "hint": "Bot ID Coze, полученный на платформе." + }, + "coze_api_base": { + "description": "API Base URL", + "hint": "Base URL for the Coze API. Default: https://api.coze.cn" + }, + "deerflow_api_base": { + "description": "API Base URL", + "hint": "URL шлюза DeerFlow. По умолчанию: http://127.0.0.1:2026" + }, + "deerflow_api_key": { + "description": "API-ключ DeerFlow", + "hint": "Опционально. Заполните, если шлюз защищен Bearer-авторизацией." + }, + "deerflow_auth_header": { + "description": "Заголовок Authorization", + "hint": "Опционально. Свой заголовок Authorization; приоритетнее ключа." + }, + "deerflow_assistant_id": { + "description": "ID ассистента", + "hint": "Assistant ID для LangGraph. По умолчанию lead_agent." + }, + "deerflow_model_name": { + "description": "Переопределение имени модели", + "hint": "Опционально. Переопределяет модель DeerFlow." + }, + "deerflow_thinking_enabled": { + "description": "Включить режим рассуждения (Thinking)" + }, + "deerflow_plan_mode": { + "description": "Включить режим планирования (Plan)", + "hint": "Управляет is_plan_mode в DeerFlow." + }, + "deerflow_subagent_enabled": { + "description": "Включить субагента", + "hint": "Управляет subagent_enabled в DeerFlow." + }, + "deerflow_max_concurrent_subagents": { + "description": "Макс. параллельных субагентов", + "hint": "Максимум параллельных субагентов. По умолчанию 3." + }, + "deerflow_recursion_limit": { + "description": "Лимит рекурсии", + "hint": "Лимит рекурсии для LangGraph." + }, + "auto_save_history": { + "description": "История диалогов управляется Coze", + "hint": "Если включено, Coze управляет историей. Локальный контекст AstrBot будет работать в режиме чтения." + } + } + }, + "help": { + "documentation": "Официальная документация", + "support": "Группа поддержки", + "helpText": "Непонятно, как настроить? См. {documentation} или {support}.", + "helpPrefix": "Непонятно, как настроить? См.", + "helpMiddle": "или", + "helpSuffix": "." + } +} \ No newline at end of file diff --git a/dashboard/src/i18n/locales/ru-RU/features/config.json b/dashboard/src/i18n/locales/ru-RU/features/config.json new file mode 100644 index 000000000..7c456ab9d --- /dev/null +++ b/dashboard/src/i18n/locales/ru-RU/features/config.json @@ -0,0 +1,129 @@ +{ + "title": "Конфигурация", + "subtitle": "Управление системными настройками", + "editor": { + "visual": "Визуальный редактор", + "code": "Редактор кода", + "revertCode": "Отменить изменения", + "applyConfig": "Применить", + "applyTip": "Кнопка «Применить» временно фиксирует изменения в визуальном редакторе. Чтобы сохранить их на постоянной основе, нажмите кнопку «Сохранить» в правом нижнем углу." + }, + "actions": { + "save": "Сохранить", + "delete": "Удалить", + "add": "Добавить", + "reset": "Сбросить настройки", + "export": "Экспорт", + "import": "Импорт", + "validate": "Проверить" + }, + "help": { + "documentation": "Документация", + "support": "Поддержка", + "helpText": "Нужна помощь? См. {documentation} или обратитесь в {support}.", + "helpPrefix": "Нужна помощь? См.", + "helpMiddle": "или обратитесь в", + "helpSuffix": "." + }, + "messages": { + "configApplied": "Настройки применены образно. Нажмите «Сохранить» для окончательной записи.", + "configApplyError": "Ошибка применения: некорректный формат JSON.", + "unsavedChangesNotice": "Есть несохраненные изменения. Пожалуйста, нажмите «Сохранить», чтобы они вступили в силу.", + "saveSuccess": "Настройки успешно сохранены", + "saveError": "Ошибка при сохранении", + "loadError": "Ошибка при загрузке настроек", + "deleteSuccess": "Удалено", + "deleteError": "Ошибка удаления", + "updateSuccess": "Обновлено", + "updateError": "Ошибка обновления" + }, + "sections": { + "general": "Основные", + "advanced": "Расширенные", + "security": "Безопасность", + "appearance": "Внешний вид", + "notification": "Уведомления" + }, + "general": { + "botName": "Имя бота", + "language": "Язык интерфейса", + "timezone": "Часовой пояс", + "autoSave": "Автосохранение", + "debugMode": "Режим отладки" + }, + "advanced": { + "logLevel": "Уровень логирования", + "maxConnections": "Макс. соединений", + "timeout": "Тайм-аут", + "retryAttempts": "Попытки повтора", + "cacheSize": "Размер кэша" + }, + "security": { + "apiKey": "Ключ API", + "allowedHosts": "Разрешенные хосты", + "rateLimit": "Лимит запросов", + "encryption": "Шифрование" + }, + "configSelection": { + "selectConfig": "Выбор конфигурации", + "normalConfig": "Обычная", + "systemConfig": "Системная" + }, + "search": { + "placeholder": "Поиск по настройкам (поле/описание/подсказка)", + "noResult": "Совпадений не найдено" + }, + "configManagement": { + "title": "Управление конфигурациями", + "description": "AstrBot поддерживает несколько конфигураций для разных ботов. По умолчанию используется «default».", + "newConfig": "Новая конфигурация", + "editConfig": "Изменить конфигурацию", + "manageConfigs": "Управление файлами...", + "configName": "Имя", + "fillConfigName": "Введите имя конфигурации", + "confirmDelete": "Вы уверены, что хотите удалить конфигурацию «{name}»? Это действие необратимо.", + "pleaseEnterName": "Пожалуйста, введите имя", + "createFailed": "Ошибка создания конфигурации", + "deleteFailed": "Ошибка удаления", + "updateFailed": "Ошибка обновления" + }, + "buttons": { + "cancel": "Отмена", + "create": "Создать", + "update": "Обновить" + }, + "codeEditor": { + "title": "Редактирование файла" + }, + "fileUpload": { + "button": "Файлы", + "dialogTitle": "Загруженные файлы", + "dropzone": "Загрузить файлы", + "allowedTypes": "Разрешенные типы: {types}", + "empty": "Файлов нет", + "statusMissing": "Файл отсутствует", + "statusUnconfigured": "Не в конфиге", + "uploadSuccess": "Загружено файлов: {count}", + "uploadFailed": "Ошибка загрузки", + "loadFailed": "Ошибка получения списка файлов", + "fileTooLarge": "Файл слишком велик (макс. {max} МБ): {name}", + "deleteSuccess": "Файл удален", + "deleteFailed": "Ошибка удаления", + "addToConfig": "Добавлено в конфигурацию", + "fileCount": "Файлов: {count}", + "done": "Готово" + }, + "unsavedChangesWarning": { + "dialogTitle": "Несохраненные изменения", + "leavePage": "У вас есть несохраненные изменения. Сохранить перед уходом?", + "switchConfig": "Переключение конфигурации приведет к потере несохраненных изменений. Сохранить?", + "options": { + "save": "Сохранить", + "saveAndSwitch": "Сохранить и переключить", + "discardAndSwitch": "Сбросить и переключить", + "closeCard": "Закрыть", + "confirm": "ОК", + "cancel": "Отмена" + } + } +} \ No newline at end of file diff --git a/dashboard/src/i18n/locales/ru-RU/features/console.json b/dashboard/src/i18n/locales/ru-RU/features/console.json new file mode 100644 index 000000000..1bbd45d34 --- /dev/null +++ b/dashboard/src/i18n/locales/ru-RU/features/console.json @@ -0,0 +1,18 @@ +{ + "title": "Логи платформы", + "autoScroll": { + "enabled": "Автопрокрутка включена", + "disabled": "Автопрокрутка выключена" + }, + "pipInstall": { + "button": "Установить pip-пакет", + "dialogTitle": "Установка Pip-пакета", + "packageLabel": "*Имя пакета, например: llmtuner", + "mirrorLabel": "Использовать зеркало PyPI (опционально)", + "mirrorHint": "Приоритет зеркала PyPI > настройки «Зеркало репозитория PyPI»", + "installButton": "Установить" + }, + "debugHint": { + "text": "Для отображения Debug-логов необходимо установить соответствующий уровень в «Конфигурация → Система → Уровень логирования»" + } +} \ No newline at end of file diff --git a/dashboard/src/i18n/locales/ru-RU/features/conversation.json b/dashboard/src/i18n/locales/ru-RU/features/conversation.json new file mode 100644 index 000000000..33ebefa74 --- /dev/null +++ b/dashboard/src/i18n/locales/ru-RU/features/conversation.json @@ -0,0 +1,102 @@ +{ + "title": "Управление диалогами", + "subtitle": "Просмотр и управление историей сообщений", + "filters": { + "title": "Фильтры", + "platform": "ID бота", + "type": "Тип", + "search": "Поиск по ключевым словам", + "reset": "Сбросить" + }, + "history": { + "title": "История", + "refresh": "Обновить" + }, + "batch": { + "deleteSelected": "Удалить выбранные ({count})", + "exportSelected": "Экспорт выбранных ({count})" + }, + "pagination": { + "itemsPerPage": "на странице", + "showingItems": "Показано {start}-{end} из {total}" + }, + "table": { + "headers": { + "title": "Заголовок диалога", + "platform": "ID бота", + "type": "Тип сообщения", + "cid": "ID диалога", + "umo": "Источник сообщения", + "sessionId": "ID сессии", + "createdAt": "Создан", + "updatedAt": "Обновлен", + "actions": "Действия" + } + }, + "actions": { + "view": "Просмотр", + "edit": "Редактировать", + "delete": "Удалить" + }, + "messageTypes": { + "group": "Группа", + "friend": "ЛС", + "unknown": "Неизвестно" + }, + "status": { + "noTitle": "Без заголовка", + "unknown": "Неизвестно", + "noData": "История диалогов пуста", + "emptyContent": "Содержимое диалога пусто", + "audioNotSupported": "Ваш браузер не поддерживает воспроизведение аудио." + }, + "dialogs": { + "view": { + "title": "Детали диалога", + "editMode": "Режим редактирования", + "previewMode": "Режим просмотра", + "saveChanges": "Сохранить изменения", + "close": "Закрыть", + "confirmClose": "У вас есть несохраненные изменения. Вы уверены, что хотите закрыть?" + }, + "edit": { + "title": "Изменить информацию", + "titleLabel": "Заголовок диалога", + "titlePlaceholder": "Введите заголовок", + "cancel": "Отмена", + "save": "Сохранить" + }, + "delete": { + "title": "Подтверждение удаления", + "message": "Вы уверены, что хотите удалить диалог «{title}»? Это действие необратимо.", + "cancel": "Отмена", + "confirm": "Удалить" + }, + "batchDelete": { + "title": "Массовое удаление", + "message": "Вы уверены, что хотите удалить {count} выбранных диалогов? Это действие необратимо!", + "andMore": "и еще {count}", + "cancel": "Отмена", + "confirm": "Удалить всё", + "warning": "Внимание: удаление нельзя будет отменить!" + } + }, + "messages": { + "fetchError": "Не удалось загрузить список диалогов", + "saveSuccess": "Сохранено", + "saveError": "Ошибка сохранения", + "deleteSuccess": "Удалено", + "deleteError": "Ошибка удаления", + "historyError": "Не удалось загрузить историю диалога", + "historySaveSuccess": "История сохранена", + "historySaveError": "Ошибка сохранения истории", + "invalidJson": "Некорректный формат JSON", + "noItemSelected": "Сначала выберите диалоги для удаления", + "batchDeleteSuccess": "Успешно удалено {count} диалогов", + "batchDeleteError": "Ошибка массового удаления", + "batchDeletePartial": "Удаление завершено: успешно {deleted}, ошибок {failed}", + "exportSuccess": "Экспорт завершен", + "exportError": "Ошибка экспорта", + "noItemSelectedForExport": "Сначала выберите диалоги для экспорта" + } +} \ No newline at end of file diff --git a/dashboard/src/i18n/locales/ru-RU/features/cron.json b/dashboard/src/i18n/locales/ru-RU/features/cron.json new file mode 100644 index 000000000..5bc98ab13 --- /dev/null +++ b/dashboard/src/i18n/locales/ru-RU/features/cron.json @@ -0,0 +1,66 @@ +{ + "page": { + "title": "Запланированные задачи", + "beta": "Экспериментальные функции", + "subtitle": "Управление будущими задачами AstrBot. Бот автоматически проснется, выполнит задачу и отправит результат. Требуется включить «Проактивные способности» в конфигурации.", + "proactive": { + "supported": "Отправка результатов поддерживается на платформах: {platforms}", + "unsupported": "Нет платформ, поддерживающих проактивные сообщения. Включите их в настройках платформ." + } + }, + "actions": { + "create": "Новая задача", + "refresh": "Обновить", + "delete": "Удалить", + "cancel": "Отмена", + "submit": "Создать" + }, + "table": { + "title": "Список задач", + "empty": "Задач пока нет.", + "headers": { + "name": "Имя", + "type": "Тип", + "cron": "Cron", + "session": "ID сессии", + "nextRun": "Следующий запуск", + "lastRun": "Последний запуск", + "note": "Описание", + "actions": "Действия" + }, + "type": { + "once": "Разовая", + "recurring": "Повторяющаяся", + "activeAgent": "Активный агент", + "workflow": "Рабочий процесс", + "unknown": "{type}" + }, + "timezoneLocal": "Местное время", + "notAvailable": "—" + }, + "form": { + "title": "Создать задачу", + "chatHint": "Вы можете ставить задачи прямо в чате, AstrBot создаст их автоматически без заполнения этой формы.", + "runOnce": "Разовая задача", + "name": "Имя задачи", + "note": "Описание", + "cron": "Cron-выражения", + "cronPlaceholder": "0 9 * * *", + "runAt": "Время запуска", + "session": "Целевая сессия (platform_id:message_type:session_id)", + "timezone": "Часовой пояс (опционально, напр. Europe/Moscow)", + "enabled": "Включено" + }, + "messages": { + "loadFailed": "Ошибка загрузки задач", + "updateFailed": "Ошибка обновления", + "deleteSuccess": "Удалено", + "deleteFailed": "Ошибка удаления", + "sessionRequired": "Укажите сессию", + "noteRequired": "Заполните описание", + "cronRequired": "Укажите Cron-выражение", + "runAtRequired": "Выберите время запуска", + "createSuccess": "Задача создана", + "createFailed": "Ошибка создания" + } +} \ No newline at end of file diff --git a/dashboard/src/i18n/locales/ru-RU/features/dashboard.json b/dashboard/src/i18n/locales/ru-RU/features/dashboard.json new file mode 100644 index 000000000..b4ecd50ab --- /dev/null +++ b/dashboard/src/i18n/locales/ru-RU/features/dashboard.json @@ -0,0 +1,65 @@ +{ + "title": "Логи платформы", + "subtitle": "Мониторинг и статистика в реальном времени", + "lastUpdate": "Последнее обновление", + "status": { + "loading": "Загрузка...", + "dataError": "Ошибка получения данных", + "noticeError": "Ошибка получения объявлений", + "online": "В сети", + "uptime": "Время работы", + "memoryUsage": "Память" + }, + "stats": { + "totalMessage": { + "title": "Всего сообщений", + "subtitle": "Все сообщения со всех платформ" + }, + "onlinePlatform": { + "title": "Платформы", + "subtitle": "Количество подключенных платформ" + }, + "runningTime": { + "title": "Время работы", + "subtitle": "Общее время работы системы", + "format": "{hours} ч. {minutes} мин. {seconds} сек." + }, + "memoryUsage": { + "title": "Память", + "subtitle": "Использование оперативной памяти", + "cpuLoad": "Загрузка CPU", + "status": { + "good": "Отлично", + "normal": "Нормально", + "high": "Высокая" + } + } + }, + "charts": { + "messageTrend": { + "title": "Тренды сообщений", + "subtitle": "Изменение количества сообщений во времени", + "totalMessages": "Всего сообщений", + "dailyAverage": "В среднем за день", + "growthRate": "Скорость роста", + "timeLabel": "Время", + "messageCount": "Кол-во сообщений", + "timeRanges": { + "1day": "За 1 день", + "3days": "За 3 дня", + "1week": "За 7 дней", + "1month": "За 30 дней" + } + }, + "platformStat": { + "title": "Статистика по платформам", + "subtitle": "Распределение сообщений по платформам", + "total": "Всего", + "noData": "Нет данных по платформам", + "messageUnit": "шт.", + "platformCount": "Кол-во платформ", + "mostActive": "Самый активный", + "totalPercentage": "Доля от общего числа" + } + } +} \ No newline at end of file diff --git a/dashboard/src/i18n/locales/ru-RU/features/extension.json b/dashboard/src/i18n/locales/ru-RU/features/extension.json new file mode 100644 index 000000000..2ff06f573 --- /dev/null +++ b/dashboard/src/i18n/locales/ru-RU/features/extension.json @@ -0,0 +1,358 @@ +{ + "title": "Плагины", + "subtitle": "Управление и настройка расширений системы", + "tabs": { + "installedPlugins": "Плагины AstrBot", + "market": "Магазин плагинов", + "installedMcpServers": "MCP", + "skills": "Навыки", + "handlersOperation": "Управление поведением" + }, + "titles": { + "installedAstrBotPlugins": "Установленные плагины AstrBot" + }, + "failedPlugins": { + "title": "Ошибка загрузки ({count})", + "hint": "Эти плагины не удалось загрузить. Вы можете попробовать перезагрузить их или удалить.", + "columns": { + "plugin": "Плагин", + "error": "Ошибка" + } + }, + "search": { + "placeholder": "Поиск плагинов...", + "marketPlaceholder": "Поиск в магазине..." + }, + "filters": { + "all": "Все" + }, + "views": { + "card": "Плитка", + "list": "Список" + }, + "buttons": { + "showSystemPlugins": "Показать системные", + "hideSystemPlugins": "Скрыть системные", + "install": "Установить", + "uninstall": "Удалить", + "update": "Обновить", + "reload": "Перезагрузить", + "enable": "Включить", + "disable": "Выключить", + "configure": "Настроить", + "viewInfo": "Детали", + "viewDocs": "Документация", + "viewRepo": "Репозиторий", + "close": "Закрыть", + "save": "Сохранить", + "saveAndClose": "Сохранить и закрыть", + "cancel": "Отмена", + "actions": "Действия", + "back": "Назад", + "selectFile": "Выбрать файл", + "refresh": "Обновить", + "updateAll": "Обновить все", + "deleteSource": "Удалить источник", + "reshuffle": "Мне повезет!" + }, + "status": { + "enabled": "Включен", + "disabled": "Выключен", + "system": "Системный", + "loading": "Загрузка...", + "installed": "Установлен", + "unknown": "Неизвестно" + }, + "tooltips": { + "enable": "Включить", + "disable": "Выключить", + "reload": "Перезагрузить", + "configure": "Настроить", + "viewInfo": "Просмотр поведения", + "viewDocs": "Документация", + "update": "Обновить", + "uninstall": "Удалить" + }, + "table": { + "headers": { + "name": "Имя", + "description": "Описание", + "version": "Версия", + "author": "Автор", + "status": "Статус", + "actions": "Действия", + "stars": "Звезды", + "lastUpdate": "Обновлен", + "tags": "Теги", + "eventType": "Тип события", + "specificType": "Тип", + "trigger": "Триггер" + } + }, + "empty": { + "noPlugins": "Плагины не найдены", + "noPluginsDesc": "Попробуйте установить новые плагины или включите отображение системных." + }, + "market": { + "recommended": "🥳 Рекомендуем", + "allPlugins": "📦 Все плагины", + "showFullName": "Полное имя", + "devDocs": "Документация для разработчиков", + "submitRepo": "Добавить репозиторий", + "customSource": "Свои источники", + "source": "Источник", + "availableSources": "Доступные источники", + "sourceManagement": "Управление источниками", + "addSource": "Добавить источник", + "sourceName": "Имя", + "sourceUrl": "Исходный URL", + "defaultSource": "Источник по умолчанию", + "removeSource": "Удалить источник", + "confirmRemoveSource": "Вы уверены, что хотите удалить этот источник плагинов?", + "sourceAdded": "Источник успешно добавлен", + "sourceRemoved": "Источник удален", + "sourceError": "Ошибка операции", + "selectSource": "Выбрать источник", + "currentSource": "Текущий источник", + "editSource": "Изменить источник", + "sourceUpdated": "Источник обновлен", + "defaultOfficialSource": "Официальный источник", + "sourceExists": "Этот источник уже есть в списке", + "installPlugin": "Установить плагин", + "randomPlugins": "🎲 Случайные плагины", + "showRandomPlugins": "Показать случайные", + "hideRandomPlugins": "Скрыть случайные", + "sourceSafetyWarning": "Даже при использовании источников по умолчанию мы не можем гарантировать 100% безопасность и стабильность сторонних плагинов. Пожалуйста, будьте внимательны." + }, + "sort": { + "by": "Сортировать по", + "default": "По умолчанию", + "installTime": "Дате установки", + "name": "Имени", + "stars": "Звездам", + "author": "Автору", + "updated": "Дате обновления", + "updateStatus": "Статусу обновления", + "ascending": "По возрастанию", + "descending": "По убыванию" + }, + "tags": { + "danger": "Опасно" + }, + "dialogs": { + "error": { + "title": "Ошибка", + "checkConsole": "Подробности смотрите в логах платформы" + }, + "config": { + "title": "Настройка плагина", + "noConfig": "У этого плагина нет настраиваемых параметров" + }, + "loading": { + "title": "Загрузка...", + "logs": "Логи" + }, + "uninstall": { + "title": "Подтверждение удаления", + "message": "Вы уверены, что хотите удалить этот плагин?", + "deleteConfig": "Удалить файл конфигурации плагина", + "deleteData": "Удалить сохраненные данные плагина", + "configHint": "Конфиг находится в data/config", + "dataHint": "Данные находятся в data/plugin_data и data/plugins_data" + }, + "install": { + "title": "Установка плагина", + "fromFile": "Из файла", + "fromUrl": "По ссылке", + "supportPlatformsCount": "Поддерживает платформ: {count}" + }, + "danger_warning": { + "title": "Внимание!", + "message": "Этот плагин может содержать небезопасный код или функции, которые могут привести к нестабильности системы или потере данных. Вы уверены, что хотите продолжить установку?", + "confirm": "Продолжить", + "cancel": "Отмена" + }, + "versionCompatibility": { + "title": "Предупреждение о версии", + "message": "Требуемая плагином версия AstrBot не совпадает с вашей текущей версией. Вы можете продолжить установку на свой страх и риск.", + "confirm": "Игнорировать и установить", + "cancel": "Отмена" + }, + "forceUpdate": { + "title": "Новых версий не найдено", + "message": "Новых версий не обнаружено. Выполнить принудительную переустановку из удаленного репозитория?", + "confirm": "Принудительно" + }, + "updateAllConfirm": { + "title": "Обновить всё", + "message": "Обновить все плагины ({count} шт.)? Это может занять некоторое время.", + "confirm": "Подтвердить" + } + }, + "messages": { + "uninstalling": "Удаление", + "refreshing": "Обновление списка плагинов...", + "refreshSuccess": "Список плагинов обновлен", + "refreshFailed": "Ошибка при обновлении списка", + "operationFailed": "Ошибка операции", + "reloadSuccess": "Перезагрузка завершена", + "reloadFailed": "Ошибка перезагрузки", + "updateSuccess": "Обновление завершено", + "addSuccess": "Успешно добавлено", + "saveSuccess": "Сохранено", + "deleteSuccess": "Удалено", + "installing": "Установка из файла...", + "installingFromUrl": "Установка по ссылке...", + "installFailed": "Ошибка установки:", + "getMarketDataFailed": "Ошибка получения данных магазина:", + "hasUpdate": "Доступно обновление:", + "confirmDelete": "Вы уверены, что хотите удалить плагин?", + "fillUrlOrFile": "Укажите ссылку или выберите файл", + "dontFillBoth": "Пожалуйста, используйте либо ссылку, либо файл, но не оба сразу", + "supportedFormats": "Поддерживаются файлы плагинов в формате .zip", + "updateAllSuccess": "Все плагины успешно обновлены", + "updateAllFailed": "Ошибок при обновлении: {failed} из {total}:", + "fillSourceNameAndUrl": "Пожалуйста, введите имя и адрес источника", + "invalidUrl": "Введите корректный URL", + "enterJsonUrl": "Введите URL, возвращающий список плагинов в формате JSON" + }, + "upload": { + "fromFile": "Загрузить файл", + "fromUrl": "Указать ссылку", + "selectFile": "Выбрать файл", + "enterUrl": "Ссылка на репозиторий" + }, + "skills": { + "modeLocal": "Локальные навыки", + "modeNeo": "Навыки Neo", + "actions": "Действия", + "upload": "Загрузить навыки", + "refresh": "Обновить", + "empty": "Навыки не найдены", + "emptyHint": "Пожалуйста, загрузите архив с навыками", + "uploadDialogTitle": "Загрузка навыков", + "uploadHint": "Поддерживается массовая загрузка zip-архивов. Вы также можете перетащить файлы в это окно. Система автоматически проверит структуру каждого архива.", + "structureRequirement": "Архив должен содержать одну корневую папку (например, `skillname/`), внутри которой обязательно должен находиться файл `SKILL.md`.", + "abilityMultiple": "Поддержка массовой загрузки", + "abilityValidate": "Автопроверка `SKILL.md`", + "abilitySkip": "Пропуск дубликатов", + "selectFile": "Выбрать файл", + "selectFiles": "Выбрать файлы", + "dropzoneTitle": "Перетащите zip-файлы сюда", + "dropzoneAction": "или нажмите, чтобы выбрать файлы на компьютере", + "dropzoneHint": "Система проверит структуру архивов перед загрузкой", + "fileListTitle": "Очередь загрузки", + "fileListEmpty": "Здесь будет отображаться статус проверки и загрузки файлов", + "uploading": "Загрузка...", + "batchResultTitle": "Результаты загрузки", + "batchResultSummary": "Всего: {total}, успешно: {success}", + "batchSuccessList": "Успешно загружено", + "batchFailedList": "Ошибка загрузки", + "confirm": "ОК", + "confirmUpload": "Начать загрузку", + "cancel": "Отмена", + "statusWaiting": "В очереди", + "statusUploading": "Загрузка...", + "statusSuccess": "Готово", + "statusError": "Ошибка структуры", + "statusSkipped": "Пропущено", + "summaryTotal": "Всего: {count}", + "summaryReady": "Готовы: {count}", + "summarySuccess": "Успешно: {count}", + "summaryFailed": "Ошибок: {count}", + "summarySkipped": "Дубликатов: {count}", + "validationReady": "Ожидает загрузки (проверка структуры будет выполнена автоматически)", + "validationZipOnly": "Допускаются только zip-архивы", + "validationDuplicate": "Файл уже есть в списке, пропуск", + "validationUploading": "Проверка и загрузка...", + "validationUploadFailed": "Ошибка загрузки, попробуйте еще раз", + "validationUploadedAs": "Установлено как {name}", + "validationNoResult": "Результат не получен, проверьте логи платформы", + "noDescription": "Нет описания", + "path": "Путь", + "uploadSuccess": "Успешно загружено", + "uploadFailed": "Ошибка загрузки", + "download": "Скачать", + "downloadSuccess": "Скачивание начато", + "downloadFailed": "Ошибка скачивания", + "loadFailed": "Не удалось загрузить навыки", + "updateSuccess": "Обновлено", + "updateFailed": "Ошибка обновления", + "deleteTitle": "Подтверждение удаления", + "deleteMessage": "Вы уверены, что хотите удалить этот навык?", + "deleteSuccess": "Удалено", + "deleteFailed": "Ошибка удаления", + "neoSkillKey": "Фильтр по ключу", + "neoStatus": "Статус кандидата", + "neoStage": "Этап публикации", + "neoFilterHint": "Фильтрация записей о публикации", + "neoAll": "Все", + "neoCandidates": "Кандидаты Neo", + "neoReleases": "Релизы Neo", + "neoLoadFailed": "Ошибка загрузки данных Neo Skills", + "neoPass": "Одобрить", + "neoReject": "Отклонить", + "neoEvaluateSuccess": "Оценка обновлена", + "neoEvaluateFailed": "Ошибка обновления оценки", + "neoPromoteSuccess": "Опубликовано", + "neoPromoteFailed": "Ошибка публикации", + "neoRollback": "Откат", + "neoRollbackSuccess": "Откат выполнен", + "neoRollbackFailed": "Ошибка отката", + "neoDeactivate": "Деактивация", + "neoDeactivateSuccess": "Деактивировано", + "neoDeactivateFailed": "Ошибка деактивации", + "neoSync": "Синхронизация", + "neoSyncSuccess": "Синхронизировано", + "neoSyncFailed": "Ошибка синхронизации", + "neoDelete": "Удалить", + "neoDeleteSuccess": "Удалено", + "neoDeleteFailed": "Ошибка удаления", + "neoPayloadTitle": "Детали Neo Payload", + "neoPayloadFailed": "Ошибка чтения Payload", + "runtimeNoneWarning": "Среда выполнения Computer Use не задана. Навыки могут не работать, так как нет активного окружения.", + "runtimeHint": "Установите среду выполнения в «local» или «sandbox» в настройках способностей использования компьютера.", + "neoRuntimeRequired": "Neo Skills доступны только в среде sandbox с драйвером shipyard_neo.", + "sourceLocalOnly": "Локальный навык", + "sourceSandboxOnly": "Предустановленный Sandbox навык", + "sourceBoth": "Локальный + Sandbox", + "sandboxDiscoveryPending": "Предустановленные Sandbox навыки не найдены. Запустите сессию Sandbox хотя бы один раз.", + "sandboxPresetReadonly": "Предустановленные навыки Sandbox доступны только для чтения и не могут быть удалены здесь." + }, + "card": { + "actions": { + "pluginConfig": "Настройки", + "uninstallPlugin": "Удалить", + "reloadPlugin": "Перезагрузить", + "togglePlugin": "Плагин", + "viewHandlers": "Действия", + "updateTo": "Обновить до", + "reinstall": "Переустановить" + }, + "status": { + "hasUpdate": "Доступно обновление", + "disabled": "Плагин выключен", + "handlersCount": "действий", + "supportPlatform": "Платформы", + "supportPlatformsCount": "Платформ: {count}", + "astrbotVersion": "Требуемая версия AstrBot" + }, + "alt": { + "logo": "логотип", + "extensionIcon": "иконка расширения" + }, + "errors": { + "confirmNotRegistered": "$confirm не зарегистрирован" + } + }, + "conflicts": { + "title": "Конфликт команд", + "message": "Обнаружены конфликтующие команды. Это может привести к некорректной работе. Рекомендуется разрешить конфликты в панели «Управление командами».", + "pairs": "конфликтующих пар", + "goToManage": "Управление", + "later": "Позже" + }, + "pluginChangelog": { + "menuTitle": "Журнал изменений" + } +} \ No newline at end of file diff --git a/dashboard/src/i18n/locales/ru-RU/features/knowledge-base/detail.json b/dashboard/src/i18n/locales/ru-RU/features/knowledge-base/detail.json new file mode 100644 index 000000000..10d2109bd --- /dev/null +++ b/dashboard/src/i18n/locales/ru-RU/features/knowledge-base/detail.json @@ -0,0 +1,118 @@ +{ + "title": "Детали базы знаний", + "backToList": "К списку", + "tabs": { + "overview": "Обзор", + "documents": "Документы", + "retrieval": "Поиск", + "sessions": "Сессии", + "settings": "Настройки" + }, + "overview": { + "title": "Информация", + "name": "Название", + "description": "Описание", + "emoji": "Иконка", + "createdAt": "Создана", + "updatedAt": "Обновлена", + "stats": "Статистика", + "docCount": "Количество документов", + "chunkCount": "Количество фрагментов", + "embeddingModel": "Embedding модель", + "rerankModel": "Rerank модель", + "notSet": "не выбрано" + }, + "documents": { + "title": "Список документов", + "upload": "Загрузить", + "empty": "Документов нет", + "name": "Имя файла", + "type": "Тип", + "size": "Размер", + "chunks": "Фрагменты", + "createdAt": "Дата загрузки", + "actions": "Действия", + "view": "Смотреть", + "delete": "Удалить", + "deleteConfirm": "Вы уверены, что хотите удалить «{name}»?", + "deleteWarning": "Это удалит файл и все его фрагменты из индекса.", + "uploading": "Загрузка...", + "uploadSuccess": "Файл успешно загружен", + "uploadFailed": "Ошибка загрузки", + "deleteSuccess": "Файл удален", + "deleteFailed": "Ошибка удаления" + }, + "upload": { + "title": "Добавление контента", + "selectFile": "Файл", + "dropzone": "Нажмите или перетащите файл сюда", + "supportedFormats": "Форматы: ", + "maxSize": "Максимум: 128MB", + "chunkSettings": "Фрагментация", + "batchSettings": "Пакетная обработка", + "cleaningSettings": "Очистка данных", + "enableCleaning": "Включить очистку контента", + "cleaningProvider": "Сервис для очистки", + "cleaningProviderHint": "LLM провайдер для суммаризации и извлечения смыслов из веб-страниц", + "chunkSize": "Размер чанка", + "chunkSizeHint": "Символов в блоке (по умолчанию: 512)", + "chunkOverlap": "Перекрытие", + "chunkOverlapHint": "Перекрытие между блоками (по умолчанию: 50)", + "batchSize": "Размер пакета", + "batchSizeHint": "Блоков за один запрос (по умолчанию: 32)", + "tasksLimit": "Лимит задач", + "tasksLimitHint": "Макс. параллельных потоков (по умолчанию: 3)", + "maxRetries": "Попытки", + "maxRetriesHint": "Повторов при сбое (по умолчанию: 3)", + "cancel": "Отмена", + "submit": "Загрузить", + "fileRequired": "Пожалуйста, выберите файл", + "fileUpload": "Загрузка файла", + "fromUrl": "Из URL", + "urlPlaceholder": "Ссылка на веб-страницу", + "urlRequired": "Введите URL", + "urlHint": "Контент будет автоматически извлечен со страницы. Убедитесь, что сайт разрешает доступ роботам.", + "beta": "Бета-версия" + }, + "retrieval": { + "title": "Поиск и проверка", + "subtitle": "Проверьте качество поиска (Dense & Sparse) по вашей базе знаний", + "query": "Тестовый запрос", + "queryPlaceholder": "Что вы хотите найти?", + "search": "Найти", + "searching": "Ищем...", + "results": "Результаты поиска", + "noResults": "Релевантный контент не найден", + "tryDifferentQuery": "Попробуйте изменить формулировку запроса", + "settings": "Параметры поиска", + "topK": "Количество результатов", + "topKHint": "Сколько фрагментов возвращать", + "enableRerank": "Включить Rerank", + "enableRerankHint": "Применить переранжирование для повышения точности", + "score": "Вес (Score)", + "document": "Документ", + "chunk": "Фрагмент #{index}", + "content": "Текст", + "charCount": "{count} симв.", + "searchSuccess": "Поиск завершен, найдено: {count}", + "searchFailed": "Ошибка выполнения поиска", + "queryRequired": "Введите поисковый запрос" + }, + "settings": { + "title": "Общие настройки базы", + "basic": "Основные", + "retrieval": "Поиск", + "chunkSize": "Размер чанка", + "chunkOverlap": "Перекрытие", + "topKDense": "Вернуть (Dense)", + "topKSparse": "Вернуть (Sparse)", + "topMFinal": "Итоговый результат", + "enableRerank": "Включить Rerank", + "embeddingProvider": "Провайдер Embedding", + "rerankProvider": "Провайдер Rerank", + "save": "Сохранить", + "saveSuccess": "Настройки сохранены", + "saveFailed": "Ошибка сохранения", + "tips": "Внимание! Изменение этих параметров повлияет на будущую выдачу базы знаний." + } +} \ No newline at end of file diff --git a/dashboard/src/i18n/locales/ru-RU/features/knowledge-base/document.json b/dashboard/src/i18n/locales/ru-RU/features/knowledge-base/document.json new file mode 100644 index 000000000..7fcb30ee9 --- /dev/null +++ b/dashboard/src/i18n/locales/ru-RU/features/knowledge-base/document.json @@ -0,0 +1,55 @@ +{ + "title": "Просмотр документа", + "backToKB": "К базе знаний", + "info": { + "title": "Информация о документе", + "name": "Имя файла", + "type": "Формат", + "size": "Размер", + "chunkCount": "Количество фрагментов", + "createdAt": "Загружен" + }, + "chunks": { + "title": "Фрагменты текста", + "empty": "Фрагменты не найдены", + "index": "Индекс", + "content": "Текст", + "charCount": "Символов", + "actions": "Действия", + "view": "Детали", + "edit": "Изменить", + "delete": "Удалить", + "preview": "Обзор", + "search": "Поиск по документу", + "searchPlaceholder": "Найти во фрагментах...", + "showing": "Показано", + "deleteConfirm": "Удалить этот фрагмент?", + "deleteSuccess": "Фрагмент удален", + "deleteFailed": "Ошибка удаления" + }, + "edit": { + "title": "Редактирование фрагмента", + "content": "Текст", + "cancel": "Отмена", + "save": "Сохранить", + "saveSuccess": "Фрагмент обновлен", + "saveFailed": "Ошибка сохранения" + }, + "delete": { + "title": "Удаление", + "confirmText": "Вы уверены?", + "warning": "Удаление фрагмента может ухудшить качество ответов AI по этой теме.", + "cancel": "Отмена", + "confirm": "Удалить", + "deleteSuccess": "Удаление выполнено", + "deleteFailed": "Ошибка удаления" + }, + "view": { + "title": "Детальный просмотр", + "index": "Индекс", + "content": "Текст", + "charCount": "Символов", + "vecDocId": "ID вектора", + "close": "Закрыть" + } +} \ No newline at end of file diff --git a/dashboard/src/i18n/locales/ru-RU/features/knowledge-base/index.json b/dashboard/src/i18n/locales/ru-RU/features/knowledge-base/index.json new file mode 100644 index 000000000..17fef3cc1 --- /dev/null +++ b/dashboard/src/i18n/locales/ru-RU/features/knowledge-base/index.json @@ -0,0 +1,67 @@ +{ + "title": "Управление базами знаний", + "subtitle": "Централизованное управление всеми знаниями AstrBot", + "list": { + "title": "Мои базы знаний", + "subtitle": "Все доступные коллекции знаний", + "create": "Создать базу", + "refresh": "Обновить", + "empty": "Баз знаний пока нет", + "loading": "Загрузка...", + "documents": "док.", + "chunks": "фрагм.", + "sessionConfig": "Профиль" + }, + "card": { + "edit": "Изменить", + "delete": "Удалить", + "open": "Открыть", + "docCount": "Документов: {count}", + "chunkCount": "Фрагментов: {count}" + }, + "create": { + "title": "Создание базы знаний", + "nameLabel": "Название", + "namePlaceholder": "Придумайте имя для базы", + "descriptionLabel": "Описание", + "descriptionPlaceholder": "Для чего нужна эта база?", + "emojiLabel": "Иконка", + "embeddingModelLabel": "Embedding модель", + "rerankModelLabel": "Rerank модель (опционально)", + "providerInfo": "Провайдер: {id} | Размерность: {dimensions}", + "rerankProviderInfo": "Провайдер: {id}", + "cancel": "Отмена", + "submit": "Создать", + "nameRequired": "Введите название базы знаний" + }, + "edit": { + "title": "Редактирование", + "submit": "Сохранить" + }, + "delete": { + "title": "Удаление", + "confirmText": "Вы уверены, что хотите удалить базу знаний «{name}»?", + "warning": "Это действие необратимо. Все документы, фрагменты и настройки будут навсегда удалены.", + "cancel": "Отмена", + "confirm": "Удалить" + }, + "emoji": { + "title": "Выберите иконку", + "close": "Закрыть", + "categories": { + "books": "Книги и документы", + "emotions": "Эмоции", + "objects": "Вещи", + "symbols": "Символы" + } + }, + "messages": { + "createSuccess": "База знаний создана", + "createFailed": "Ошибка создания", + "updateSuccess": "Обновлено успешно", + "updateFailed": "Ошибка обновления", + "deleteSuccess": "Удалено успешно", + "deleteFailed": "Ошибка удаления", + "loadError": "Не удалось загрузить список" + } +} \ No newline at end of file diff --git a/dashboard/src/i18n/locales/ru-RU/features/migration.json b/dashboard/src/i18n/locales/ru-RU/features/migration.json new file mode 100644 index 000000000..4d41dfce8 --- /dev/null +++ b/dashboard/src/i18n/locales/ru-RU/features/migration.json @@ -0,0 +1,18 @@ +{ + "dialog": { + "title": "Помощник по миграции", + "warning": "👋 Добро пожаловать в v4.0.0! В этой версии мы оптимизировали формат хранения данных. Обнаружена необходимость миграции базы данных.", + "loading": "Загрузка списка платформ...", + "loadError": "Ошибка загрузки, попробуйте еще раз", + "noPlatforms": "Конфигурации платформ не найдены", + "retry": "Повторить", + "startMigration": "Начать миграцию", + "migrating": "Выполняется миграция...", + "migratingSubtitle": "Пожалуйста, подождите. Не закрывайте это окно до завершения процесса.", + "migrationError": "Ошибка миграции", + "success": "Миграция успешно завершена!", + "completed": "Миграция выполнена", + "restartRecommended": "Рекомендуется перезапустить приложение, чтобы все изменения вступили в силу.", + "restartNow": "Перезапустить сейчас" + } +} \ No newline at end of file diff --git a/dashboard/src/i18n/locales/ru-RU/features/persona.json b/dashboard/src/i18n/locales/ru-RU/features/persona.json new file mode 100644 index 000000000..e6e58ad7f --- /dev/null +++ b/dashboard/src/i18n/locales/ru-RU/features/persona.json @@ -0,0 +1,146 @@ +{ + "page": { + "description": "Управление настройками и поведением персонажей" + }, + "buttons": { + "create": "Создать персонажа", + "createFirst": "Создать первого персонажа", + "edit": "Изменить", + "delete": "Удалить", + "cancel": "Отмена", + "save": "Сохранить", + "move": "Переместить", + "addDialogPair": "Добавить пример диалога" + }, + "labels": { + "presetDialogs": "Примеры диалогов ({count})", + "createdAt": "Создан", + "updatedAt": "Обновлен" + }, + "form": { + "personaId": "ID персонажа", + "systemPrompt": "Системный промпт", + "customErrorMessage": "Свое сообщение об ошибке (опционально)", + "customErrorMessageHelp": "Это сообщение будет отправлено пользователю при сбое запроса к LLM. Если оставить пустым, будет использовано системное сообщение по умолчанию.", + "presetDialogs": "Примеры диалогов", + "presetDialogsHelp": "Добавьте примеры взаимодействия, чтобы помочь AI лучше понять свою роль и стиль общения.", + "userMessage": "Сообщение пользователя", + "assistantMessage": "Ответ AI", + "tools": "Инструменты / MCP серверы", + "toolsHelp": "Выберите инструменты, доступные этому персонажу. Инструменты позволяют AI взаимодействовать с внешним миром: искать в интернете, выполнять расчеты и т.д.", + "toolsSelection": "Выбор инструментов", + "selectAllTools": "Выбрать все", + "clearAllTools": "Очистить всё", + "allSelected": "Выбрано всё", + "mcpServersQuickSelect": "Быстрый выбор MCP серверов", + "searchTools": "Поиск инструментов", + "selectedTools": "Выбранные инструменты", + "noToolsAvailable": "Нет доступных инструментов", + "noToolsFound": "Инструменты не найдены", + "loadingTools": "Загрузка инструментов...", + "allToolsAvailable": "Использовать все доступные инструменты", + "noToolsSelected": "Инструменты не выбраны", + "skills": "Навыки (Skills)", + "skillsHelp": "Выберите навыки, доступные этому персонажу. Навыки предоставляют AI готовые сценарии и правила работы.", + "skillsAllAvailable": "По умолчанию использовать все навыки", + "skillsSelectSpecific": "Выбрать определенные навыки", + "searchSkills": "Поиск навыков", + "selectedSkills": "Выбранные навыки", + "noSkillsAvailable": "Нет доступных навыков", + "noSkillsFound": "Навыки не найдены", + "loadingSkills": "Загрузка навыков...", + "allSkillsAvailable": "Использовать все доступные навыки", + "noSkillsSelected": "Навыки не выбраны", + "skillsRuntimeNoneWarning": "Среда выполнения Computer Use не задана. Навыки могут не работать, так как нет активного окружения.", + "createInFolder": "Будет создан в папке «{folder}»", + "rootFolder": "Все персонажи" + }, + "dialog": { + "create": { + "title": "Создание персонажа" + }, + "edit": { + "title": "Редактирование персонажа" + } + }, + "empty": { + "title": "Персонажи не настроены", + "description": "Самое время создать одного!", + "folderEmpty": "Папка пуста", + "folderEmptyDescription": "Создайте нового персонажа или папку, чтобы начать" + }, + "validation": { + "required": "Это поле обязательно для заполнения", + "minLength": "Минимум {min} символов", + "alphanumeric": "Разрешены только латинские буквы, цифры, подчёркивания и дефисы", + "dialogRequired": "{type} не может быть пустым", + "personaIdExists": "Персонаж с таким ID уже существует" + }, + "messages": { + "loadError": "Не удалось загрузить список персонажей", + "saveSuccess": "Сохранено", + "saveError": "Ошибка сохранения", + "deleteConfirm": "Вы уверены, что хотите удалить персонажа «{id}»? Это действие необратимо.", + "deleteSuccess": "Удалено", + "deleteError": "Ошибка удаления" + }, + "persona": { + "personasTitle": "Персонаж", + "toolsCount": "инстр.", + "skillsCount": "навыков", + "contextMenu": { + "moveTo": "Переместить в..." + }, + "messages": { + "moveSuccess": "Персонаж перемещен", + "moveError": "Не удалось переместить персонажа" + } + }, + "folder": { + "sidebarTitle": "Папки", + "rootFolder": "Корень", + "foldersTitle": "Папки", + "noFolders": "Папок нет", + "createButton": "Новая папка", + "searchPlaceholder": "Поиск папок...", + "form": { + "name": "Имя папки", + "description": "Описание (опционально)" + }, + "validation": { + "nameRequired": "Имя папки не может быть пустым" + }, + "contextMenu": { + "open": "Открыть", + "rename": "Переименовать", + "moveTo": "Переместить в...", + "delete": "Удалить" + }, + "createDialog": { + "title": "Создать папку", + "createButton": "Создать" + }, + "renameDialog": { + "title": "Переименовать папку" + }, + "deleteDialog": { + "title": "Удаление папки", + "message": "Вы уверены, что хотите удалить папку «{name}»?", + "warning": "Все персонажи из этой папки будут перемещены в корневой каталог." + }, + "messages": { + "createSuccess": "Папка создана", + "createError": "Ошибка создания папки", + "renameSuccess": "Папка переименована", + "renameError": "Ошибка переименования папки", + "deleteSuccess": "Папка удалена", + "deleteError": "Ошибка удаления папки" + } + }, + "moveDialog": { + "title": "Перемещение", + "description": "Выберите папку для «{name}»", + "success": "Объект перемещен", + "error": "Ошибка перемещения" + } +} \ No newline at end of file diff --git a/dashboard/src/i18n/locales/ru-RU/features/platform.json b/dashboard/src/i18n/locales/ru-RU/features/platform.json new file mode 100644 index 000000000..04c7613a9 --- /dev/null +++ b/dashboard/src/i18n/locales/ru-RU/features/platform.json @@ -0,0 +1,135 @@ +{ + "title": "Боты", + "subtitle": "Управление адаптерами платформ для подключения к мессенджерам", + "adapters": "Адаптеры платформ", + "addAdapter": "Создать бота", + "emptyText": "Боты не настроены. Нажмите «Создать бота», чтобы начать.", + "viewWebhook": "Показать Webhook", + "webhookCopied": "URL скопирован в буфер обмена", + "webhookCopyFailed": "Не удалось скопировать, сделайте это вручную", + "webhookDialog": { + "title": "Адрес Webhook", + "description": "Используйте этот адрес для обратных вызовов. Убедитесь, что ваш AstrBot доступен из интернета. Рекомендуется указать «Внешний URL для Webhook» в Конфигурация -> Система.", + "close": "Закрыть" + }, + "details": { + "adapterType": "Тип адаптера", + "token": "Токен", + "description": "Описание" + }, + "logs": { + "title": "Логи платформы", + "expand": "Развернуть", + "collapse": "Свернуть" + }, + "dialog": { + "add": "Добавить", + "edit": "Изменить", + "adapter": "Бот", + "refresh": "Обновить", + "cancel": "Отмена", + "save": "Сохранить", + "addPlatform": "Создать бота", + "connectTitle": "Подключение к {name}", + "viewTutorial": "Открыть руководство", + "noTemplates": "Шаблоны не найдены", + "idConflict": { + "title": "Конфликт ID", + "message": "Бот с ID «{id}» уже существует. Пожалуйста, используйте уникальный ID.", + "confirm": "Понятно" + }, + "securityWarning": { + "title": "Безопасность", + "aiocqhttpTokenMissing": "Для защиты соединения крайне рекомендуется установить ws_reverse_token. Работа без токена небезопасна.", + "learnMore": "Подробнее" + }, + "invalidPlatformId": "ID платформы не может содержать символы ':' или '!'." + }, + "createDialog": { + "step1Title": "Выберите мессенджер", + "step1Hint": "Куда вы хотите подключить бота? (QQ, Telegram, Discord, WeChat и др.)", + "platformTypeLabel": "Платформа", + "configFileTitle": "Файл конфигурации", + "optional": "опционально", + "configHint": "Как настроить бота? Конфиг содержит модель, персонажа, базу знаний и набор плагинов.", + "configDefaultHint": "По умолчанию используется профиль «default». Вы сможете изменить его позже.", + "useExistingConfig": "Использовать существующий конфиг", + "selectConfigLabel": "Выберите профиль", + "createNewConfig": "Создать новый профиль", + "newConfigNameLabel": "Имя нового профиля", + "newConfigTitle": "Создание нового профиля", + "newConfigLoadFailed": "Не удалось загрузить шаблон конфигурации", + "addRouteRule": "Добавить правило маршрутизации", + "viewMode": "Просмотр", + "editMode": "Редактирование", + "noRouteRules": "Правила маршрутизации не заданы, будет использоваться профиль по умолчанию", + "sessionIdPlaceholder": "ID сессии или *", + "allSessions": "Все сессии", + "configMissing": "Файл конфигурации не найден", + "routeHint": "* При получении сообщения AstrBot ищет первое совпадение в списке сверху вниз. Используйте слэш-команду /sid, чтобы узнать ID текущей сессии. Если совпадений нет, используется профиль по умолчанию.", + "warningContinue": "Игнорировать и создать", + "warningEditAgain": "Вернуться к редактированию", + "configDrawerTitle": "Управление профилями", + "configDrawerIdLabel": "ID", + "configTableHeaders": { + "configId": "ID связанного профиля", + "scope": "Область применения" + }, + "routeTableHeaders": { + "source": "Источник (тип:ID)", + "config": "Файл конфига", + "actions": "Действия" + }, + "messageTypeOptions": { + "all": "Все сообщения", + "group": "Групповые (GroupMessage)", + "friend": "Личные (FriendMessage)" + }, + "messageTypeLabels": { + "all": "Все", + "group": "Группа", + "friend": "ЛС" + } + }, + "messages": { + "updateSuccess": "Обновлено!", + "addSuccess": "Добавлено!", + "deleteSuccess": "Удалено!", + "statusUpdateSuccess": "Статус обновлен!", + "deleteConfirm": "Вы уверены, что хотите удалить этого бота?", + "configNotFoundOpenConfig": "Целевой конфиг не найден. Открыта страница настроек для проверки.", + "updateMissingPlatformId": "Ошибка обновления: отсутствует ID платформы.", + "platformUpdateFailed": "Не удалось обновить платформу.", + "addSuccessWithConfig": "Бот успешно добавлен, профиль обновлен", + "configIdMissing": "Не удалось получить ID конфигурации.", + "routingUpdateFailed": "Ошибка обновления маршрутов: {message}", + "createConfigFailed": "Ошибка создания профиля: {message}", + "platformIdMissing": "Не удалось получить ID платформы.", + "routingSaveFailed": "Ошибка сохранения маршрутов: {message}" + }, + "status": { + "enabled": "Включен", + "disabled": "Выключен", + "connecting": "Подключение", + "connected": "Подключен", + "disconnected": "Отключен", + "error": "Ошибка" + }, + "runtimeStatus": { + "running": "Работает", + "error": "Ошибка", + "pending": "Ожидание", + "stopped": "Остановлен", + "unknown": "Неизвестно", + "errors": "ошибок" + }, + "errorDialog": { + "title": "Детали ошибки", + "platformId": "ID платформы", + "errorCount": "Кол-во ошибок", + "lastError": "Последняя ошибка", + "occurredAt": "Время", + "traceback": "Стек вызовов", + "close": "Закрыть" + } +} \ No newline at end of file diff --git a/dashboard/src/i18n/locales/ru-RU/features/provider.json b/dashboard/src/i18n/locales/ru-RU/features/provider.json new file mode 100644 index 000000000..1287bfbd4 --- /dev/null +++ b/dashboard/src/i18n/locales/ru-RU/features/provider.json @@ -0,0 +1,151 @@ +{ + "title": "Провайдеры моделей", + "subtitle": "Настройка AI моделей для диалогов. Также поддерживает Dify, Coze, а также внешние Agent-сервисы.", + "providers": { + "title": "Сервис-провайдеры", + "settings": "Настройки", + "addProvider": "Добавить провайдера", + "providerType": "Тип провайдера", + "tabs": { + "all": "Все", + "chatCompletion": "Диалоги", + "agentRunner": "Агенты", + "speechToText": "STT (Речь -> Текст)", + "textToSpeech": "TTS (Текст -> Речь)", + "embedding": "Эмбеддинги", + "rerank": "Rerank (Ранжирование)" + }, + "empty": { + "all": "Провайдеры не добавлены. Нажмите «Добавить провайдера», чтобы начать.", + "typed": "Провайдеры типа «{type}» не найдены." + }, + "description": { + "openai": "Поддерживаются все провайдеры, совместимые с OpenAI API.", + "vllm_rerank": "Также поддерживает Jina AI, Cohere, PPIO и другие.", + "default": "Преобразование речи в текст" + } + }, + "availability": { + "title": "Доступность провайдеров", + "subtitle": "Статус определяется путем выполнения тестового запроса. Может взиматься плата согласно тарифу API.", + "refresh": "Проверить статус", + "noData": "Нажмите «Проверить статус», чтобы узнать доступность моделей", + "available": "Доступен", + "unavailable": "Недоступен", + "pending": "Проверка...", + "errorMessage": "Ошибка", + "test": "Тест" + }, + "logs": { + "title": "Логи сервиса", + "expand": "Развернуть", + "collapse": "Свернуть" + }, + "dialogs": { + "addProvider": { + "title": "Новый провайдер", + "tabs": { + "basic": "Диалоги", + "agentRunner": "Агенты", + "speechToText": "Преобразование текста в речь", + "textToSpeech": "Переранжирование", + "embedding": "Эмбеддинги", + "rerank": "API Key" + }, + "noTemplates": "Шаблоны для этого типа не найдены" + }, + "config": { + "addTitle": "Добавить", + "editTitle": "Изменить", + "provider": "Провайдер", + "cancel": "Отмена", + "save": "Сохранить" + }, + "settings": { + "title": "Общие настройки провайдеров", + "sessionSeparation": { + "title": "Изоляция провайдеров по сессиям", + "description": "Позволяет выбирать независимых провайдеров для генерации текста, TTS и STT в каждой конкретной сессии." + }, + "close": "Закрыть" + } + }, + "messages": { + "success": { + "update": "Обновлено!", + "add": "Добавлено!", + "delete": "Удалено!", + "statusUpdate": "Статус обновлен!", + "sessionSeparation": "Настройки изоляции сохранены" + }, + "error": { + "sessionSeparation": "Не удалось загрузить настройки изоляции", + "fetchStatus": "Не удалось получить статус провайдеров", + "testError": "Тест {id} провален: {error}" + }, + "confirm": { + "delete": "Вы уверены, что хотите удалить провайдера «{id}»?" + } + }, + "providerTypes": { + "title": "Тип провайдера" + }, + "providerSources": { + "title": "Источник провайдера", + "add": "Добавить", + "empty": "Источники не найдены", + "selectHint": "Пожалуйста, выберите источник провайдера", + "selectCreated": "Выбрать существующий источник", + "save": "Сохранить конфиг", + "saveAndFetchModels": "Сохранить и загрузить модели", + "fetchModels": "Загрузить список моделей", + "saveSuccess": "Источник успешно сохранен", + "saveError": "Ошибка сохранения источника", + "deleteConfirm": "Вы уверены, что хотите удалить источник «{id}»? Все связанные конфигурации моделей будут удалены.", + "deleteSuccess": "Источник удален", + "deleteError": "Ошибка удаления", + "enabled": "Включен", + "disabled": "Выключен", + "advancedConfig": "Расширенные настройки...", + "fields": { + "name": "Имя", + "apiKey": "Base URL", + "baseUrl": "Base URL" + }, + "hints": { + "id": "Уникальный ID источника", + "key": "Ваш серетный API-ключ", + "apiBase": "Адрес API точки входа (Endpoint URL)", + "proxy": "Прокси сервер (HTTP/HTTPS), напр. http://127.0.0.1:7890. Используется только для запросов к этому провайдеру." + }, + "labels": { + "proxy": "Прокси" + } + }, + "models": { + "available": "Доступные модели", + "configured": "Настроенные модели", + "empty": "Модели не настроены. Нажмите «Загрузить список моделей» выше.", + "noModelsFound": "Модели не найдены", + "fetchError": "Не удалось получить список моделей", + "addSuccess": "Модель {model} успешно добавлена", + "deleteConfirm": "Вы уверены, что хотите удалить модель «{id}»?", + "deleteSuccess": "Модель удалена", + "deleteError": "Ошибка удаления модели", + "testSuccess": "Тест модели «{id}» пройден успешно", + "testError": "Тест модели провален", + "searchPlaceholder": "Поиск по имени или ID", + "manualAddButton": "Добавить вручную", + "manualDialogTitle": "Произвольная модель", + "manualDialogModelLabel": "Код модели (напр. gpt-4o-mini)", + "manualDialogPreviewLabel": "Отображаемый ID (авто)", + "manualDialogPreviewHint": "Будет выглядеть как: SourceID/ModelID", + "manualModelRequired": "Укажите ID модели", + "manualModelExists": "Эта модель уже добавлена", + "configure": "Настроить", + "tooltips": { + "providerId": "ID провайдера", + "modelId": "ID модели" + } + } +} \ No newline at end of file diff --git a/dashboard/src/i18n/locales/ru-RU/features/session-management.json b/dashboard/src/i18n/locales/ru-RU/features/session-management.json new file mode 100644 index 000000000..19c3a9aa5 --- /dev/null +++ b/dashboard/src/i18n/locales/ru-RU/features/session-management.json @@ -0,0 +1,130 @@ +{ + "title": "Управление сессиями", + "subtitle": "Настройка индивидуальных правил для конкретных диалогов. Эти правила имеют приоритет над глобальной конфигурацией.", + "buttons": { + "refresh": "Обновить", + "edit": "Изменить", + "editRule": "Редактировать правило", + "deleteAllRules": "Удалить все правила", + "addRule": "Добавить правило", + "save": "Сохранить", + "cancel": "Отмена", + "delete": "Удалить", + "clear": "Очистить", + "next": "Далее", + "editCustomName": "Изменить заметку", + "batchDelete": "Массовое удаление" + }, + "customRules": { + "title": "Пользовательские правила", + "rulesCount": "правил", + "hasRules": "Настроено", + "noRules": "Индивидуальных правил нет", + "noRulesDesc": "Нажмите «Добавить правило», чтобы задать настройки для конкретного диалога", + "serviceConfig": "Сервис", + "pluginConfig": "Плагины", + "kbConfig": "База знаний", + "providerConfig": "Модель", + "configured": "Настроено", + "noCustomName": "Без заметки" + }, + "quickEditName": { + "title": "Редактирование заметки" + }, + "search": { + "placeholder": "Поиск сессии..." + }, + "table": { + "headers": { + "umoInfo": "Источник (UMO)", + "rulesOverview": "Обзор правил", + "actions": "Действия" + } + }, + "persona": { + "none": "Из конфигурации" + }, + "provider": { + "followConfig": "Из конфигурации" + }, + "addRule": { + "title": "Добавление правила", + "description": "Выберите источник сообщения (UMO) для настройки. Индивидуальные правила приоритетнее глобальных. Используйте команду /sid в чате, чтобы узнать информацию об источнике.", + "selectUmo": "Выберите сессию", + "noUmos": "Нет доступных сессий" + }, + "ruleEditor": { + "title": "Редактор правил", + "description": "Настройте поведение для этой сессии. Настройки ниже перекроют глобальный конфиг.", + "serviceConfig": { + "title": "Сервисные настройки", + "sessionEnabled": "Обрабатывать сообщения", + "llmEnabled": "Использовать LLM", + "ttsEnabled": "Использовать TTS", + "customName": "Заметка для сессии" + }, + "providerConfig": { + "title": "Выбор моделей", + "chatProvider": "Чат-модель", + "sttProvider": "STT (Распознавание)", + "ttsProvider": "TTS (Озвучка)" + }, + "personaConfig": { + "title": "Персона", + "selectPersona": "Выберите Persona", + "hint": "При выборе Persona все диалоги из этого источника будут использовать именно её." + }, + "pluginConfig": { + "title": "Плагины", + "disabledPlugins": "Отключенные плагины", + "hint": "Выберите плагины, которые нужно ОТКЛЮЧИТЬ в этой сессии. Остальные останутся активными." + }, + "kbConfig": { + "title": "База знаний", + "selectKbs": "Выбор баз знаний", + "topK": "Количество результатов (Top K)", + "enableRerank": "Использовать Rerank" + } + }, + "deleteConfirm": { + "title": "Подтверждение", + "message": "Удалить все настройки для этой сессии? Будут применены глобальные настройки." + }, + "batchDeleteConfirm": { + "title": "Массовое удаление", + "message": "Удалить {count} выбранных правил? Будут применены глобальные настройки." + }, + "batchOperations": { + "title": "Массовые операции", + "hint": "Быстрое изменение настроек для группы сессий", + "scope": "Область применения", + "scopeSelected": "Выбранные", + "scopeAll": "Все сессии", + "scopeGroup": "Все группы", + "scopePrivate": "Личные диалоги", + "llmStatus": "Статус LLM", + "ttsStatus": "Статус TTS", + "chatProvider": "Чат-модель", + "ttsProvider": "TTS-модель", + "apply": "Применить" + }, + "status": { + "enabled": "Включено", + "disabled": "Выключено" + }, + "messages": { + "refreshSuccess": "Данные обновлены", + "loadError": "Ошибка загрузки", + "saveSuccess": "Настройки сохранены", + "saveError": "Ошибка сохранения", + "clearSuccess": "Очищено", + "clearError": "Ошибка очистки", + "deleteSuccess": "Удалено", + "deleteError": "Ошибка удаления", + "noChanges": "Изменений не обнаружено", + "batchDeleteSuccess": "Массовое удаление выполнено", + "batchDeleteError": "Ошибка массового удаления", + "batchUpdateError": "Ошибка пакетного обновления", + "batchUpdateSuccess": "Пакетное обновление успешно выполнено" + } +} \ No newline at end of file diff --git a/dashboard/src/i18n/locales/ru-RU/features/settings.json b/dashboard/src/i18n/locales/ru-RU/features/settings.json new file mode 100644 index 000000000..29b826fff --- /dev/null +++ b/dashboard/src/i18n/locales/ru-RU/features/settings.json @@ -0,0 +1,180 @@ +{ + "network": { + "title": "Сеть", + "githubProxy": { + "title": "Зеркало GitHub", + "subtitle": "Адрес для ускорения загрузки плагинов и обновлений AstrBot. Особенно актуально для пользователей из Китая. Все адреса предоставляются как есть, если обновление не удается — проверьте доступность выбранного зеркала.", + "label": "Выбрать ускоритель GitHub" + }, + "proxySelector": { + "title": "Ускорение GitHub", + "noProxy": "Не использовать", + "useProxy": "Включить", + "testConnection": "Проверить соединение", + "available": "Доступен", + "unavailable": "Недоступен", + "custom": "Свой вариант" + } + }, + "theme": { + "title": "Тема оформления", + "subtitle": "Настройка основных и дополнительных цветов. Изменения вступают в силу немедленно и сохраняются в браузере.", + "customize": { + "title": "Цвета темы", + "primary": "Основной", + "secondary": "Дополнительный", + "reset": "Сбросить" + } + }, + "system": { + "title": "Система", + "restart": { + "title": "Перезапуск", + "subtitle": "Выполнить мягкий перезапуск AstrBot", + "button": "Перезагрузить" + }, + "migration": { + "title": "Миграция данных в v4.0.0", + "subtitle": "Если у вас возникли проблемы с совместимостью данных после обновления, запустите помощник вручную.", + "button": "Запустить миграцию" + }, + "backup": { + "title": "Резервное копирование", + "subtitle": "Важнейший инструмент для безопасного переноса данных между серверами.", + "button": "Управление бэкапами" + } + }, + "sidebar": { + "title": "Боковая панель", + "customize": { + "title": "Настройка меню", + "subtitle": "Перетаскивайте элементы, чтобы изменить их порядок или скрыть в группе «Дополнительно». Настройки сохраняются локально в браузере.", + "reset": "Сбросить порядок", + "mainItems": "Основные разделы", + "moreItems": "Дополнительно" + } + }, + "backup": { + "dialog": { + "title": "Резервное копирование" + }, + "tabs": { + "export": "Экспорт", + "import": "Импорт", + "list": "Список копий" + }, + "export": { + "title": "Создать резервную копию", + "description": "Экспорт всех данных в ZIP-архив, включая базы данных, базу знаний, конфигурации и вложения.", + "includes": "Включает: основную БД, векторные индексы знаний, файлы конфигурации, медиа-вложения.", + "button": "Начать экспорт", + "processing": "Экспорт...", + "wait": "Пожалуйста, подождите, мы упаковываем данные...", + "completed": "Готово!", + "download": "Скачать архив", + "another": "Создать новый", + "failed": "Ошибка экспорта", + "retry": "Повторить" + }, + "import": { + "title": "Восстановление из копии", + "warning": "⚠️ Внимание! Импорт полностью удалит и перезапишет текущие данные! Убедитесь, что у вас есть копия текущего состояния.", + "selectFile": "Выберите ZIP-архив", + "uploadAndCheck": "Загрузить и проверить", + "uploading": "Загрузка...", + "uploadWait": "Файл передается на сервер...", + "uploadInit": "Инициализация...", + "uploadingChunks": "Передача фрагментов...", + "uploadComplete": "Загружено, идет сборка...", + "checking": "Проверка структуры...", + "invalidBackup": "Некорректный файл резервной копии", + "backupContents": "Состав архива", + "tables": "таблиц БД", + "knowledgeBases": "баз знаний", + "configFiles": "конфигов", + "confirmImport": "Подтвердите импорт", + "button": "Начать восстановление", + "processing": "Восстановление...", + "wait": "Идет процесс развертывания данных...", + "completed": "Восстановление успешно завершено!", + "restartRequired": "Данные восстановлены. Необходимо немедленно перезапустить AstrBot для вступления изменений в силу.", + "restartNow": "Перезапустить сейчас", + "failed": "Ошибка импорта", + "retry": "Повторить", + "version": { + "backupVersion": "Версия бэкапа", + "currentVersion": "Текущая версия", + "backupTime": "Дата создания", + "matchTitle": "✅ Версии совпадают", + "matchMessage": "Импорт перезапишет все текущие данные, включая:\n• Основную БД (чаты, настройки)\n• Базы знаний\n• Плагины и их данные\n• Файлы конфигурации\n\nЭто действие необратимо! Продолжить?", + "minorDiffTitle": "⚠️ Разница в минорной версии", + "minorDiffMessage": "Разница в минорных версиях обычно допустима, но структура данных могла немного измениться. Все текущие данные будут удалены!\n\nПродолжить импорт?", + "majorDiffTitle": "⛔ Импорт невозможен", + "majorDiffMessage": "Версии основного выпуска различаются. Импорт между мажорными версиями может привести к фатальному повреждению данных.\nИспользуйте AstrBot той же основной версии." + } + }, + "list": { + "empty": "Резервные копии не найдены", + "refresh": "Обновить список", + "confirmDelete": "Вы уверены, что хотите безвозвратно удалить эту копию?", + "uploaded": "Загружено", + "restore": "Восстановить из этого файла", + "rename": "Переименовать", + "renameTitle": "Переименование файла", + "newName": "Новое имя", + "renameHint": "Разрешены буквы, цифры, точки, дефисы и подчеркивания", + "renameRequired": "Введите имя файла", + "renameInvalidChars": "Имя содержит недопустимые символы", + "renameFailed": "Ошибка переименования", + "ftpHint": "Для больших архивов вы можете загружать их напрямую в папку data/backups через FTP/SFTP." + } + }, + "apiKey": { + "title": "API Keys", + "manageTitle": "Ключи доступа разработчика", + "subtitle": "Управление токенами для доступа к открытому HTTP API AstrBot.", + "name": "Имя ключа", + "expiresInDays": "Срок действия", + "expiryOptions": { + "day1": "1 день", + "day7": "7 дней", + "day30": "30 дней", + "day90": "90 дней", + "permanent": "Бессрочно" + }, + "permanentWarning": "Бессрочные ключи менее безопасны. Пожалуйста, храните их в надежном месте.", + "scopes": "Область доступа (Scopes)", + "create": "Создать API Key", + "revoke": "Отозвать", + "delete": "Удалить", + "copy": "Копировать", + "docsLink": "Документация API", + "plaintextHint": "Обязательно сохраните ключ сейчас. После закрытия окна вы больше не сможете увидеть его значение.", + "empty": "Ключи не созданы", + "status": { + "active": "Активен", + "inactive": "Неактивен" + }, + "table": { + "name": "Имя", + "prefix": "Префикс", + "scopes": "Права", + "status": "Статус", + "lastUsed": "Использован", + "createdAt": "Создан", + "actions": "Действия" + }, + "messages": { + "loadFailed": "Не удалось загрузить ключи", + "scopeRequired": "Выберите хотя бы одну область доступа", + "createSuccess": "API Key создан", + "createFailed": "Ошибка создания ключа", + "revokeSuccess": "Ключ отозван", + "revokeFailed": "Ошибка отзыва ключа", + "deleteSuccess": "Ключ удален", + "deleteFailed": "Ошибка удаления ключа", + "copySuccess": "Ключ скопирован", + "copyFailed": "Ошибка копирования" + } + } +} \ No newline at end of file diff --git a/dashboard/src/i18n/locales/ru-RU/features/subagent.json b/dashboard/src/i18n/locales/ru-RU/features/subagent.json new file mode 100644 index 000000000..368bd2046 --- /dev/null +++ b/dashboard/src/i18n/locales/ru-RU/features/subagent.json @@ -0,0 +1,65 @@ +{ + "page": { + "title": "Оркестрация SubAgent", + "beta": "Экспериментально", + "subtitle": "Основной LLM может напрямую использовать свои инструменты или делегировать задачи SubAgent через handoff." + }, + "actions": { + "refresh": "Обновить", + "save": "Сохранить", + "add": "Добавить SubAgent", + "delete": "Удалить", + "close": "Закрыть" + }, + "switches": { + "enable": "Включить оркестрацию SubAgent", + "enableHint": "Включить функциональность под-агентов", + "dedupe": "Дедупликация инструментов основного LLM (скрывать инструменты, дублируемые SubAgent)", + "dedupeHint": "Удалить дублирующиеся инструменты из основного агента" + }, + "description": { + "disabled": "Выключено: SubAgent отключен; основной LLM подключает инструменты согласно правилам персонажа (все по умолчанию) и вызывает их напрямую.", + "enabled": "Включено: основной LLM сохраняет свои инструменты и подключает инструменты делегирования transfer_to_*. При дедупликации инструменты, пересекающиеся с SubAgent, удаляются из основного набора." + }, + "section": { + "title": "Субагенты", + "globalSettings": "Глобальные настройки" + }, + "cards": { + "statusEnabled": "Включено", + "statusDisabled": "Отключено", + "unnamed": "Безымянный SubAgent", + "transferPrefix": "передать_{name}", + "switchLabel": "Включить", + "previewTitle": "Предпросмотр: инструмент handoff, видимый основному LLM", + "personaChip": "Персонаж: {id}", + "personaPreview": "ПРЕДПРОСМОТР ПЕРСОНАЖА" + }, + "form": { + "nameLabel": "Имя агента (используется для transfer_to_{name})", + "nameHint": "Используйте строчные латинские буквы и подчеркивания; имя должно быть глобально уникальным.", + "providerLabel": "Chat Provider (опционально)", + "providerHint": "Оставьте пустым, чтобы использовать глобальный провайдер по умолчанию.", + "personaLabel": "Выберите персонажа", + "personaHint": "SubAgent наследует системные настройки и инструменты выбранного персонажа.", + "descriptionLabel": "Описание для основного LLM (используется для принятия решения о handoff)", + "descriptionHint": "Отображается как описание инструмента transfer_to_* — будьте кратки и ясны." + }, + "messages": { + "loadConfigFailed": "Не удалось загрузить конфигурацию", + "loadPersonaFailed": "Не удалось загрузить список персонажей", + "nameMissing": "У SubAgent отсутствует имя", + "nameInvalid": "Недопустимое имя SubAgent: только строчные латинские буквы/цифры/подчеркивания, должно начинаться с буквы", + "nameDuplicate": "Дублирующееся имя SubAgent: {name}", + "personaMissing": "У SubAgent {name} не выбран персонаж", + "saveSuccess": "Успешно сохранено", + "saveFailed": "Ошибка сохранения", + "nameRequired": "Имя обязательно", + "namePattern": "Только строчные буквы, цифры и подчеркивание" + }, + "empty": { + "title": "Агенты не настроены", + "subtitle": "Добавьте первого под-агента, чтобы начать", + "action": "Создать первого агента" + } +} \ No newline at end of file diff --git a/dashboard/src/i18n/locales/ru-RU/features/tool-use.json b/dashboard/src/i18n/locales/ru-RU/features/tool-use.json new file mode 100644 index 000000000..f37707e8f --- /dev/null +++ b/dashboard/src/i18n/locales/ru-RU/features/tool-use.json @@ -0,0 +1,195 @@ +{ + "title": "Инструменты и функции", + "subtitle": "Управление MCP-серверами и доступными функциями", + "tooltip": { + "info": "Что такое Function Calling и MCP?", + "marketplace": "Обзор и установка MCP-серверов от сообщества", + "serverConfig": "Конфигурация MCP-серверов (stdio) поддерживает следующие поля:\ncommand: имя команды (например, python или uv)\nargs: массив аргументов (например, [\"run\", \"server.py\"])\nenv: объект переменных окружения (например, {\"api_key\": \"abc\"})\ncwd: рабочий каталог (например, /path/to/server)\nencoding: кодировка вывода (по умолчанию utf-8)\nПодробности см. в документации MCP.\n⚠️ Если вы используете Docker, устанавливайте сервера в смонтированную директорию data." + }, + "tabs": { + "local": "Локальные сервера", + "marketplace": "Магазин MCP" + }, + "mcpServers": { + "title": "MCP Сервера", + "buttons": { + "refresh": "Обновить", + "add": "Добавить сервер", + "useTemplateStdio": "Шаблон Stdio", + "useTemplateStreamableHttp": "Шаблон Streamable HTTP", + "useTemplateSse": "Шаблон SSE", + "sync": "Синхронизировать" + }, + "empty": "MCP-сервера не найдены. Нажмите «Добавить сервер».", + "status": { + "noTools": "Нет доступных инструментов", + "availableTools": "Доступные инструменты", + "configSummary": "Конфигурация: {keys}", + "noConfig": "Конфигурация не задана" + } + }, + "functionTools": { + "title": "Функции (Tools)", + "buttons": { + "view": "Показать инструменты" + }, + "search": "Поиск по функциям", + "empty": "Доступные инструменты не найдены", + "description": "Описание функции", + "parameters": "Параметры", + "noParameters": "У этого инструмента нет параметров", + "table": { + "paramName": "Параметр", + "type": "Тип", + "description": "Описание", + "required": "Обяз.", + "origin": "Источник", + "originName": "Имя источника", + "actions": "Действия" + } + }, + "marketplace": { + "title": "Магазин MCP-серверов", + "search": "Поиск по магазину", + "buttons": { + "refresh": "Обновить", + "detail": "Инфо", + "import": "Импорт" + }, + "loading": "Загрузка списка серверов...", + "empty": "Доступных MCP-серверов не найдено", + "status": { + "availableTools": "Инструментов: {count}", + "noToolsInfo": "Нет данных об инструментах" + } + }, + "dialogs": { + "addServer": { + "title": "Добавление MCP-сервера", + "editTitle": "Редактирование MCP-сервера", + "fields": { + "name": "Название сервера", + "nameRequired": "Название обязательно", + "enable": "Включить сервер", + "config": "Конфигурация сервера" + }, + "errors": { + "configEmpty": "Конфигурация не может быть пустой", + "jsonFormat": "Ошибка формата JSON: {error}", + "jsonParse": "Ошибка разбора JSON: {error}" + }, + "buttons": { + "cancel": "Отмена", + "save": "Сохранить", + "testConnection": "Тест связи", + "sync": "Синхронизировать" + }, + "tips": { + "timeoutConfig": "Тайм-аут вызова инструментов настраивается отдельно на странице конфигурации" + } + }, + "serverDetail": { + "title": "Детали сервера", + "installConfig": "Конфигурация установки", + "availableTools": "Список инструментов", + "buttons": { + "close": "Закрыть", + "importConfig": "Импортировать конфиг" + } + }, + "confirmDelete": "Вы уверены, что хотите удалить сервер «{name}»?", + "syncProvider": { + "title": "Синхронизация MCP", + "subtitle": "Загрузка конфигурации MCP-серверов от провайдера", + "steps": { + "selectProvider": "Шаг 1: Провайдер", + "configureAuth": "Шаг 2: Авторизация", + "syncServers": "Шаг 3: Синхронизация" + }, + "providers": { + "modelscope": "ModelScope", + "description": "ModelScope — это сообщество моделей с открытым исходным кодом, предоставляющее различные MCP-сервера для AI-сервисов" + }, + "fields": { + "provider": "Выберите провайдера", + "accessToken": "Токен доступа", + "tokenRequired": "Токен обязателен", + "tokenHint": "Введите ваш токен доступа ModelScope" + }, + "buttons": { + "cancel": "Отмена", + "previous": "Назад", + "next": "Далее", + "sync": "Начать", + "getToken": "Получить токен" + }, + "status": { + "selectProvider": "Пожалуйста, выберите провайдера MCP-серверов", + "enterToken": "Введите токен для продолжения", + "readyToSync": "Готов к синхронизации" + }, + "messages": { + "syncSuccess": "MCP-сервера успешно синхронизированы!", + "syncError": "Ошибка синхронизации: {error}", + "tokenHelp": "Как получить токен ModelScope? Нажмите кнопку справа для инструкции" + } + } + }, + "messages": { + "getServersError": "Ошибка получения списка серверов: {error}", + "getToolsError": "Ошибка получения списка инструментов: {error}", + "saveSuccess": "Настройки сохранены!", + "saveError": "Ошибка сохранения: {error}", + "deleteSuccess": "Сервер удален успешно!", + "deleteError": "Ошибка удаления: {error}", + "updateSuccess": "Обновлено успешно!", + "updateError": "Ошибка обновления: {error}", + "getMarketError": "Не удалось загрузить магазин MCP: {error}", + "importError": { + "noConfig": "У этого сервера нет доступной конфигурации", + "invalidFormat": "Неверный формат конфигурации", + "failed": "Импорт не удался: {error}" + }, + "configParseError": "Ошибка разбора конфигурации: {error}", + "noAvailableConfig": "Конфигурация отсутствует", + "toggleToolSuccess": "Статус инструмента изменен!", + "toggleToolError": "Не удалось изменить статус: {error}", + "testError": "Ошибка теста связи: {error}" + }, + "syncProvider": { + "title": "Синхронизация серверов MCP", + "subtitle": "Синхронизировать конфигурации серверов MCP от провайдеров с локальными", + "steps": { + "selectProvider": "Шаг 1: Выберите провайдер", + "configureAuth": "Шаг 2: Настройте аутентификацию", + "syncServers": "Шаг 3: Синхронизируйте серверы" + }, + "providers": { + "modelscope": "ModelScope", + "description": "ModelScope — это сообщество открытых моделей, предоставляющее серверы MCP для различных сервисов машинного обучения и ИИ" + }, + "fields": { + "provider": "Выберите провайдер", + "accessToken": "Токен доступа", + "tokenRequired": "Требуется токен доступа", + "tokenHint": "Введите ваш токен доступа ModelScope" + }, + "buttons": { + "cancel": "Отмена", + "previous": "Назад", + "next": "Далее", + "sync": "Начать синхронизацию", + "getToken": "Получить токен" + }, + "status": { + "selectProvider": "Пожалуйста, выберите провайдер сервера MCP", + "enterToken": "Введите токен доступа для продолжения", + "readyToSync": "Готово к синхронизации конфигураций серверов" + }, + "messages": { + "syncSuccess": "Серверы MCP успешно синхронизированы!", + "syncError": "Ошибка синхронизации: {error}", + "tokenHelp": "Как получить токен доступа ModelScope? Нажмите кнопку справа для получения инструкций" + } + } +} \ No newline at end of file diff --git a/dashboard/src/i18n/locales/ru-RU/features/trace.json b/dashboard/src/i18n/locales/ru-RU/features/trace.json new file mode 100644 index 000000000..484985bab --- /dev/null +++ b/dashboard/src/i18n/locales/ru-RU/features/trace.json @@ -0,0 +1,10 @@ +{ + "title": "Трассировка (Trace)", + "autoScroll": { + "enabled": "Автопрокрутка: ВКЛ", + "disabled": "Автопрокрутка: ВЫКЛ" + }, + "hint": "В данный момент записываются только вызовы моделей основного агента AstrBot. Система будет совершенствоваться.", + "recording": "Запись...", + "paused": "Пауза" +} \ No newline at end of file diff --git a/dashboard/src/i18n/locales/ru-RU/features/welcome.json b/dashboard/src/i18n/locales/ru-RU/features/welcome.json new file mode 100644 index 000000000..ea7d3043a --- /dev/null +++ b/dashboard/src/i18n/locales/ru-RU/features/welcome.json @@ -0,0 +1,37 @@ +{ + "greeting": { + "morning": "Доброе утро, добро пожаловать в AstrBot", + "afternoon": "Добрый день, добро пожаловать в AstrBot", + "evening": "Добрый вечер, добро пожаловать в AstrBot", + "newYear": "С Новым Годом!" + }, + "subtitle": "Сначала пройдите базовое руководство. Настройку платформ и провайдеров моделей можно завершить позже.", + "announcement": { + "title": "Объявление" + }, + "onboard": { + "title": "Быстрый старт", + "subtitle": "Вы можете выполнить первичную настройку прямо здесь.", + "step1Title": "Настройка платформ", + "step1Desc": "Подключите AstrBot к QQ, Lark, WeChat, Telegram и другим мессенджерам.", + "step2Title": "Настройка AI моделей", + "step2Desc": "Выберите и настройте AI провайдеров для AstrBot.", + "configure": "Настроить", + "skip": "Пропустить", + "pending": "Ожидает", + "completed": "Готово", + "skipped": "Пропущено", + "platformLoadFailed": "Ошибка загрузки конфигурации платформ", + "providerLoadFailed": "Ошибка загрузки конфигурации провайдеров", + "providerUpdateFailed": "Ошибка обновления провайдера по умолчанию в файле default", + "providerDefaultUpdated": "Провайдер {id} установлен по умолчанию в файле default" + }, + "resources": { + "title": "Ресурсы", + "githubDesc": "Поставьте нам звезду на GitHub!", + "docsTitle": "Документация", + "docsDesc": "Официальная документация AstrBot.", + "afdianTitle": "Afdian", + "afdianDesc": "Поддержите команду AstrBot через Afdian." + } +} \ No newline at end of file diff --git a/dashboard/src/i18n/locales/ru-RU/messages/errors.json b/dashboard/src/i18n/locales/ru-RU/messages/errors.json new file mode 100644 index 000000000..90ea43c85 --- /dev/null +++ b/dashboard/src/i18n/locales/ru-RU/messages/errors.json @@ -0,0 +1,39 @@ +{ + "network": { + "timeout": "Время ожидания запроса истекло, попробуйте позже", + "connection": "Ошибка сетевого соединения. Проверьте интернет", + "server": "Внутренняя ошибка сервера. Обратитесь в поддержку", + "unavailable": "Сервис временно недоступен", + "forbidden": "Доступ запрещен" + }, + "validation": { + "required": "Это поле обязательно для заполнения", + "invalid": "Неверный формат ввода", + "tooLong": "Введено слишком много символов", + "tooShort": "Введено слишком мало символов", + "email": "Укажите корректный email", + "url": "Укажите корректный URL", + "number": "Введите числовое значение" + }, + "auth": { + "unauthorized": "Авторизация не выполнена, войдите снова", + "forbidden": "Недостаточно прав для выполнения операции", + "tokenExpired": "Сессия истекла, пожалуйста, войдите заново", + "invalidCredentials": "Неверное имя пользователя или пароль" + }, + "file": { + "uploadFailed": "Загрузка файла не удалась", + "invalidFormat": "Неподдерживаемый формат файла", + "tooLarge": "Файл слишком большой", + "notFound": "Файл не найден" + }, + "operation": { + "failed": "Операция не удалась", + "cancelled": "Операция отменена", + "notSupported": "Действие не поддерживается", + "conflict": "Конфликт операций, попробуйте позже" + }, + "browser": { + "audioNotSupported": "Ваш браузер не поддерживает воспроизведение аудио." + } +} \ No newline at end of file diff --git a/dashboard/src/i18n/locales/ru-RU/messages/success.json b/dashboard/src/i18n/locales/ru-RU/messages/success.json new file mode 100644 index 000000000..1501d5cb2 --- /dev/null +++ b/dashboard/src/i18n/locales/ru-RU/messages/success.json @@ -0,0 +1,23 @@ +{ + "operation": { + "saved": "Сохранено", + "created": "Создано", + "updated": "Обновлено успешно", + "deleted": "Удалено", + "uploaded": "Загружено", + "downloaded": "Скачано", + "imported": "Импорт завершен", + "exported": "Экспорт завершен", + "copied": "Скопировано в буфер", + "sent": "Отправлено" + }, + "connection": { + "connected": "Подключено", + "authenticated": "Вход выполнен", + "synchronized": "Синхронизация завершена" + }, + "validation": { + "valid": "Проверка пройдена", + "completed": "Готово" + } +} \ No newline at end of file diff --git a/dashboard/src/i18n/locales/ru-RU/messages/validation.json b/dashboard/src/i18n/locales/ru-RU/messages/validation.json new file mode 100644 index 000000000..07d061579 --- /dev/null +++ b/dashboard/src/i18n/locales/ru-RU/messages/validation.json @@ -0,0 +1,25 @@ +{ + "required": "Это поле обязательно", + "email": "Введите корректный email", + "url": "Введите корректный URL", + "number": "Введите число", + "min": "Минимальное значение: {min}", + "max": "Максимальное значение: {max}", + "minLength": "Минимум {length} симв.", + "maxLength": "Максимум {length} симв.", + "pattern": "Неверный формат", + "unique": "Такое значение уже существует", + "confirm": "Значения не совпадают", + "fileSize": "Размер файла не должен превышать {size}MB", + "fileType": "Неподдерживаемый тип файла", + "required_field": "Заполните обязательные поля", + "invalid_format": "Некорректный формат", + "password_too_short": "Пароль должен быть не менее 8 символов", + "password_too_weak": "Пароль слишком слабый", + "invalid_phone": "Некорректный номер телефона", + "invalid_date": "Некорректная дата", + "date_range": "Неверный диапазон дат", + "upload_failed": "Загрузка не удалась", + "network_error": "Ошибка сети, попробуйте снова", + "operation_cannot_be_undone": "⚠️ Это действие нельзя отменить, будьте осторожны!" +} \ No newline at end of file diff --git a/dashboard/src/i18n/translations.ts b/dashboard/src/i18n/translations.ts index d72cc9114..fa15e619f 100644 --- a/dashboard/src/i18n/translations.ts +++ b/dashboard/src/i18n/translations.ts @@ -42,7 +42,7 @@ import zhCNErrors from './locales/zh-CN/messages/errors.json'; import zhCNSuccess from './locales/zh-CN/messages/success.json'; import zhCNValidation from './locales/zh-CN/messages/validation.json'; -// 英文翻译 +// English translation import enUSCommon from './locales/en-US/core/common.json'; import enUSActions from './locales/en-US/core/actions.json'; import enUSStatus from './locales/en-US/core/status.json'; @@ -83,6 +83,47 @@ import enUSErrors from './locales/en-US/messages/errors.json'; import enUSSuccess from './locales/en-US/messages/success.json'; import enUSValidation from './locales/en-US/messages/validation.json'; +// Russian translation +import ruRUCommon from './locales/ru-RU/core/common.json'; +import ruRUActions from './locales/ru-RU/core/actions.json'; +import ruRUStatus from './locales/ru-RU/core/status.json'; +import ruRUNavigation from './locales/ru-RU/core/navigation.json'; +import ruRUHeader from './locales/ru-RU/core/header.json'; +import ruRUShared from './locales/ru-RU/core/shared.json'; + +import ruRUChat from './locales/ru-RU/features/chat.json'; +import ruRUExtension from './locales/ru-RU/features/extension.json'; +import ruRUConversation from './locales/ru-RU/features/conversation.json'; +import ruRUSessionManagement from './locales/ru-RU/features/session-management.json'; +import ruRUToolUse from './locales/ru-RU/features/tool-use.json'; +import ruRUProvider from './locales/ru-RU/features/provider.json'; +import ruRUPlatform from './locales/ru-RU/features/platform.json'; +import ruRUConfig from './locales/ru-RU/features/config.json'; +import ruRUConfigMetadata from './locales/ru-RU/features/config-metadata.json'; +import ruRUConsole from './locales/ru-RU/features/console.json'; +import ruRUTrace from './locales/ru-RU/features/trace.json'; +import ruRUAbout from './locales/ru-RU/features/about.json'; +import ruRUSettings from './locales/ru-RU/features/settings.json'; +import ruRUAuth from './locales/ru-RU/features/auth.json'; +import ruRUChart from './locales/ru-RU/features/chart.json'; +import ruRUDashboard from './locales/ru-RU/features/dashboard.json'; +import ruRUCron from './locales/ru-RU/features/cron.json'; +import ruRUAlkaidIndex from './locales/ru-RU/features/alkaid/index.json'; +import ruRUAlkaidKnowledgeBase from './locales/ru-RU/features/alkaid/knowledge-base.json'; +import ruRUAlkaidMemory from './locales/ru-RU/features/alkaid/memory.json'; +import ruRUKnowledgeBaseIndex from './locales/ru-RU/features/knowledge-base/index.json'; +import ruRUKnowledgeBaseDetail from './locales/ru-RU/features/knowledge-base/detail.json'; +import ruRUKnowledgeBaseDocument from './locales/ru-RU/features/knowledge-base/document.json'; +import ruRUPersona from './locales/ru-RU/features/persona.json'; +import ruRUMigration from './locales/ru-RU/features/migration.json'; +import ruRUCommand from './locales/ru-RU/features/command.json'; +import ruRUSubagent from './locales/ru-RU/features/subagent.json'; +import ruRUWelcome from './locales/ru-RU/features/welcome.json'; + +import ruRUErrors from './locales/ru-RU/messages/errors.json'; +import ruRUSuccess from './locales/ru-RU/messages/success.json'; +import ruRUValidation from './locales/ru-RU/messages/validation.json'; + // 组装翻译对象 export const translations = { 'zh-CN': { @@ -182,6 +223,55 @@ export const translations = { success: enUSSuccess, validation: enUSValidation } + }, + 'ru-RU': { + core: { + common: ruRUCommon, + actions: ruRUActions, + status: ruRUStatus, + navigation: ruRUNavigation, + header: ruRUHeader, + shared: ruRUShared + }, + features: { + chat: ruRUChat, + extension: ruRUExtension, + conversation: ruRUConversation, + 'session-management': ruRUSessionManagement, + tooluse: ruRUToolUse, + provider: ruRUProvider, + platform: ruRUPlatform, + config: ruRUConfig, + 'config-metadata': ruRUConfigMetadata, + console: ruRUConsole, + trace: ruRUTrace, + about: ruRUAbout, + settings: ruRUSettings, + auth: ruRUAuth, + chart: ruRUChart, + dashboard: ruRUDashboard, + cron: ruRUCron, + alkaid: { + index: ruRUAlkaidIndex, + 'knowledge-base': ruRUAlkaidKnowledgeBase, + memory: ruRUAlkaidMemory + }, + 'knowledge-base': { + index: ruRUKnowledgeBaseIndex, + detail: ruRUKnowledgeBaseDetail, + document: ruRUKnowledgeBaseDocument + }, + persona: ruRUPersona, + migration: ruRUMigration, + command: ruRUCommand, + subagent: ruRUSubagent, + welcome: ruRUWelcome + }, + messages: { + errors: ruRUErrors, + success: ruRUSuccess, + validation: ruRUValidation + } } }; From e6c11647557a16f1a7f3b13d5dac119435adf0b2 Mon Sep 17 00:00:00 2001 From: 2ndelement <72551339+2ndelement@users.noreply.github.com> Date: Fri, 13 Mar 2026 00:24:15 +0800 Subject: [PATCH 09/62] perf(QQ Official API): improve streaming message delivery reliability and proactive media sending (#6131) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(qqofficial): fix streaming message delivery for C2C * fix(qqofficial): rewrite send_streaming for C2C vs non-C2C split * fix(qqofficial): add _extract_response_message_id for safe id extraction * fix(qqofficial): flush stream segment on tool-call break signal * fix(qqofficial): downgrade rich-media to non-stream send in C2C * fix(qqofficial): auto-append \n to final stream chunk (state=10) * fix(qqofficial): propagate stream param to all _send_with_markdown_fallback call sites * fix(qqofficial): retry on STREAM_MARKDOWN_NEWLINE_ERROR with newline fix * fix(qqofficial): handle None/non-dict response in post_c2c_message gracefully * fix(qqofficial): remove msg_id from video/file media payloads in send_by_session QQ API rejects msg_id on proactive media (video/file, msg_type=7) messages sent via the tool-call path, returning "请求参数msg_id无效或越权". The msg_id passive-reply credential is consumed by the first send and cannot be reused for subsequent media uploads in the same session. Remove msg_id from the payload after setting msg_type=7 for video and file sends, for both FRIEND_MESSAGE (C2C) and GROUP_MESSAGE paths. * fix(qqofficial): replace deprecated get_event_loop() with get_running_loop() asyncio.get_event_loop() is deprecated since Python 3.10 and raises a DeprecationWarning (or errors) when called from inside a running coroutine without a current event loop set on the thread. Replace both call-sites in the streaming throttle logic with asyncio.get_running_loop(), which is the correct API to use inside an already-running async context. Co-Authored-By: Claude Sonnet --------- Co-authored-by: 2ndelement <2ndelement@users.noreply.github.com> Co-authored-by: Claude Sonnet --- .../qqofficial/qqofficial_message_event.py | 148 +++++++++++++++--- .../qqofficial/qqofficial_platform_adapter.py | 6 + 2 files changed, 132 insertions(+), 22 deletions(-) diff --git a/astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py b/astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py index d1fd0e187..97b2b2fb4 100644 --- a/astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py +++ b/astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py @@ -51,6 +51,7 @@ class QQOfficialMessageEvent(AstrMessageEvent): VIDEO_FILE_TYPE = 2 VOICE_FILE_TYPE = 3 FILE_FILE_TYPE = 4 + STREAM_MARKDOWN_NEWLINE_ERROR = "流式消息md分片需要\\n结束" def __init__( self, @@ -69,35 +70,71 @@ class QQOfficialMessageEvent(AstrMessageEvent): await self._post_send() async def send_streaming(self, generator, use_fallback: bool = False): - """流式输出仅支持消息列表私聊""" + """流式输出仅支持消息列表私聊(C2C),其他消息源退化为普通发送""" + # 先标记事件层“已执行发送操作”,避免异常路径遗漏 + await super().send_streaming(generator, use_fallback) + # QQ C2C 流式协议:开始/中间分片使用 state=1,结束分片使用 state=10 stream_payload = {"state": 1, "id": None, "index": 0, "reset": False} - last_edit_time = 0 # 上次编辑消息的时间 - throttle_interval = 1 # 编辑消息的间隔时间 (秒) + last_edit_time = 0 # 上次发送分片的时间 + throttle_interval = 1 # 分片间最短间隔 (秒) ret = None + source = ( + self.message_obj.raw_message + ) # 提前获取,避免 generator 为空时 NameError try: async for chain in generator: source = self.message_obj.raw_message + + if not isinstance(source, botpy.message.C2CMessage): + # 非 C2C 场景:直接累积,最后统一发 + if not self.send_buffer: + self.send_buffer = chain + else: + self.send_buffer.chain.extend(chain.chain) + continue + + # ---- C2C 流式场景 ---- + + # tool_call break 信号:工具开始执行,先把已有 buffer 以 state=10 结束当前流式段 + if chain.type == "break": + if self.send_buffer: + stream_payload["state"] = 10 + ret = await self._post_send(stream=stream_payload) + ret_id = self._extract_response_message_id(ret) + if ret_id is not None: + stream_payload["id"] = ret_id + # 重置 stream_payload,为下一段流式做准备 + stream_payload = { + "state": 1, + "id": None, + "index": 0, + "reset": False, + } + last_edit_time = 0 + continue + + # 累积内容 if not self.send_buffer: self.send_buffer = chain else: self.send_buffer.chain.extend(chain.chain) - if isinstance(source, botpy.message.C2CMessage): - # 真流式传输 - current_time = asyncio.get_running_loop().time() - time_since_last_edit = current_time - last_edit_time - - if time_since_last_edit >= throttle_interval: - ret = cast( - message.Message, - await self._post_send(stream=stream_payload), - ) - stream_payload["index"] += 1 - stream_payload["id"] = ret["id"] - last_edit_time = asyncio.get_running_loop().time() + # 节流:按时间间隔发送中间分片 + current_time = asyncio.get_running_loop().time() + if current_time - last_edit_time >= throttle_interval: + ret = cast( + message.Message, + await self._post_send(stream=stream_payload), + ) + stream_payload["index"] += 1 + ret_id = self._extract_response_message_id(ret) + if ret_id is not None: + stream_payload["id"] = ret_id + last_edit_time = asyncio.get_running_loop().time() + self.send_buffer = None # 清空已发送的分片,避免下次重复发送旧内容 if isinstance(source, botpy.message.C2CMessage): - # 结束流式对话,并且传输 buffer 中剩余的消息 + # 结束流式对话,发送 buffer 中剩余内容 stream_payload["state"] = 10 ret = await self._post_send(stream=stream_payload) else: @@ -105,9 +142,22 @@ class QQOfficialMessageEvent(AstrMessageEvent): except Exception as e: logger.error(f"发送流式消息时出错: {e}", exc_info=True) + # 避免累计内容在异常后被整包重复发送:仅清理缓存,不做非流式整包兜底 + # 如需兜底,应该只发送未发送 delta(后续可继续优化) self.send_buffer = None - return await super().send_streaming(generator, use_fallback) + return None + + @staticmethod + def _extract_response_message_id(ret) -> str | None: + """兼容 qq-botpy 返回 Message 对象或 dict 两种形态。""" + if ret is None: + return None + if isinstance(ret, dict): + ret_id = ret.get("id") + return str(ret_id) if ret_id is not None else None + ret_id = getattr(ret, "id", None) + return str(ret_id) if ret_id is not None else None async def _post_send(self, stream: dict | None = None): if not self.send_buffer: @@ -135,6 +185,11 @@ class QQOfficialMessageEvent(AstrMessageEvent): file_name, ) = await QQOfficialMessageEvent._parse_to_qqofficial(self.send_buffer) + # C2C 流式仅用于文本分片,富媒体时降级为普通发送,避免平台侧流式校验报错。 + if stream and (image_base64 or record_file_path): + logger.debug("[QQOfficial] 检测到富媒体,降级为非流式发送。") + stream = None + if ( not plain_text and not image_base64 @@ -145,6 +200,17 @@ class QQOfficialMessageEvent(AstrMessageEvent): ): return None + # QQ C2C 流式 API 说明: + # - 开始/中间分片(state=1):增量追加内容,不需要 \n(加了会导致强制换行) + # - 最终分片(state=10):结束流,content 必须以 \n 结尾(QQ API 要求) + if ( + stream + and stream.get("state") == 10 + and plain_text + and not plain_text.endswith("\n") + ): + plain_text = plain_text + "\n" + payload: dict = { # "content": plain_text, "markdown": MarkdownPayload(content=plain_text) if plain_text else None, @@ -214,6 +280,7 @@ class QQOfficialMessageEvent(AstrMessageEvent): ), payload=payload, plain_text=plain_text, + stream=stream, ) case botpy.message.C2CMessage(): @@ -270,6 +337,7 @@ class QQOfficialMessageEvent(AstrMessageEvent): ), payload=payload, plain_text=plain_text, + stream=stream, ) else: ret = await self._send_with_markdown_fallback( @@ -279,6 +347,7 @@ class QQOfficialMessageEvent(AstrMessageEvent): ), payload=payload, plain_text=plain_text, + stream=stream, ) logger.debug(f"Message sent to C2C: {ret}") @@ -294,6 +363,7 @@ class QQOfficialMessageEvent(AstrMessageEvent): ), payload=payload, plain_text=plain_text, + stream=stream, ) case botpy.message.DirectMessage(): @@ -308,6 +378,7 @@ class QQOfficialMessageEvent(AstrMessageEvent): ), payload=payload, plain_text=plain_text, + stream=stream, ) case _: @@ -324,10 +395,31 @@ class QQOfficialMessageEvent(AstrMessageEvent): send_func, payload: dict, plain_text: str, + stream: dict | None = None, ): try: return await send_func(payload) except botpy.errors.ServerError as err: + # QQ 流式 markdown 分片校验:内容必须以换行结尾。 + # 某些边界场景服务端仍可能判定失败,这里做一次修正重试。 + if stream and self.STREAM_MARKDOWN_NEWLINE_ERROR in str(err): + retry_payload = payload.copy() + + markdown_payload = retry_payload.get("markdown") + if isinstance(markdown_payload, dict): + md_content = cast(str, markdown_payload.get("content", "") or "") + if md_content and not md_content.endswith("\n"): + retry_payload["markdown"] = {"content": md_content + "\n"} + + content = cast(str | None, retry_payload.get("content")) + if content and not content.endswith("\n"): + retry_payload["content"] = content + "\n" + + logger.warning( + "[QQOfficial] 流式 markdown 分片换行校验失败,已修正后重试一次。" + ) + return await send_func(retry_payload) + if ( self.MARKDOWN_NOT_ALLOWED_ERROR not in str(err) or not payload.get("markdown") @@ -339,10 +431,14 @@ class QQOfficialMessageEvent(AstrMessageEvent): "[QQOfficial] markdown 发送被拒绝,回退到 content 模式重试。" ) fallback_payload = payload.copy() - fallback_payload["markdown"] = None + fallback_payload.pop("markdown", None) fallback_payload["content"] = plain_text if fallback_payload.get("msg_type") == 2: fallback_payload["msg_type"] = 0 + if stream: + fallback_content = cast(str, fallback_payload.get("content") or "") + if fallback_content and not fallback_content.endswith("\n"): + fallback_payload["content"] = fallback_content + "\n" return await send_func(fallback_payload) async def upload_group_and_c2c_image( @@ -460,13 +556,21 @@ class QQOfficialMessageEvent(AstrMessageEvent): ) -> message.Message: payload = locals() payload.pop("self", None) + # QQ API does not accept stream.id=None; remove it when not yet assigned + if "stream" in payload and payload["stream"] is not None: + stream_data = dict(payload["stream"]) + if stream_data.get("id") is None: + stream_data.pop("id", None) + payload["stream"] = stream_data route = Route("POST", "/v2/users/{openid}/messages", openid=openid) result = await self.bot.api._http.request(route, json=payload) + if result is None: + logger.warning("[QQOfficial] post_c2c_message: API 返回 None,跳过本次发送") + return None if not isinstance(result, dict): - raise RuntimeError( - f"Failed to post c2c message, response is not dict: {result}" - ) + logger.error(f"[QQOfficial] post_c2c_message: 响应不是 dict: {result}") + return None return message.Message(**result) diff --git a/astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py b/astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py index 88d4a2128..436be70db 100644 --- a/astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py +++ b/astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py @@ -212,6 +212,7 @@ class QQOfficialPlatformAdapter(Platform): if media: payload["media"] = media payload["msg_type"] = 7 + payload.pop("msg_id", None) if file_source: media = await QQOfficialMessageEvent.upload_group_and_c2c_media( send_helper, # type: ignore @@ -223,6 +224,7 @@ class QQOfficialPlatformAdapter(Platform): if media: payload["media"] = media payload["msg_type"] = 7 + payload.pop("msg_id", None) ret = await self.client.api.post_group_message( group_openid=session.session_id, **payload, @@ -266,6 +268,9 @@ class QQOfficialPlatformAdapter(Platform): if media: payload["media"] = media payload["msg_type"] = 7 + # QQ API rejects msg_id for media (video/file) messages sent + # via the proactive tool-call path; remove it to avoid 越权 error. + payload.pop("msg_id", None) if file_source: media = await QQOfficialMessageEvent.upload_group_and_c2c_media( send_helper, # type: ignore @@ -277,6 +282,7 @@ class QQOfficialPlatformAdapter(Platform): if media: payload["media"] = media payload["msg_type"] = 7 + payload.pop("msg_id", None) ret = await QQOfficialMessageEvent.post_c2c_message( send_helper, # type: ignore From 994d39241e89d46a4b8bd64885018b1666b2ab40 Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Fri, 13 Mar 2026 00:26:40 +0800 Subject: [PATCH 10/62] chore: ruff format --- astrbot/dashboard/routes/backup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/astrbot/dashboard/routes/backup.py b/astrbot/dashboard/routes/backup.py index 49722c6a3..ecc5dbfc8 100644 --- a/astrbot/dashboard/routes/backup.py +++ b/astrbot/dashboard/routes/backup.py @@ -986,7 +986,7 @@ class BackupRoute(Route): "require": ["exp"], # Require expiration claim "verify_signature": True, # Explicitly verify signature "verify_exp": True, # Verify expiration - } + }, ) except jwt.ExpiredSignatureError: return Response().error("Token 已过期,请刷新页面后重试").__dict__ From a21bb5b234a5cfd54071a11227a50627284c638b Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Fri, 13 Mar 2026 00:33:36 +0800 Subject: [PATCH 11/62] chore: bump version to 4.20.0 --- astrbot/cli/__init__.py | 2 +- astrbot/core/config/default.py | 2 +- changelogs/v4.20.0.md | 64 ++++++++++++++++++++++++++++++++++ pyproject.toml | 2 +- 4 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 changelogs/v4.20.0.md diff --git a/astrbot/cli/__init__.py b/astrbot/cli/__init__.py index 593f1c94e..9abbe5d75 100644 --- a/astrbot/cli/__init__.py +++ b/astrbot/cli/__init__.py @@ -1 +1 @@ -__version__ = "4.19.5" +__version__ = "4.20.0" diff --git a/astrbot/core/config/default.py b/astrbot/core/config/default.py index ba656ff53..16d7e89e3 100644 --- a/astrbot/core/config/default.py +++ b/astrbot/core/config/default.py @@ -5,7 +5,7 @@ from typing import Any, TypedDict from astrbot.core.utils.astrbot_path import get_astrbot_data_path -VERSION = "4.19.5" +VERSION = "4.20.0" DB_PATH = os.path.join(get_astrbot_data_path(), "data_v4.db") WEBHOOK_SUPPORTED_PLATFORMS = [ diff --git a/changelogs/v4.20.0.md b/changelogs/v4.20.0.md new file mode 100644 index 000000000..3db18b5ea --- /dev/null +++ b/changelogs/v4.20.0.md @@ -0,0 +1,64 @@ +## What's Changed + +### 新增 + +- 新增俄语翻译([#6081](https://github.com/AstrBotDevs/AstrBot/pull/6081))。 +- QQ 官方 Bot 新增文件、语音、视频消息支持(含 WebSocket 模式)([#6063](https://github.com/AstrBotDevs/AstrBot/pull/6063))。 + +### 优化 + +- 优化 QQ 官方 Bot 的流式消息投递可靠性与主动媒体发送能力([#6131](https://github.com/AstrBotDevs/AstrBot/pull/6131))。 +- 优化边界场景下 booter 选择逻辑与消息发送工具([#6064](https://github.com/AstrBotDevs/AstrBot/pull/6064))。 + +### 修复 + +- 修复 Dashboard README 对话框锚点导航失效([#6083](https://github.com/AstrBotDevs/AstrBot/pull/6083))。 +- 优先使用具名 weekday 的 cron 示例,避免歧义([#6091](https://github.com/AstrBotDevs/AstrBot/pull/6091))。 +- 修复插件市场安装后状态未及时刷新的问题([#6124](https://github.com/AstrBotDevs/AstrBot/pull/6124))。 +- 修复插件依赖安装逻辑:仅安装缺失依赖([#6088](https://github.com/AstrBotDevs/AstrBot/pull/6088))。 +- 移除 Telegram 适配器中已废弃的 `normalize_whitespace` 参数([#6044](https://github.com/AstrBotDevs/AstrBot/pull/6044))。 +- 修复 Windows 本地 skill 文件读取问题([#6028](https://github.com/AstrBotDevs/AstrBot/pull/6028))。 +- 修复 Discord pre-ack emoji 配置重启后不持久化的问题([#6031](https://github.com/AstrBotDevs/AstrBot/pull/6031))。 +- 统一 WebUI 搜索框清空行为([#6017](https://github.com/AstrBotDevs/AstrBot/pull/6017))。 +- 优化插件依赖自动安装流程与 Dashboard 安装体验([#5954](https://github.com/AstrBotDevs/AstrBot/pull/5954))。 + + +### 文档 + +- 新增 Astrbook 和玖帕喵社区链接([#6135](https://github.com/AstrBotDevs/AstrBot/pull/6135))。 +- 修正文档 `docker.md` 与 `napcat.md` 中的拼写错误([#6048](https://github.com/AstrBotDevs/AstrBot/pull/6048))。 +- 在多语言 README 中补充官方开发群号,并改进配置元数据中的正则说明。 +- 更新编辑链接模式并移除过时仓库引用。 + +--- + +## What's Changed (EN) + +### New Features + +- Added Russian translation support ([#6081](https://github.com/AstrBotDevs/AstrBot/pull/6081)). +- Added file, voice, and video message support for QQ Official Bot (including WebSocket mode) ([#6063](https://github.com/AstrBotDevs/AstrBot/pull/6063)). + +### Improvements + +- Improved streaming message delivery reliability and proactive media sending for QQ Official API ([#6131](https://github.com/AstrBotDevs/AstrBot/pull/6131)). +- Optimized booter selection logic in edge cases and message sending tooling ([#6064](https://github.com/AstrBotDevs/AstrBot/pull/6064)). + +### Bug Fixes + +- Fixed broken README dialog anchor navigation in the Dashboard ([#6083](https://github.com/AstrBotDevs/AstrBot/pull/6083)). +- Preferred named weekday cron examples to reduce ambiguity ([#6091](https://github.com/AstrBotDevs/AstrBot/pull/6091)). +- Fixed plugin market install-state refresh after installation ([#6124](https://github.com/AstrBotDevs/AstrBot/pull/6124)). +- Fixed plugin dependency installation logic to install only missing packages ([#6088](https://github.com/AstrBotDevs/AstrBot/pull/6088)). +- Removed deprecated `normalize_whitespace` parameter in the Telegram adapter ([#6044](https://github.com/AstrBotDevs/AstrBot/pull/6044)). +- Fixed local skill file reading issues on Windows ([#6028](https://github.com/AstrBotDevs/AstrBot/pull/6028)). +- Fixed Discord pre-ack emoji config not being persisted across restarts ([#6031](https://github.com/AstrBotDevs/AstrBot/pull/6031)). +- Unified WebUI search input clear behavior ([#6017](https://github.com/AstrBotDevs/AstrBot/pull/6017)). +- Improved plugin dependency auto-install flow and Dashboard installation experience ([#5954](https://github.com/AstrBotDevs/AstrBot/pull/5954)). + +### Documentation + +- Added Astrbook and Jiupa Miao community links ([#6135](https://github.com/AstrBotDevs/AstrBot/pull/6135)). +- Fixed typos in `docker.md` and `napcat.md` ([#6048](https://github.com/AstrBotDevs/AstrBot/pull/6048)). +- Added official developer group IDs to multilingual READMEs and improved regex description in config metadata. +- Updated edit-link patterns and removed obsolete repository references. diff --git a/pyproject.toml b/pyproject.toml index 787af9ee7..fe98575f2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "AstrBot" -version = "4.19.5" +version = "4.20.0" description = "Easy-to-use multi-platform LLM chatbot and development framework" readme = "README.md" requires-python = ">=3.12" From a8ff2b3d9cb3f340def8b754854ceb0642a52a0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=A8=E3=82=A4=E3=82=AB=E3=82=AF?= <1259085392@qq.com> Date: Fri, 13 Mar 2026 11:53:50 +0900 Subject: [PATCH 12/62] fix(dashboard): stabilize sidebar hash navigation on startup (#6159) * fix(dashboard): stabilize sidebar hash navigation on startup * fix(dashboard): reuse shared extension tab route helpers * fix(dashboard): avoid leaking extension route query state * fix(dashboard): preserve route params in tab locations * fix(dashboard): harden hash tab routing fallbacks * fix(dashboard): warn on tab route navigation failures * fix(dashboard): harden extension tab startup routing --- dashboard/src/main.ts | 9 +- dashboard/src/router/MainRoutes.ts | 4 +- dashboard/src/router/routeConstants.mjs | 1 + dashboard/src/utils/hashRouteTabs.mjs | 46 +++++++ dashboard/src/utils/routerReadiness.mjs | 5 + .../src/views/extension/useExtensionPage.js | 52 +++----- dashboard/tests/hashRouteTabs.test.mjs | 123 ++++++++++++++++++ dashboard/tests/routerReadiness.test.mjs | 29 +++++ 8 files changed, 230 insertions(+), 39 deletions(-) create mode 100644 dashboard/src/router/routeConstants.mjs create mode 100644 dashboard/src/utils/hashRouteTabs.mjs create mode 100644 dashboard/src/utils/routerReadiness.mjs create mode 100644 dashboard/tests/hashRouteTabs.test.mjs create mode 100644 dashboard/tests/routerReadiness.test.mjs diff --git a/dashboard/src/main.ts b/dashboard/src/main.ts index 687166654..4bfec5e77 100644 --- a/dashboard/src/main.ts +++ b/dashboard/src/main.ts @@ -11,19 +11,21 @@ import VueApexCharts from 'vue3-apexcharts'; import print from 'vue3-print-nb'; import { loader } from '@guolao/vue-monaco-editor' import axios from 'axios'; +import { waitForRouterReadyInBackground } from './utils/routerReadiness.mjs'; // 初始化新的i18n系统,等待完成后再挂载应用 -setupI18n().then(() => { +setupI18n().then(async () => { console.log('🌍 新i18n系统初始化完成'); const app = createApp(App); - app.use(router); const pinia = createPinia(); app.use(pinia); + app.use(router); app.use(print); app.use(VueApexCharts); app.use(vuetify); app.use(confirmPlugin); + await router.isReady(); app.mount('#app'); // 挂载后同步 Vuetify 主题 @@ -49,14 +51,15 @@ setupI18n().then(() => { // 即使i18n初始化失败,也要挂载应用(使用回退机制) const app = createApp(App); - app.use(router); const pinia = createPinia(); app.use(pinia); + app.use(router); app.use(print); app.use(VueApexCharts); app.use(vuetify); app.use(confirmPlugin); app.mount('#app'); + waitForRouterReadyInBackground(router); // 挂载后同步 Vuetify 主题 import('./stores/customizer').then(({ useCustomizerStore }) => { diff --git a/dashboard/src/router/MainRoutes.ts b/dashboard/src/router/MainRoutes.ts index 024f591cc..109122dd8 100644 --- a/dashboard/src/router/MainRoutes.ts +++ b/dashboard/src/router/MainRoutes.ts @@ -1,3 +1,5 @@ +import { EXTENSION_ROUTE_NAME } from './routeConstants.mjs'; + const MainRoutes = { path: '/main', meta: { @@ -17,7 +19,7 @@ const MainRoutes = { component: () => import('@/views/WelcomePage.vue') }, { - name: 'Extensions', + name: EXTENSION_ROUTE_NAME, path: '/extension', component: () => import('@/views/ExtensionPage.vue') }, diff --git a/dashboard/src/router/routeConstants.mjs b/dashboard/src/router/routeConstants.mjs new file mode 100644 index 000000000..c5f4f628a --- /dev/null +++ b/dashboard/src/router/routeConstants.mjs @@ -0,0 +1 @@ +export const EXTENSION_ROUTE_NAME = 'Extensions'; diff --git a/dashboard/src/utils/hashRouteTabs.mjs b/dashboard/src/utils/hashRouteTabs.mjs new file mode 100644 index 000000000..c1d7c453d --- /dev/null +++ b/dashboard/src/utils/hashRouteTabs.mjs @@ -0,0 +1,46 @@ +import { EXTENSION_ROUTE_NAME } from '../router/routeConstants.mjs'; + +export function getValidHashTab(routeHash, validTabs) { + const hash = String(routeHash || ''); + const tab = hash.includes('#') ? hash.slice(hash.lastIndexOf('#') + 1) : hash; + return validTabs.includes(tab) ? tab : null; +} + +export function createTabRouteLocation(route, tab, fallbackRouteName = EXTENSION_ROUTE_NAME) { + const query = route?.query ? { ...route.query } : {}; + const params = route?.params ? { ...route.params } : undefined; + + if (route?.name) { + return { + name: route.name, + ...(params ? { params } : {}), + query, + hash: `#${tab}`, + }; + } + + if (route?.path) { + return { + path: route.path, + query, + hash: `#${tab}`, + }; + } + + return { + name: fallbackRouteName, + ...(params ? { params } : {}), + query, + hash: `#${tab}`, + }; +} + +export async function replaceTabRoute(router, route, tab, logger = console) { + try { + await router.replace(createTabRouteLocation(route, tab)); + return true; + } catch (error) { + logger.warn?.('Failed to update extension tab route:', error); + return false; + } +} diff --git a/dashboard/src/utils/routerReadiness.mjs b/dashboard/src/utils/routerReadiness.mjs new file mode 100644 index 000000000..d989212c7 --- /dev/null +++ b/dashboard/src/utils/routerReadiness.mjs @@ -0,0 +1,5 @@ +export function waitForRouterReadyInBackground(router, logger = console) { + router.isReady().catch((error) => { + logger.warn?.('Router did not become ready after fallback mount:', error); + }); +} diff --git a/dashboard/src/views/extension/useExtensionPage.js b/dashboard/src/views/extension/useExtensionPage.js index 4534c2f33..da77df083 100644 --- a/dashboard/src/views/extension/useExtensionPage.js +++ b/dashboard/src/views/extension/useExtensionPage.js @@ -10,6 +10,10 @@ import { toInitials, toPinyinText, } from "@/utils/pluginSearch"; +import { + getValidHashTab, + replaceTabRoute, +} from "@/utils/hashRouteTabs.mjs"; import { ref, computed, onMounted, onUnmounted, reactive, watch } from "vue"; import { useRoute, useRouter } from "vue-router"; import { useDisplay } from "vuetify"; @@ -103,16 +107,11 @@ export const useExtensionPage = () => { const activeTab = ref("installed"); const validTabs = ["installed", "market", "mcp", "skills", "components"]; const isValidTab = (tab) => validTabs.includes(tab); - const getLocationHash = () => - typeof window !== "undefined" ? window.location.hash : ""; - const extractTabFromHash = (hash) => { - const lastHashIndex = (hash || "").lastIndexOf("#"); - if (lastHashIndex === -1) return ""; - return hash.slice(lastHashIndex + 1); - }; + const getLocationHash = () => route.hash || ""; + const extractTabFromHash = (hash) => getValidHashTab(hash, validTabs); const syncTabFromHash = (hash) => { const tab = extractTabFromHash(hash); - if (isValidTab(tab)) { + if (tab) { activeTab.value = tab; return true; } @@ -1436,9 +1435,7 @@ export const useExtensionPage = () => { // 生命周期 onMounted(async () => { if (!syncTabFromHash(getLocationHash())) { - if (typeof window !== "undefined") { - window.location.hash = `#${activeTab.value}`; - } + await replaceTabRoute(router, route, activeTab.value); } await getExtensions(); @@ -1446,17 +1443,9 @@ export const useExtensionPage = () => { loadCustomSources(); // 检查是否有 open_config 参数 - let urlParams; - if (window.location.hash) { - // For hash mode (#/path?param=value) - const hashQuery = window.location.hash.split("?")[1] || ""; - urlParams = new URLSearchParams(hashQuery); - } else { - // For history mode (/path?param=value) - urlParams = new URLSearchParams(window.location.search); - } - console.log("URL Parameters:", urlParams.toString()); - const plugin_name = urlParams.get("open_config"); + const plugin_name = Array.isArray(route.query.open_config) + ? route.query.open_config[0] + : route.query.open_config; if (plugin_name) { console.log(`Opening config for plugin: ${plugin_name}`); openExtensionConfig(plugin_name); @@ -1528,10 +1517,10 @@ export const useExtensionPage = () => { ); watch( - () => route.fullPath, - () => { - const tab = extractTabFromHash(getLocationHash()); - if (isValidTab(tab) && tab !== activeTab.value) { + () => route.hash, + (newHash) => { + const tab = extractTabFromHash(newHash); + if (tab && tab !== activeTab.value) { activeTab.value = tab; } }, @@ -1539,15 +1528,8 @@ export const useExtensionPage = () => { watch(activeTab, (newTab) => { if (!isValidTab(newTab)) return; - const currentTab = extractTabFromHash(getLocationHash()); - if (currentTab === newTab) return; - const hash = getLocationHash(); - const lastHashIndex = hash.lastIndexOf("#"); - const nextHash = - lastHashIndex > 0 ? `${hash.slice(0, lastHashIndex)}#${newTab}` : `#${newTab}`; - if (typeof window !== "undefined") { - window.location.hash = nextHash; - } + if (route.hash === `#${newTab}`) return; + void replaceTabRoute(router, route, newTab); }); return { diff --git a/dashboard/tests/hashRouteTabs.test.mjs b/dashboard/tests/hashRouteTabs.test.mjs new file mode 100644 index 000000000..2aefd679c --- /dev/null +++ b/dashboard/tests/hashRouteTabs.test.mjs @@ -0,0 +1,123 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; + +import * as hashRouteTabs from '../src/utils/hashRouteTabs.mjs'; +import { EXTENSION_ROUTE_NAME } from '../src/router/routeConstants.mjs'; + +const { createTabRouteLocation, getValidHashTab } = hashRouteTabs; + +test('getValidHashTab returns the tab name for a valid route hash', () => { + const validTabs = ['installed', 'market', 'mcp']; + + assert.equal(getValidHashTab('#market', validTabs), 'market'); +}); + +test('getValidHashTab rejects empty and unknown hashes', () => { + const validTabs = ['installed', 'market', 'mcp']; + + assert.equal(getValidHashTab('', validTabs), null); + assert.equal(getValidHashTab('#unknown', validTabs), null); +}); + +test('getValidHashTab uses the last hash segment when multiple hashes are present', () => { + const validTabs = ['installed', 'market', 'mcp']; + + assert.equal(getValidHashTab('#/extension#foo#installed', validTabs), 'installed'); +}); + +test('createTabRouteLocation preserves the current path and query', () => { + const query = { open_config: 'sample-plugin', page: '2' }; + const location = createTabRouteLocation( + { + path: '/extension', + query, + }, + 'market', + ); + + assert.deepEqual(location, { + path: '/extension', + query: { open_config: 'sample-plugin', page: '2' }, + hash: '#market', + }); + assert.notEqual(location.query, query); +}); + +test('createTabRouteLocation falls back to the extension route name', () => { + const location = createTabRouteLocation(undefined, 'installed'); + + assert.deepEqual(location, { + name: EXTENSION_ROUTE_NAME, + query: {}, + hash: '#installed', + }); +}); + +test('createTabRouteLocation prefers route name and preserves params', () => { + const params = { pluginId: 'demo-plugin' }; + const location = createTabRouteLocation( + { + name: 'ExtensionDetails', + path: '/extension/demo-plugin', + params, + query: { tab: 'details' }, + }, + 'market', + ); + + assert.deepEqual(location, { + name: 'ExtensionDetails', + params: { pluginId: 'demo-plugin' }, + query: { tab: 'details' }, + hash: '#market', + }); + assert.notEqual(location.params, params); +}); + +test('createTabRouteLocation omits params for path-based routes', () => { + const params = { pluginId: 'demo-plugin' }; + const location = createTabRouteLocation( + { + path: '/extension/demo-plugin', + params, + }, + 'installed', + ); + + assert.deepEqual(location, { + path: '/extension/demo-plugin', + query: {}, + hash: '#installed', + }); + assert.equal(location.params, undefined); +}); + +test('replaceTabRoute catches rejected router updates', async () => { + assert.equal(typeof hashRouteTabs.replaceTabRoute, 'function'); + + const error = new Error('blocked'); + let logged; + const router = { + replace: async () => { + throw error; + }, + }; + const logger = { + warn: (message, cause) => { + logged = { message, cause }; + }, + }; + + const result = await hashRouteTabs.replaceTabRoute( + router, + { name: EXTENSION_ROUTE_NAME, query: { page: '1' } }, + 'installed', + logger, + ); + + assert.equal(result, false); + assert.deepEqual(logged, { + message: 'Failed to update extension tab route:', + cause: error, + }); +}); diff --git a/dashboard/tests/routerReadiness.test.mjs b/dashboard/tests/routerReadiness.test.mjs new file mode 100644 index 000000000..171b271bc --- /dev/null +++ b/dashboard/tests/routerReadiness.test.mjs @@ -0,0 +1,29 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; + +test('waitForRouterReadyInBackground returns immediately and logs failures', async () => { + const module = await import('../src/utils/routerReadiness.mjs').catch(() => null); + + assert.ok(module?.waitForRouterReadyInBackground); + + const error = new Error('router blocked'); + let warned; + const readyPromise = Promise.reject(error); + const logger = { + warn: (message, cause) => { + warned = { message, cause }; + }, + }; + + const result = module.waitForRouterReadyInBackground( + { isReady: () => readyPromise }, + logger, + ); + + assert.equal(result, undefined); + await Promise.resolve(); + assert.deepEqual(warned, { + message: 'Router did not become ready after fallback mount:', + cause: error, + }); +}); From a60a40bca33ff46381c5961ec4273ec0527f8239 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=B3=AF=E7=B1=B3=E8=8C=A8?= <143102889+nuomicici@users.noreply.github.com> Date: Fri, 13 Mar 2026 20:51:39 +0800 Subject: [PATCH 13/62] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E9=83=A8=E5=88=86?= =?UTF-8?q?=E6=96=B0=E7=89=88=E6=9C=AC=E5=90=8D=E7=A7=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update the instructions for installing and configuring the Matrix adapter in AstrBot. --- docs/zh/platform/matrix.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/zh/platform/matrix.md b/docs/zh/platform/matrix.md index e4445e739..d27b337c2 100644 --- a/docs/zh/platform/matrix.md +++ b/docs/zh/platform/matrix.md @@ -8,6 +8,7 @@ 进入 AstrBot WebUI 的插件市场,搜索 `astrbot_plugin_matrix_adapter`,点击安装。 安装完成后,前往 消息平台 → 新增适配器 → 选择 Matrix(若选项缺失,尝试重启 AstrBot 或检查插件安装状态)。 +> 新版本`消息平台`更名为`机器人` 在弹出的配置对话框中点击 `启用`。 From 97732987d97b721f17ba6951251f09fa5347a530 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=B3=AF=E7=B1=B3=E8=8C=A8?= <143102889+nuomicici@users.noreply.github.com> Date: Fri, 13 Mar 2026 20:53:41 +0800 Subject: [PATCH 14/62] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E9=83=A8=E5=88=86?= =?UTF-8?q?=E6=96=B0=E7=89=88=E5=90=8D=E7=A7=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/zh/platform/wecom_ai_bot.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/zh/platform/wecom_ai_bot.md b/docs/zh/platform/wecom_ai_bot.md index 904c0790c..6ea10ad57 100644 --- a/docs/zh/platform/wecom_ai_bot.md +++ b/docs/zh/platform/wecom_ai_bot.md @@ -31,6 +31,7 @@ ## 配置 AstrBot 1. 进入 AstrBot 的管理面板,点击左侧栏 `消息平台`,然后在右侧的界面中,点击 `+ 新增适配器`,选择 `企业微信智能机器人`,进入配置页面。 + > 请注意,最新版本`消息平台`已更名为`机器人`! ![新增适配器](https://files.astrbot.app/docs/source/images/wecom_ai_bot/image-2.png) From 8d2140f607506423cf5f5c572be73b11ea6b5138 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=B3=AF=E7=B1=B3=E8=8C=A8?= <143102889+nuomicici@users.noreply.github.com> Date: Fri, 13 Mar 2026 21:13:45 +0800 Subject: [PATCH 15/62] Update docs/zh/platform/wecom_ai_bot.md Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- docs/zh/platform/wecom_ai_bot.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/zh/platform/wecom_ai_bot.md b/docs/zh/platform/wecom_ai_bot.md index 6ea10ad57..55aca1b01 100644 --- a/docs/zh/platform/wecom_ai_bot.md +++ b/docs/zh/platform/wecom_ai_bot.md @@ -30,8 +30,7 @@ ## 配置 AstrBot -1. 进入 AstrBot 的管理面板,点击左侧栏 `消息平台`,然后在右侧的界面中,点击 `+ 新增适配器`,选择 `企业微信智能机器人`,进入配置页面。 - > 请注意,最新版本`消息平台`已更名为`机器人`! +1. 进入 AstrBot 的管理面板,点击左侧栏 `机器人`(旧版本为 `消息平台`),然后在右侧的界面中,点击 `+ 新增适配器`,选择 `企业微信智能机器人`,进入配置页面。 ![新增适配器](https://files.astrbot.app/docs/source/images/wecom_ai_bot/image-2.png) From 1afbb357db4e134fbb2aaf3d955a2e5245e78177 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=B3=AF=E7=B1=B3=E8=8C=A8?= <143102889+nuomicici@users.noreply.github.com> Date: Fri, 13 Mar 2026 21:14:00 +0800 Subject: [PATCH 16/62] Update docs/zh/platform/matrix.md Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- docs/zh/platform/matrix.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/zh/platform/matrix.md b/docs/zh/platform/matrix.md index d27b337c2..822ce853d 100644 --- a/docs/zh/platform/matrix.md +++ b/docs/zh/platform/matrix.md @@ -7,8 +7,7 @@ 进入 AstrBot WebUI 的插件市场,搜索 `astrbot_plugin_matrix_adapter`,点击安装。 -安装完成后,前往 消息平台 → 新增适配器 → 选择 Matrix(若选项缺失,尝试重启 AstrBot 或检查插件安装状态)。 -> 新版本`消息平台`更名为`机器人` +安装完成后,前往 机器人(旧版本为 `消息平台`) → 新增适配器 → 选择 Matrix(若选项缺失,尝试重启 AstrBot 或检查插件安装状态)。 在弹出的配置对话框中点击 `启用`。 From 493662524a4516e25c8c44afa7a9aff9d4efec56 Mon Sep 17 00:00:00 2001 From: Salman Chishti Date: Sat, 14 Mar 2026 10:08:25 +0000 Subject: [PATCH 17/62] ci: upgrade GitHub Actions to latest versions (#6251) Signed-off-by: Salman Muin Kayser Chishti <13schishti@gmail.com> --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 68da87dfd..0cfe18261 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -50,7 +50,7 @@ jobs: echo "tag=$tag" >> "$GITHUB_OUTPUT" - name: Setup pnpm - uses: pnpm/action-setup@v4.3.0 + uses: pnpm/action-setup@v4.4.0 with: version: 10.28.2 From 89fbd75e7ac9c23e2e8e02f297b5c104c0f38b75 Mon Sep 17 00:00:00 2001 From: MousseC <31097345+MousseC@users.noreply.github.com> Date: Sat, 14 Mar 2026 18:12:55 +0800 Subject: [PATCH 18/62] perf(OneBot): add a whitespace after At component (#6238) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修复 At 组件后的空格在发送时被 strip 移除的问题。在消息解析时检测 At 组件并在其后额外插入空格。 --- .../sources/aiocqhttp/aiocqhttp_message_event.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py b/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py index 7e42a0fd8..4b642d8ce 100644 --- a/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py +++ b/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py @@ -6,6 +6,7 @@ from aiocqhttp import CQHttp, Event from astrbot.api.event import AstrMessageEvent, MessageChain from astrbot.api.message_components import ( + At, BaseMessageComponent, File, Image, @@ -70,11 +71,19 @@ class AiocqhttpMessageEvent(AstrMessageEvent): """解析成 OneBot json 格式""" ret = [] for segment in message_chain.chain: - if isinstance(segment, Plain): + if isinstance(segment, At): + # At 组件后插入一个空格,避免与后续文本粘连 + d = await AiocqhttpMessageEvent._from_segment_to_dict(segment) + ret.append(d) + ret.append({"type": "text", "data": {"text": " "}}) + elif isinstance(segment, Plain): if not segment.text.strip(): continue - d = await AiocqhttpMessageEvent._from_segment_to_dict(segment) - ret.append(d) + d = await AiocqhttpMessageEvent._from_segment_to_dict(segment) + ret.append(d) + else: + d = await AiocqhttpMessageEvent._from_segment_to_dict(segment) + ret.append(d) return ret @classmethod From 38f5e077ee59996dcfae5ba40b5ed1e05406ac8d Mon Sep 17 00:00:00 2001 From: lalala Date: Sat, 14 Mar 2026 18:15:06 +0800 Subject: [PATCH 19/62] fix: remove duplicate dependencies (#6247) remove duplicate `aiocqhttp` `aiodocker` `aiohttp` in requirements.txt --- requirements.txt | 3 --- 1 file changed, 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index 49e0707e0..124937039 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,9 +1,6 @@ aiocqhttp>=1.4.4 aiodocker>=0.24.0 aiohttp>=3.11.18 -aiocqhttp>=1.4.4 -aiodocker>=0.24.0 -aiohttp>=3.11.18 aiosqlite>=0.21.0 anthropic>=0.51.0 apscheduler>=3.11.0 From 08fc6577555f7aa5fddea80b42e7fa2a24302706 Mon Sep 17 00:00:00 2001 From: qingyun Date: Sat, 14 Mar 2026 18:18:14 +0800 Subject: [PATCH 20/62] fix: preserve whitespace in Plain.toDict() for @ mentions (#6244) * fix: preserve whitespace in Plain.toDict() for @ mentions - Remove .strip() from Plain.toDict() to match async to_dict() behavior - Fixes #6237: QQ @mentions no longer lose trailing spaces - This ensures '@user message' displays correctly instead of '@usermessage' * refactor: remove redundant to_dict() from Plain class - Let Plain inherit to_dict() from BaseMessageComponent - BaseMessageComponent.to_dict() calls toDict() by default - Reduces code duplication and prevents future divergence - Addressed code review feedback from @gemini-code-assist and @sourcery-ai * feat: add async to_dict method to Plain message component * fix: add return type hint to Plain.toDict method --------- Co-authored-by: ccsang Co-authored-by: Soulter <905617992@qq.com> --- astrbot/core/message/components.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/astrbot/core/message/components.py b/astrbot/core/message/components.py index d9ea6aa26..6311681cd 100644 --- a/astrbot/core/message/components.py +++ b/astrbot/core/message/components.py @@ -96,10 +96,10 @@ class Plain(BaseMessageComponent): def __init__(self, text: str, convert: bool = True, **_) -> None: super().__init__(text=text, convert=convert, **_) - def toDict(self): - return {"type": "text", "data": {"text": self.text.strip()}} + def toDict(self) -> dict: + return {"type": "text", "data": {"text": self.text}} - async def to_dict(self): + async def to_dict(self) -> dict: return {"type": "text", "data": {"text": self.text}} From 326183a3fd5037b43d17ae8cf1ddb84fa895bed3 Mon Sep 17 00:00:00 2001 From: DroidKali <21024784+DroidKali@users.noreply.github.com> Date: Sat, 14 Mar 2026 18:20:48 +0800 Subject: [PATCH 21/62] fix: update startup command to 'astrbot run' in all README files (#6189) Updated the quick start command from 'astrbot' to 'astrbot run' across all language versions of README documentation for consistency and correctness. Co-authored-by: DroidKali Co-authored-by: Qwen-Coder --- README.md | 2 +- README_fr.md | 2 +- README_ja.md | 2 +- README_ru.md | 2 +- README_zh-TW.md | 2 +- README_zh.md | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 8ad697131..7b56c756a 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,7 @@ For users who want to quickly experience AstrBot, are familiar with command-line ```bash uv tool install astrbot astrbot init # Only execute this command for the first time to initialize the environment -astrbot +astrbot run ``` > Requires [uv](https://docs.astral.sh/uv/) to be installed. diff --git a/README_fr.md b/README_fr.md index e406d32b2..98e7f9955 100644 --- a/README_fr.md +++ b/README_fr.md @@ -78,7 +78,7 @@ Pour les utilisateurs qui veulent découvrir AstrBot rapidement, qui sont famili ```bash uv tool install astrbot astrbot init # Exécutez cette commande uniquement la première fois pour initialiser l'environnement -astrbot +astrbot run ``` > [uv](https://docs.astral.sh/uv/) doit être installé. diff --git a/README_ja.md b/README_ja.md index 7aa146c13..2b7c43d48 100644 --- a/README_ja.md +++ b/README_ja.md @@ -78,7 +78,7 @@ AstrBot を素早く試したいユーザーで、コマンドラインに慣れ ```bash uv tool install astrbot astrbot init # 初回のみ実行して環境を初期化します -astrbot +astrbot run ``` > [uv](https://docs.astral.sh/uv/) のインストールが必要です。 diff --git a/README_ru.md b/README_ru.md index 35da14acb..29d077b45 100644 --- a/README_ru.md +++ b/README_ru.md @@ -78,7 +78,7 @@ AstrBot — это универсальная платформа Agent-чатб ```bash uv tool install astrbot astrbot init # Выполните эту команду только при первом запуске для инициализации окружения -astrbot +astrbot run ``` > Требуется установленный [uv](https://docs.astral.sh/uv/). diff --git a/README_zh-TW.md b/README_zh-TW.md index 1ace852b8..20749a077 100644 --- a/README_zh-TW.md +++ b/README_zh-TW.md @@ -78,7 +78,7 @@ AstrBot 是一個開源的一站式 Agent 聊天機器人平台,可接入主 ```bash uv tool install astrbot astrbot init # 僅首次執行此命令以初始化環境 -astrbot +astrbot run ``` > 需要安裝 [uv](https://docs.astral.sh/uv/)。 diff --git a/README_zh.md b/README_zh.md index e13d9b4e5..1e7c6b7f3 100644 --- a/README_zh.md +++ b/README_zh.md @@ -78,7 +78,7 @@ AstrBot 是一个开源的一站式 Agentic 个人和群聊助手,可在 QQ、 ```bash uv tool install astrbot astrbot init # 仅首次执行此命令以初始化环境 -astrbot +astrbot run ``` > 需要安装 [uv](https://docs.astral.sh/uv/)。 From 1a03180643dbc335e59a7a86535bd0a64cf676a8 Mon Sep 17 00:00:00 2001 From: Ann-Holmes <41296458+Ann-Holmes@users.noreply.github.com> Date: Sat, 14 Mar 2026 18:21:47 +0800 Subject: [PATCH 22/62] Add binding for local temp directory in YAML (#6191) * Add binding for local temp directory in YAML Bind the local temp directory to the sandbox for file access. * Update compose-with-shipyard.yml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> --------- Co-authored-by: Soulter <37870767+Soulter@users.noreply.github.com> Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> --- compose-with-shipyard.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/compose-with-shipyard.yml b/compose-with-shipyard.yml index 338f7b86f..24ced5a95 100644 --- a/compose-with-shipyard.yml +++ b/compose-with-shipyard.yml @@ -37,6 +37,7 @@ services: - DEFAULT_SHIP_MEMORY=512m volumes: - ${PWD}/data/shipyard/bay_data:/app/data + - ${PWD}/data/temp:/AstrBot/data/temp # Bind the local temp directory to the sandbox so that the uploaded file can be accessed in the sandbox - /var/run/docker.sock:/var/run/docker.sock:ro networks: - astrbot_network From 86ef758a9a13731661795fe9f2ef4aba9f16d34b Mon Sep 17 00:00:00 2001 From: eason <85663565+mango766@users.noreply.github.com> Date: Sat, 14 Mar 2026 18:22:14 +0800 Subject: [PATCH 23/62] fix: prevent ValueError when removing already-removed API key in retry loop (#6193) In _handle_api_error(), when a 429 rate-limit is encountered, the code calls available_api_keys.remove(chosen_key). If the same key was already removed in a previous retry iteration (e.g. the key rotated back to the same value), this raises ValueError which crashes the entire LLM request with an opaque error instead of a proper retry/fallback. Add a membership check before calling remove() to prevent the crash. Co-authored-by: easonysliu --- astrbot/core/provider/sources/openai_source.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/astrbot/core/provider/sources/openai_source.py b/astrbot/core/provider/sources/openai_source.py index adee24073..c40234ed4 100644 --- a/astrbot/core/provider/sources/openai_source.py +++ b/astrbot/core/provider/sources/openai_source.py @@ -629,7 +629,8 @@ class ProviderOpenAIOfficial(Provider): # 最后一次不等待 if retry_cnt < max_retries - 1: await asyncio.sleep(1) - available_api_keys.remove(chosen_key) + if chosen_key in available_api_keys: + available_api_keys.remove(chosen_key) if len(available_api_keys) > 0: chosen_key = random.choice(available_api_keys) return ( From f8d075b5d3c040a1eff1bf3bd6ed00ef25f8f009 Mon Sep 17 00:00:00 2001 From: Sakari <20642596+sakarie9@users.noreply.github.com> Date: Sat, 14 Mar 2026 18:27:13 +0800 Subject: [PATCH 24/62] fix(telegram): avoid treating normal replies as topic threads (#6174) --- astrbot/core/platform/sources/telegram/tg_adapter.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/astrbot/core/platform/sources/telegram/tg_adapter.py b/astrbot/core/platform/sources/telegram/tg_adapter.py index 2dd72bd0c..87e21391e 100644 --- a/astrbot/core/platform/sources/telegram/tg_adapter.py +++ b/astrbot/core/platform/sources/telegram/tg_adapter.py @@ -289,8 +289,8 @@ class TelegramPlatformAdapter(Platform): else: message.type = MessageType.GROUP_MESSAGE message.group_id = str(update.message.chat.id) - if update.message.message_thread_id: - # Topic Group + if update.message.is_topic_message and update.message.message_thread_id: + # Telegram Topic Group: include thread id to isolate per-topic sessions. message.group_id += "#" + str(update.message.message_thread_id) message.session_id = message.group_id message.message_id = str(update.message.message_id) From 91933bbd19e88b1c174ff2c0158a7d4062674813 Mon Sep 17 00:00:00 2001 From: Anima Date: Sat, 14 Mar 2026 20:45:55 +0800 Subject: [PATCH 25/62] perf: webui theme color improvement (#6263) * fix: update scrollbar styles to follow theme variables * fix: update theme colors to use CSS variables for consistency * fix: change login button color to primary for better visibility * fix: update theme colors for Dark and Light themes; change login button color to secondary * fix: update border and theme colors for consistency in DarkTheme * fix: update sidebar list class to conditionally hide scrollbar in mini sidebar mode * fix: simplify button visibility logic and remove unnecessary leftPadding style * fix: refactor language switcher to use grouped menu for better UX * fix: update theme colors to use primary color for consistency across components * fix: add preview text for template output in multiple languages --- dashboard/src/components/chat/ChatInput.vue | 22 +-- .../components/chat/ConversationSidebar.vue | 170 ++++++++++++++---- .../chat/message_list_comps/RefNode.vue | 13 +- .../components/shared/LanguageSwitcher.vue | 9 +- dashboard/src/components/shared/Logo.vue | 5 +- .../src/components/shared/StyledMenu.vue | 32 ++-- .../src/i18n/locales/en-US/core/shared.json | 1 + .../src/i18n/locales/ru-RU/core/shared.json | 1 + .../src/i18n/locales/zh-CN/core/shared.json | 1 + .../full/vertical-header/VerticalHeader.vue | 90 +++++++--- .../full/vertical-sidebar/VerticalSidebar.vue | 2 +- .../src/scss/components/_VScrollbar.scss | 81 +++------ dashboard/src/scss/layout/_sidebar.scss | 15 +- dashboard/src/theme/DarkTheme.ts | 18 +- dashboard/src/theme/LightTheme.ts | 8 +- .../views/authentication/auth/LoginPage.vue | 4 +- 16 files changed, 288 insertions(+), 184 deletions(-) diff --git a/dashboard/src/components/chat/ChatInput.vue b/dashboard/src/components/chat/ChatInput.vue index 87cc82c66..d2d9c4b88 100644 --- a/dashboard/src/components/chat/ChatInput.vue +++ b/dashboard/src/components/chat/ChatInput.vue @@ -15,7 +15,7 @@
- mdi-cloud-upload + mdi-cloud-upload {{ tm('input.dropToUpload') }}
@@ -41,7 +41,7 @@ @@ -87,7 +87,7 @@ {{ tm('voice.liveMode') }}
--> - @@ -95,13 +95,13 @@ {{ isRecording ? tm('voice.speaking') : tm('voice.startRecording') }} - + {{ tm('input.stopGenerating') }} -
@@ -117,7 +117,7 @@
- + {{ tm('voice.recording') }} @@ -126,7 +126,7 @@
- + {{ file.original_name }} @@ -399,8 +399,8 @@ defineExpose({ left: 0; right: 0; bottom: 0; - background-color: rgba(103, 58, 183, 0.15); - border: 2px dashed rgba(103, 58, 183, 0.5); + background-color: rgba(var(--v-theme-primary), 0.12); + border: 2px dashed rgba(var(--v-theme-primary), 0.45); border-radius: 24px; display: flex; align-items: center; @@ -419,7 +419,7 @@ defineExpose({ .drop-text { font-size: 16px; font-weight: 500; - color: #673ab7; + color: rgb(var(--v-theme-primary)); } /* Fade transition for drop overlay */ @@ -439,7 +439,7 @@ defineExpose({ justify-content: space-between; padding: 8px 16px; margin: 8px 8px 0 8px; - background-color: rgba(103, 58, 183, 0.06); + background-color: rgba(var(--v-theme-primary), 0.06); border-radius: 12px; gap: 8px; max-height: 500px; diff --git a/dashboard/src/components/chat/ConversationSidebar.vue b/dashboard/src/components/chat/ConversationSidebar.vue index 01a5fccfb..37dee4041 100644 --- a/dashboard/src/components/chat/ConversationSidebar.vue +++ b/dashboard/src/components/chat/ConversationSidebar.vue @@ -5,7 +5,7 @@ 'mobile-sidebar-open': isMobile && mobileMenuOpen, 'mobile-sidebar': isMobile }" - :style="{ 'background-color': isDark ? sidebarCollapsed ? '#1e1e1e' : '#2d2d2d' : sidebarCollapsed ? '#ffffff' : '#f1f4f9' }"> + :style="{ backgroundColor: sidebarCollapsed && !isMobile ? 'rgb(var(--v-theme-surface))' : 'rgb(var(--v-theme-mcpCardBg))' }">