From 1b7e4fbbdc732a26113bfc7a997dcbfcb59215e4 Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Wed, 8 Jan 2025 12:43:34 +0800 Subject: [PATCH 1/4] =?UTF-8?q?perf:=20=E9=80=80=E5=87=BA=E6=97=B6?= =?UTF-8?q?=E5=85=B3=E9=97=AD=20aiohttp=20client=20session?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/core_lifecycle.py | 2 ++ astrbot/core/provider/manager.py | 7 ++++++- astrbot/core/provider/sources/dify_source.py | 3 +++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/astrbot/core/core_lifecycle.py b/astrbot/core/core_lifecycle.py index 3655e55ee..ddb285cc9 100644 --- a/astrbot/core/core_lifecycle.py +++ b/astrbot/core/core_lifecycle.py @@ -92,6 +92,8 @@ class AstrBotCoreLifecycle: self.event_queue.closed = True for task in self.curr_tasks: task.cancel() + + await self.provider_manager.terminate() for task in self.curr_tasks: try: diff --git a/astrbot/core/provider/manager.py b/astrbot/core/provider/manager.py index 20ac8a0d0..ea90470db 100644 --- a/astrbot/core/provider/manager.py +++ b/astrbot/core/provider/manager.py @@ -65,4 +65,9 @@ class ProviderManager(): logger.warning("未启用任何大模型提供商适配器。") def get_insts(self): - return self.provider_insts \ No newline at end of file + return self.provider_insts + + async def terminate(self): + for provider_inst in self.provider_insts: + if hasattr(provider_inst, "terminate"): + await provider_inst.terminate() \ No newline at end of file diff --git a/astrbot/core/provider/sources/dify_source.py b/astrbot/core/provider/sources/dify_source.py index 64755c968..4ee1897e6 100644 --- a/astrbot/core/provider/sources/dify_source.py +++ b/astrbot/core/provider/sources/dify_source.py @@ -127,3 +127,6 @@ class ProviderDify(Provider): async def get_human_readable_context(self, session_id, page, page_size): raise Exception("暂不支持获得 Dify 的历史消息记录。") + + async def terminate(self): + await self.api_client.close() \ No newline at end of file From e6b06f914baa4a87213385bbae1bbe0390ebf876 Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Wed, 8 Jan 2025 20:46:34 +0800 Subject: [PATCH 2/4] =?UTF-8?q?perf:=20provider=20=E5=81=8F=E5=A5=BD?= =?UTF-8?q?=E9=A1=B9=E8=AE=B0=E5=BF=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/api/__init__.py | 2 ++ astrbot/core/__init__.py | 4 ++- astrbot/core/provider/manager.py | 16 +++++++----- astrbot/core/utils/shared_preferences.py | 33 ++++++++++++++++++++++++ packages/astrbot/main.py | 14 ++++++---- 5 files changed, 57 insertions(+), 12 deletions(-) create mode 100644 astrbot/core/utils/shared_preferences.py diff --git a/astrbot/api/__init__.py b/astrbot/api/__init__.py index e75cde6ff..dd2e0fbc7 100644 --- a/astrbot/api/__init__.py +++ b/astrbot/api/__init__.py @@ -2,6 +2,7 @@ from astrbot.core.config.astrbot_config import AstrBotConfig from astrbot import logger from astrbot.core.utils.personality import personalities from astrbot.core import html_renderer +from astrbot.core import sp from astrbot.core.star.register import register_llm_tool as llm_tool __all__ = [ @@ -10,4 +11,5 @@ __all__ = [ "personalities", "html_renderer", "llm_tool", + "sp" ] \ No newline at end of file diff --git a/astrbot/core/__init__.py b/astrbot/core/__init__.py index 94e32224c..f7876a78a 100644 --- a/astrbot/core/__init__.py +++ b/astrbot/core/__init__.py @@ -1,6 +1,7 @@ import os from .log import LogManager, LogBroker from astrbot.core.utils.t2i.renderer import HtmlRenderer +from astrbot.core.utils.shared_preferences import SharedPreferences from astrbot.core.db.sqlite import SQLiteDatabase from astrbot.core.config.default import DB_PATH @@ -13,4 +14,5 @@ if os.environ.get('TESTING', ""): logger.setLevel('DEBUG') db_helper = SQLiteDatabase(DB_PATH) -WEBUI_SK = "Advanced_System_for_Text_Response_and_Bot_Operations_Tool" \ No newline at end of file +sp = SharedPreferences() # 简单的偏好设置存储 +WEBUI_SK = "Advanced_System_for_Text_Response_and_Bot_Operations_Tool" diff --git a/astrbot/core/provider/manager.py b/astrbot/core/provider/manager.py index ea90470db..d72b4b5aa 100644 --- a/astrbot/core/provider/manager.py +++ b/astrbot/core/provider/manager.py @@ -5,7 +5,7 @@ from typing import List from astrbot.core.db import BaseDatabase from collections import defaultdict from .register import provider_cls_map, llm_tools -from astrbot.core import logger +from astrbot.core import logger, sp class ProviderManager(): def __init__(self, config: AstrBotConfig, db_helper: BaseDatabase): @@ -48,21 +48,25 @@ class ProviderManager(): if not provider_config['enable']: continue if provider_config['type'] not in provider_cls_map: - logger.error(f"未找到适用于 {provider_config['type']}({provider_config['id']}) 的 大模型提供商适配器,请检查是否已经安装或者名称填写错误。已跳过。") + logger.error(f"未找到适用于 {provider_config['type']}({provider_config['id']}) 的提供商适配器,请检查是否已经安装或者名称填写错误。已跳过。") continue + selected_provider_id = sp.get("curr_provider") cls_type = provider_cls_map[provider_config['type']] - logger.info(f"尝试实例化 {provider_config['type']}({provider_config['id']}) 大模型提供商适配器 ...") + logger.info(f"尝试实例化 {provider_config['type']}({provider_config['id']}) 提供商适配器 ...") try: inst = cls_type(provider_config, self.provider_settings, self.db_helper, self.provider_settings.get('persistant_history', True)) self.provider_insts.append(inst) + if selected_provider_id == provider_config['id']: + self.curr_provider_inst = inst + logger.info(f"已选择 {provider_config['type']}({provider_config['id']}) 作为当前提供商适配器。") except Exception as e: traceback.print_exc() - logger.error(f"实例化 {provider_config['type']}({provider_config['id']}) 大模型提供商适配器 失败:{e}") + logger.error(f"实例化 {provider_config['type']}({provider_config['id']}) 提供商适配器失败:{e}") - if len(self.provider_insts) > 0: + if len(self.provider_insts) > 0 and not self.curr_provider_inst: self.curr_provider_inst = self.provider_insts[0] else: - logger.warning("未启用任何大模型提供商适配器。") + logger.warning("未启用任何提供商适配器。") def get_insts(self): return self.provider_insts diff --git a/astrbot/core/utils/shared_preferences.py b/astrbot/core/utils/shared_preferences.py new file mode 100644 index 000000000..e9a9ed0d7 --- /dev/null +++ b/astrbot/core/utils/shared_preferences.py @@ -0,0 +1,33 @@ +import json +import os + +class SharedPreferences: + def __init__(self, path="data/shared_preferences.json"): + self.path = path + self._data = self._load_preferences() + + def _load_preferences(self): + if os.path.exists(self.path): + with open(self.path, "r") as f: + return json.load(f) + return {} + + def _save_preferences(self): + with open(self.path, "w") as f: + json.dump(self._data, f, indent=4) + + def get(self, key, default=None): + return self._data.get(key, default) + + def put(self, key, value): + self._data[key] = value + self._save_preferences() + + def remove(self, key): + if key in self._data: + del self._data[key] + self._save_preferences() + + def clear(self): + self._data.clear() + self._save_preferences() \ No newline at end of file diff --git a/packages/astrbot/main.py b/packages/astrbot/main.py index 0af740faf..c81fa8f88 100644 --- a/packages/astrbot/main.py +++ b/packages/astrbot/main.py @@ -3,7 +3,7 @@ import datetime import astrbot.api.star as star import astrbot.api.event.filter as filter from astrbot.api.event import AstrMessageEvent, MessageEventResult -from astrbot.api import personalities +from astrbot.api import personalities, sp from astrbot.api.provider import Personality, ProviderRequest from typing import Union @@ -169,8 +169,9 @@ UID: {user_id} 此 ID 可用于设置管理员。/op 授权管理员, /deo if idx is None: ret = "## 当前载入的 LLM 提供商\n" for idx, llm in enumerate(self.context.get_all_providers()): - ret += f"{idx + 1}. {llm.meta().id} ({llm.meta().model})" - if self.provider == llm: + id_ = llm.meta().id + ret += f"{idx + 1}. {id_} ({llm.meta().model})" + if self.context.get_using_provider().meta().id == id_: ret += " (当前使用)" ret += "\n" @@ -180,9 +181,12 @@ UID: {user_id} 此 ID 可用于设置管理员。/op 授权管理员, /deo if idx > len(self.context.get_all_providers()) or idx < 1: event.set_result(MessageEventResult().message("无效的序号。")) - self.context.provider_manager.curr_provider_inst = self.context.get_all_providers()[idx - 1] + provider = self.context.get_all_providers()[idx - 1] + id_ = provider.meta().id + self.context.provider_manager.curr_provider_inst = provider + sp.put("curr_provider", id_) - event.set_result(MessageEventResult().message(f"成功切换到 {self.context.provider_manager.curr_provider_inst.meta().id}。")) + event.set_result(MessageEventResult().message(f"成功切换到 {id_}。")) @filter.command("reset") async def reset(self, message: AstrMessageEvent): From 8dc8c5b5dc56dbb38f266358e3ffbef4ae7e6756 Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Wed, 8 Jan 2025 22:28:20 +0800 Subject: [PATCH 3/4] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E5=AF=B9?= =?UTF-8?q?=E6=8F=92=E4=BB=B6=E7=A6=81=E7=94=A8/=E5=90=AF=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/provider/func_tool_manager.py | 1 + astrbot/core/provider/manager.py | 3 +- astrbot/core/star/context.py | 14 +++++ astrbot/core/star/star.py | 3 + astrbot/core/star/star_handler.py | 14 ++++- astrbot/core/star/star_manager.py | 68 +++++++++++++++++++--- astrbot/core/utils/io.py | 2 +- packages/astrbot/main.py | 35 ++++++++--- 8 files changed, 120 insertions(+), 20 deletions(-) diff --git a/astrbot/core/provider/func_tool_manager.py b/astrbot/core/provider/func_tool_manager.py index 07c672daf..f1d2f7b28 100644 --- a/astrbot/core/provider/func_tool_manager.py +++ b/astrbot/core/provider/func_tool_manager.py @@ -14,6 +14,7 @@ class FuncTool: parameters: Dict description: str handler: Awaitable + handler_module_path: str = None # 必须要保留这个,handler 在初始化会被 functools.partial 包装,导致 handler 的 __module__ 为 functools active: bool = True '''是否激活''' diff --git a/astrbot/core/provider/manager.py b/astrbot/core/provider/manager.py index d72b4b5aa..e020148d6 100644 --- a/astrbot/core/provider/manager.py +++ b/astrbot/core/provider/manager.py @@ -65,7 +65,8 @@ class ProviderManager(): if len(self.provider_insts) > 0 and not self.curr_provider_inst: self.curr_provider_inst = self.provider_insts[0] - else: + + if not self.curr_provider_inst: logger.warning("未启用任何提供商适配器。") def get_insts(self): diff --git a/astrbot/core/star/context.py b/astrbot/core/star/context.py index 39ed5baf6..58810de96 100644 --- a/astrbot/core/star/context.py +++ b/astrbot/core/star/context.py @@ -1,6 +1,7 @@ from asyncio import Queue from typing import List, TypedDict, Union +from astrbot.core import sp from astrbot.core.provider.provider import Provider from astrbot.core.db import BaseDatabase from astrbot.core.config.astrbot_config import AstrBotConfig @@ -39,6 +40,7 @@ class Context: # back compatibility _register_tasks: List[Awaitable] = [] + _star_manager = None def __init__(self, event_queue: Queue, @@ -105,6 +107,12 @@ class Context: func_tool = self.provider_manager.llm_tools.get_func(name) if func_tool is not None: func_tool.active = True + + inactivated_llm_tools: list = sp.get("inactivated_llm_tools", []) + if name in inactivated_llm_tools: + inactivated_llm_tools.remove(name) + sp.put("inactivated_llm_tools", inactivated_llm_tools) + return True return False @@ -116,6 +124,12 @@ class Context: func_tool = self.provider_manager.llm_tools.get_func(name) if func_tool is not None: func_tool.active = False + + inactivated_llm_tools: list = sp.get("inactivated_llm_tools", []) + if name not in inactivated_llm_tools: + inactivated_llm_tools.append(name) + sp.put("inactivated_llm_tools", inactivated_llm_tools) + return True return False diff --git a/astrbot/core/star/star.py b/astrbot/core/star/star.py index cee93c5e6..960504f29 100644 --- a/astrbot/core/star/star.py +++ b/astrbot/core/star/star.py @@ -32,6 +32,9 @@ class StarMetadata: '''Star 的根目录名''' reserved: bool = False '''是否是 AstrBot 的保留 Star''' + + activated: bool = True + '''是否被激活''' def __str__(self) -> str: return f"StarMetadata({self.name}, {self.desc}, {self.version}, {self.repo})" \ No newline at end of file diff --git a/astrbot/core/star/star_handler.py b/astrbot/core/star/star_handler.py index 9acfa56e0..e0e13db40 100644 --- a/astrbot/core/star/star_handler.py +++ b/astrbot/core/star/star_handler.py @@ -3,6 +3,7 @@ import enum from dataclasses import dataclass from typing import Awaitable, List, Dict, TypeVar, Generic from .filter import HandlerFilter +from .star import star_map T = TypeVar('T', bound='StarHandlerMetadata') class StarHandlerRegistry(Generic[T], List[T]): @@ -16,9 +17,18 @@ class StarHandlerRegistry(Generic[T], List[T]): super().append(handler) self.star_handlers_map[handler.handler_full_name] = handler - def get_handlers_by_event_type(self, event_type: EventType) -> List[StarHandlerMetadata]: + def get_handlers_by_event_type(self, event_type: EventType, only_activated = True) -> List[StarHandlerMetadata]: '''通过事件类型获取 Handler''' - return [handler for handler in self if handler.event_type == event_type] + if only_activated: + return [ + handler + for handler in self + if handler.event_type == event_type and + star_map[handler.handler_module_path] and + star_map[handler.handler_module_path].activated + ] + else: + return [handler for handler in self if handler.event_type == event_type] def get_handler_by_full_name(self, full_name: str) -> StarHandlerMetadata: '''通过 Handler 的全名获取 Handler''' diff --git a/astrbot/core/star/star_manager.py b/astrbot/core/star/star_manager.py index 1a49f9826..264a6f5c8 100644 --- a/astrbot/core/star/star_manager.py +++ b/astrbot/core/star/star_manager.py @@ -9,7 +9,7 @@ from types import ModuleType from typing import List from pip import main as pip_main from astrbot.core.config.astrbot_config import AstrBotConfig -from astrbot.core import logger +from astrbot.core import logger, sp from .context import Context from . import StarMetadata from .updator import PluginUpdator @@ -27,6 +27,7 @@ class PluginManager: self.updator = PluginUpdator(config['plugin_repo_mirror']) self.context = context + self.context._star_manager = self # 就这样吧,不想改了 self.config = config self.plugin_store_path = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../../data/plugins")) @@ -156,6 +157,9 @@ class PluginManager: return False, "未找到任何插件模块" fail_rec = "" + inactivated_plugins: list = sp.get("inactivated_plugins", []) + inactivated_llm_tools: list = sp.get("inactivated_llm_tools", []) + # 导入 Star 模块,并尝试实例化 Star 类 for plugin_module in plugin_modules: try: @@ -196,7 +200,10 @@ class PluginManager: # llm_tool for func_tool in llm_tools.func_list: if func_tool.handler.__module__ == star_metadata.module_path: + func_tool.handler_module_path = star_metadata.module_path func_tool.handler = functools.partial(func_tool.handler, star_metadata.star_cls) + if func_tool.name in inactivated_llm_tools: + func_tool.active = False else: # v3.4.0 以前的方式注册插件 @@ -221,6 +228,9 @@ class PluginManager: star_registry.append(metadata) logger.debug(f"插件 {root_dir_name} 载入成功。") + if metadata.module_path in inactivated_plugins: + metadata.activated = False + except BaseException as e: traceback.print_exc() fail_rec += f"加载 {path} 插件时出现问题,原因 {str(e)}\n" @@ -250,22 +260,25 @@ class PluginManager: ppath = self.plugin_store_path # 从 star_registry 和 star_map 中删除 - del star_map[plugin.module_path] + await self._unbind_plugin(plugin_name, plugin.module_path) + + if not remove_dir(os.path.join(ppath, root_dir_name)): + raise Exception("移除插件成功,但是删除插件文件夹失败。您可以手动删除该文件夹,位于 addons/plugins/ 下。") + + async def _unbind_plugin(self, plugin_name: str, plugin_module_path: str): + del star_map[plugin_module_path] for i, p in enumerate(star_registry): if p.name == plugin_name: del star_registry[i] break - for handler in star_handlers_registry.get_handlers_by_module_name(plugin.module_path): + for handler in star_handlers_registry.get_handlers_by_module_name(plugin_module_path): logger.debug(f"unbind handler {handler.handler_name} from {plugin_name}") star_handlers_registry.remove(handler) - keys_to_delete = [k for k, v in star_handlers_registry.star_handlers_map.items() if k.startswith(plugin.module_path)] + keys_to_delete = [k for k, v in star_handlers_registry.star_handlers_map.items() if k.startswith(plugin_module_path)] for k in keys_to_delete: v = star_handlers_registry.star_handlers_map[k] logger.debug(f"unbind handler {v.handler_name} from {plugin_name} (map)") del star_handlers_registry.star_handlers_map[k] - - if not remove_dir(os.path.join(ppath, root_dir_name)): - raise Exception("移除插件成功,但是删除插件文件夹失败。您可以手动删除该文件夹,位于 addons/plugins/ 下。") async def update_plugin(self, plugin_name: str): plugin = self.context.get_registered_star(plugin_name) @@ -276,6 +289,47 @@ class PluginManager: await self.updator.update(plugin) self.reload() + + async def turn_off_plugin(self, plugin_name: str): + plugin = self.context.get_registered_star(plugin_name) + if not plugin: + raise Exception("插件不存在。") + inactivated_plugins: list = sp.get("inactivated_plugins", []) + if plugin.module_path not in inactivated_plugins: + inactivated_plugins.append(plugin.module_path) + + inactivated_llm_tools: list = sp.get("inactivated_llm_tools", []) + + # 禁用插件启用的 llm_tool + for func_tool in llm_tools.func_list: + if func_tool.handler_module_path == plugin.module_path: + func_tool.active = False + inactivated_llm_tools.append(func_tool.name) + + sp.put("inactivated_plugins", inactivated_plugins) + sp.put("inactivated_llm_tools", inactivated_llm_tools) + + plugin.activated = False + + async def turn_on_plugin(self, plugin_name: str): + plugin = self.context.get_registered_star(plugin_name) + if not plugin: + raise Exception("插件已经启用,无需重新启用。") + inactivated_plugins: list = sp.get("inactivated_plugins", []) + inactivated_llm_tools: list = sp.get("inactivated_llm_tools", []) + if plugin.module_path in inactivated_plugins: + inactivated_plugins.remove(plugin.module_path) + sp.put("inactivated_plugins", inactivated_plugins) + + # 启用插件启用的 llm_tool + for func_tool in llm_tools.func_list: + if func_tool.handler_module_path == plugin.module_path: + inactivated_llm_tools.remove(func_tool.name) + func_tool.active = True + sp.put("inactivated_llm_tools", inactivated_llm_tools) + + plugin.activated = True + def install_plugin_from_file(self, zip_file_path: str): desti_dir = os.path.join(self.plugin_store_path, os.path.basename(zip_file_path)) diff --git a/astrbot/core/utils/io.py b/astrbot/core/utils/io.py index 3c0a6a926..bbc3341bb 100644 --- a/astrbot/core/utils/io.py +++ b/astrbot/core/utils/io.py @@ -96,7 +96,7 @@ async def download_file(url: str, path: str): ''' try: async with aiohttp.ClientSession() as session: - async with session.get(url) as resp: + async with session.get(url, timeout=20) as resp: with open(path, 'wb') as f: while True: chunk = await resp.content.read(8192) diff --git a/packages/astrbot/main.py b/packages/astrbot/main.py index c81fa8f88..fa5161b7c 100644 --- a/packages/astrbot/main.py +++ b/packages/astrbot/main.py @@ -89,24 +89,41 @@ class Main(star.Star): event.set_result(MessageEventResult().message(f"停用工具 {tool_name} 失败,未找到此工具。")) @filter.command("plugin") - async def plugin(self, event: AstrMessageEvent, oper: str = None): - if oper is None: + async def plugin(self, event: AstrMessageEvent, oper1: str = None, oper2: str = None): + if oper1 is None: plugin_list_info = "已加载的插件:\n" for plugin in self.context.get_all_stars(): plugin_list_info += f"- `{plugin.name}` By {plugin.author}: {plugin.desc}\n" if plugin_list_info.strip() == "": plugin_list_info = "没有加载任何插件。" - plugin_list_info += "\n使用 /plugin <插件名> 查看插件帮助。" + plugin_list_info += "\n使用 /plugin <插件名> 查看插件帮助。\n使用 /plugin on/off <插件名> 启用或者禁用插件。" event.set_result(MessageEventResult().message(f"{plugin_list_info}").use_t2i(False)) else: - plugin = self.context.get_registered_star(oper) - if plugin is None: - event.set_result(MessageEventResult().message("未找到此插件。")) + if oper1 == "off": + # 禁用插件 + if oper2 is None: + event.set_result(MessageEventResult().message("/plugin off <插件名> 禁用插件。")) + return + await self.context._star_manager.turn_off_plugin(oper2) + event.set_result(MessageEventResult().message(f"插件 {oper2} 已禁用。")) + elif oper1 == "on": + # 启用插件 + if oper2 is None: + event.set_result(MessageEventResult().message("/plugin on <插件名> 启用插件。")) + return + await self.context._star_manager.turn_on_plugin(oper2) + event.set_result(MessageEventResult().message(f"插件 {oper2} 已启用。")) + else: - help_msg = plugin.star_cls.__doc__ if plugin.star_cls.__doc__ else "该插件未提供帮助信息" - ret = f"插件 {oper} 帮助信息:\n" + help_msg - event.set_result(MessageEventResult().message(ret).use_t2i(False)) + # 获取插件帮助 + plugin = self.context.get_registered_star(oper1) + if plugin is None: + event.set_result(MessageEventResult().message("未找到此插件。")) + else: + help_msg = plugin.star_cls.__doc__ if plugin.star_cls.__doc__ else "该插件未提供帮助信息" + ret = f"插件 {oper1} 帮助信息:\n" + help_msg + event.set_result(MessageEventResult().message(ret).use_t2i(False)) @filter.command("t2i") async def t2i(self, event: AstrMessageEvent): From d3d428dc9d1342cad2627d3a3e10d539f1cc5698 Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Wed, 8 Jan 2025 23:04:03 +0800 Subject: [PATCH 4/4] =?UTF-8?q?fix:=20=E7=AE=A1=E7=90=86=E9=9D=A2=E6=9D=BF?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E7=A6=81=E7=94=A8/=E5=90=AF=E7=94=A8?= =?UTF-8?q?=E6=8F=92=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/star/star_manager.py | 24 +++++++++---------- astrbot/dashboard/routes/plugin.py | 29 +++++++++++++++++++++-- dashboard/src/views/ExtensionPage.vue | 34 ++++++++++++++++++++++++++- 3 files changed, 72 insertions(+), 15 deletions(-) diff --git a/astrbot/core/star/star_manager.py b/astrbot/core/star/star_manager.py index 264a6f5c8..8d33eb3ab 100644 --- a/astrbot/core/star/star_manager.py +++ b/astrbot/core/star/star_manager.py @@ -186,22 +186,22 @@ class PluginManager: if path in star_map: # 通过装饰器的方式注册插件 - star_metadata = star_map[path] - star_metadata.star_cls = star_metadata.star_cls_type(context=self.context) - star_metadata.module = module - star_metadata.root_dir_name = root_dir_name - star_metadata.reserved = reserved + metadata = star_map[path] + metadata.star_cls = metadata.star_cls_type(context=self.context) + metadata.module = module + metadata.root_dir_name = root_dir_name + metadata.reserved = reserved - related_handlers = star_handlers_registry.get_handlers_by_module_name(star_metadata.module_path) + related_handlers = star_handlers_registry.get_handlers_by_module_name(metadata.module_path) for handler in related_handlers: - logger.debug(f"bind handler {handler.handler_name} to {star_metadata.name}") + logger.debug(f"bind handler {handler.handler_name} to {metadata.name}") # handler.handler.__self__ = star_metadata.star_cls # 绑定 handler 的 self - handler.handler = functools.partial(handler.handler, star_metadata.star_cls) + handler.handler = functools.partial(handler.handler, metadata.star_cls) # llm_tool for func_tool in llm_tools.func_list: - if func_tool.handler.__module__ == star_metadata.module_path: - func_tool.handler_module_path = star_metadata.module_path - func_tool.handler = functools.partial(func_tool.handler, star_metadata.star_cls) + if func_tool.handler.__module__ == metadata.module_path: + func_tool.handler_module_path = metadata.module_path + func_tool.handler = functools.partial(func_tool.handler, metadata.star_cls) if func_tool.name in inactivated_llm_tools: func_tool.active = False @@ -227,7 +227,7 @@ class PluginManager: star_map[path] = metadata star_registry.append(metadata) logger.debug(f"插件 {root_dir_name} 载入成功。") - + if metadata.module_path in inactivated_plugins: metadata.activated = False diff --git a/astrbot/dashboard/routes/plugin.py b/astrbot/dashboard/routes/plugin.py index c43ab7660..bc42d508e 100644 --- a/astrbot/dashboard/routes/plugin.py +++ b/astrbot/dashboard/routes/plugin.py @@ -16,7 +16,9 @@ class PluginRoute(Route): '/plugin/install-upload': ('POST', self.install_plugin_upload), '/plugin/update': ('POST', self.update_plugin), '/plugin/uninstall': ('POST', self.uninstall_plugin), - '/plugin/market_list': ('GET', self.get_online_plugins) + '/plugin/market_list': ('GET', self.get_online_plugins), + '/plugin/off': ('POST', self.off_plugin), + '/plugin/on': ('POST', self.on_plugin) } self.core_lifecycle = core_lifecycle self.plugin_manager = plugin_manager @@ -42,7 +44,8 @@ class PluginRoute(Route): "author": plugin.author, "desc": plugin.desc, "version": plugin.version, - "reserved": plugin.reserved + "reserved": plugin.reserved, + "activated": plugin.activated } _plugin_resp.append(_t) return Response().ok(_plugin_resp).__dict__ @@ -95,4 +98,26 @@ class PluginRoute(Route): return Response().ok(None, "更新成功。").__dict__ except Exception as e: logger.error(f"/api/extensions/update: {traceback.format_exc()}") + return Response().error(str(e)).__dict__ + + async def off_plugin(self): + post_data = await request.json + plugin_name = post_data["name"] + try: + await self.plugin_manager.turn_off_plugin(plugin_name) + logger.info(f"停用插件 {plugin_name} 。") + return Response().ok(None, "停用成功。").__dict__ + except Exception as e: + logger.error(f"/api/extensions/off: {traceback.format_exc()}") + return Response().error(str(e)).__dict__ + + async def on_plugin(self): + post_data = await request.json + plugin_name = post_data["name"] + try: + await self.plugin_manager.turn_on_plugin(plugin_name) + logger.info(f"启用插件 {plugin_name} 。") + return Response().ok(None, "启用成功。").__dict__ + except Exception as e: + logger.error(f"/api/extensions/on: {traceback.format_exc()}") return Response().error(str(e)).__dict__ \ No newline at end of file diff --git a/dashboard/src/views/ExtensionPage.vue b/dashboard/src/views/ExtensionPage.vue index b0ae0ffd8..69b6295a2 100644 --- a/dashboard/src/views/ExtensionPage.vue +++ b/dashboard/src/views/ExtensionPage.vue @@ -29,7 +29,9 @@ import axios from 'axios'; 更新 卸载 - 保留插件 + + 禁用 + 启用 @@ -329,6 +331,36 @@ export default { this.toast(err, "error"); }); }, + pluginOn(extension) { + axios.post('/api/plugin/on', + { + name: extension.name + }).then((res) => { + if (res.data.status === "error") { + this.toast(res.data.message, "error"); + return; + } + this.toast(res.data.message, "success"); + this.getExtensions(); + }).catch((err) => { + this.toast(err, "error"); + }); + }, + pluginOff(extension) { + axios.post('/api/plugin/off', + { + name: extension.name + }).then((res) => { + if (res.data.status === "error") { + this.toast(res.data.message, "error"); + return; + } + this.toast(res.data.message, "success"); + this.getExtensions(); + }).catch((err) => { + this.toast(err, "error"); + }); + }, openExtensionConfig(extension_name) { this.curr_namespace = extension_name; this.configDialog = true;