feat: 添加插件能针对不同消息平台开启关闭的功能
Squashed: chore: merge master branch chore: merge from master branch chore: rename updateAllPlatformCompatibility to update_all_platform_compatibility for consistency Reviewed by: @Raven95676 @Soulter
This commit is contained in:
@@ -7,6 +7,7 @@ from .waking_check.stage import WakingCheckStage
|
||||
from .whitelist_check.stage import WhitelistCheckStage
|
||||
from .rate_limit_check.stage import RateLimitStage
|
||||
from .content_safety_check.stage import ContentSafetyCheckStage
|
||||
from .platform_compatibility.stage import PlatformCompatibilityStage
|
||||
from .preprocess_stage.stage import PreProcessStage
|
||||
from .process_stage.stage import ProcessStage
|
||||
from .result_decorate.stage import ResultDecorateStage
|
||||
@@ -18,6 +19,7 @@ STAGES_ORDER = [
|
||||
"WhitelistCheckStage", # 检查是否在群聊/私聊白名单
|
||||
"RateLimitStage", # 检查会话是否超过频率限制
|
||||
"ContentSafetyCheckStage", # 检查内容安全
|
||||
"PlatformCompatibilityStage", # 检查所有处理器的平台兼容性
|
||||
"PreProcessStage", # 预处理
|
||||
"ProcessStage", # 交由 Stars 处理(a.k.a 插件),或者 LLM 调用
|
||||
"ResultDecorateStage", # 处理结果,比如添加回复前缀、t2i、转换为语音 等
|
||||
@@ -29,6 +31,7 @@ __all__ = [
|
||||
"WhitelistCheckStage",
|
||||
"RateLimitStage",
|
||||
"ContentSafetyCheckStage",
|
||||
"PlatformCompatibilityStage",
|
||||
"PreProcessStage",
|
||||
"ProcessStage",
|
||||
"ResultDecorateStage",
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
from ..stage import Stage, register_stage
|
||||
from ..context import PipelineContext
|
||||
from typing import Union, AsyncGenerator
|
||||
from astrbot.core.platform.astr_message_event import AstrMessageEvent
|
||||
from astrbot.core.star.star import star_map
|
||||
from astrbot.core import logger
|
||||
|
||||
|
||||
@register_stage
|
||||
class PlatformCompatibilityStage(Stage):
|
||||
"""检查所有处理器的平台兼容性。
|
||||
|
||||
这个阶段会检查所有处理器是否在当前平台启用,如果未启用则设置platform_compatible属性为False。
|
||||
"""
|
||||
|
||||
async def initialize(self, ctx: PipelineContext) -> None:
|
||||
"""初始化平台兼容性检查阶段
|
||||
|
||||
Args:
|
||||
ctx (PipelineContext): 消息管道上下文对象, 包括配置和插件管理器
|
||||
"""
|
||||
self.ctx = ctx
|
||||
|
||||
async def process(
|
||||
self, event: AstrMessageEvent
|
||||
) -> Union[None, AsyncGenerator[None, None]]:
|
||||
# 获取当前平台ID
|
||||
platform_id = event.get_platform_id()
|
||||
|
||||
# 获取已激活的处理器
|
||||
activated_handlers = event.get_extra("activated_handlers")
|
||||
if activated_handlers is None:
|
||||
activated_handlers = []
|
||||
|
||||
# 标记不兼容的处理器
|
||||
for handler in activated_handlers:
|
||||
# 检查处理器是否在当前平台启用
|
||||
enabled = handler.is_enabled_for_platform(platform_id)
|
||||
if not enabled:
|
||||
if handler.handler_module_path in star_map:
|
||||
plugin_name = star_map[handler.handler_module_path].name
|
||||
logger.debug(
|
||||
f"[PlatformCompatibilityStage] 插件 {plugin_name} 在平台 {platform_id} 未启用,标记处理器 {handler.handler_name} 为平台不兼容"
|
||||
)
|
||||
# 设置处理器为平台不兼容状态
|
||||
handler.platform_compatible = False
|
||||
else:
|
||||
# 确保处理器为平台兼容状态
|
||||
handler.platform_compatible = True
|
||||
|
||||
# 更新已激活的处理器列表
|
||||
event.set_extra("activated_handlers", activated_handlers)
|
||||
@@ -108,8 +108,10 @@ class LLMRequestSubStage(Stage):
|
||||
|
||||
# 执行请求 LLM 前事件钩子。
|
||||
# 装饰 system_prompt 等功能
|
||||
# 获取当前平台ID
|
||||
platform_id = event.get_platform_id()
|
||||
handlers = star_handlers_registry.get_handlers_by_event_type(
|
||||
EventType.OnLLMRequestEvent
|
||||
EventType.OnLLMRequestEvent, platform_id=platform_id
|
||||
)
|
||||
for handler in handlers:
|
||||
try:
|
||||
@@ -350,6 +352,8 @@ class LLMRequestSubStage(Stage):
|
||||
llm_response.tools_call_args,
|
||||
llm_response.tools_call_ids,
|
||||
):
|
||||
|
||||
|
||||
try:
|
||||
func_tool = req.func_tool.get_func(func_tool_name)
|
||||
if func_tool.origin == "mcp":
|
||||
@@ -368,6 +372,15 @@ class LLMRequestSubStage(Stage):
|
||||
)
|
||||
)
|
||||
else:
|
||||
# 获取处理器,过滤掉平台不兼容的处理器
|
||||
platform_id = event.get_platform_id()
|
||||
if not func_tool.handler.is_enabled_for_platform(platform_id):
|
||||
logger.debug(
|
||||
f"处理器 {func_tool_name} 在当前平台不兼容,跳过执行"
|
||||
)
|
||||
# 直接跳过,不添加任何消息到tool_call_result
|
||||
continue
|
||||
|
||||
logger.info(
|
||||
f"调用工具函数:{func_tool_name},参数:{func_tool_args}"
|
||||
)
|
||||
|
||||
@@ -31,7 +31,18 @@ class StarRequestSubStage(Stage):
|
||||
)
|
||||
if not handlers_parsed_params:
|
||||
handlers_parsed_params = {}
|
||||
|
||||
for handler in activated_handlers:
|
||||
# 检查处理器是否在当前平台兼容
|
||||
if (
|
||||
hasattr(handler, "platform_compatible")
|
||||
and handler.platform_compatible is False
|
||||
):
|
||||
logger.debug(
|
||||
f"处理器 {handler.handler_name} 在当前平台不兼容,跳过执行"
|
||||
)
|
||||
continue
|
||||
|
||||
params = handlers_parsed_params.get(handler.handler_full_name, {})
|
||||
try:
|
||||
if handler.handler_module_path not in star_map:
|
||||
|
||||
@@ -198,7 +198,7 @@ class RespondStage(Stage):
|
||||
)
|
||||
|
||||
handlers = star_handlers_registry.get_handlers_by_event_type(
|
||||
EventType.OnAfterMessageSentEvent
|
||||
EventType.OnAfterMessageSentEvent, platform_id=event.get_platform_id()
|
||||
)
|
||||
for handler in handlers:
|
||||
try:
|
||||
|
||||
@@ -96,7 +96,7 @@ class ResultDecorateStage(Stage):
|
||||
|
||||
# 发送消息前事件钩子
|
||||
handlers = star_handlers_registry.get_handlers_by_event_type(
|
||||
EventType.OnDecoratingResultEvent
|
||||
EventType.OnDecoratingResultEvent, platform_id=event.get_platform_id()
|
||||
)
|
||||
for handler in handlers:
|
||||
try:
|
||||
|
||||
@@ -81,6 +81,9 @@ class AstrMessageEvent(abc.ABC):
|
||||
def get_platform_name(self):
|
||||
return self.platform_meta.name
|
||||
|
||||
def get_platform_id(self):
|
||||
return self.platform_meta.id
|
||||
|
||||
def get_message_str(self) -> str:
|
||||
"""
|
||||
获取消息字符串。
|
||||
|
||||
@@ -7,6 +7,8 @@ class PlatformMetadata:
|
||||
"""平台的名称"""
|
||||
description: str
|
||||
"""平台的描述"""
|
||||
id: str = None
|
||||
"""平台的唯一标识符,用于配置中识别特定平台"""
|
||||
|
||||
default_config_tmpl: dict = None
|
||||
"""平台的默认配置模板"""
|
||||
|
||||
@@ -39,8 +39,9 @@ class AiocqhttpAdapter(Platform):
|
||||
self.port = platform_config["ws_reverse_port"]
|
||||
|
||||
self.metadata = PlatformMetadata(
|
||||
"aiocqhttp",
|
||||
"适用于 OneBot 标准的消息平台适配器,支持反向 WebSockets。",
|
||||
name="aiocqhttp",
|
||||
description="适用于 OneBot 标准的消息平台适配器,支持反向 WebSockets。",
|
||||
id=self.config.get("id"),
|
||||
)
|
||||
|
||||
self.bot = CQHttp(
|
||||
|
||||
@@ -73,8 +73,9 @@ class DingtalkPlatformAdapter(Platform):
|
||||
|
||||
def meta(self) -> PlatformMetadata:
|
||||
return PlatformMetadata(
|
||||
"dingtalk",
|
||||
"钉钉机器人官方 API 适配器",
|
||||
name="dingtalk",
|
||||
description="钉钉机器人官方 API 适配器",
|
||||
id=self.config.get("id"),
|
||||
)
|
||||
|
||||
async def convert_msg(
|
||||
|
||||
@@ -60,8 +60,9 @@ class GewechatPlatformAdapter(Platform):
|
||||
@override
|
||||
def meta(self) -> PlatformMetadata:
|
||||
return PlatformMetadata(
|
||||
"gewechat",
|
||||
"基于 gewechat 的 Wechat 适配器",
|
||||
name="gewechat",
|
||||
description="基于 gewechat 的 Wechat 适配器",
|
||||
id=self.config.get("id"),
|
||||
)
|
||||
|
||||
async def terminate(self):
|
||||
|
||||
@@ -70,8 +70,9 @@ class LarkPlatformAdapter(Platform):
|
||||
|
||||
def meta(self) -> PlatformMetadata:
|
||||
return PlatformMetadata(
|
||||
"lark",
|
||||
"飞书机器人官方 API 适配器",
|
||||
name="lark",
|
||||
description="飞书机器人官方 API 适配器",
|
||||
id=self.config.get("id"),
|
||||
)
|
||||
|
||||
async def convert_msg(self, event: lark.im.v1.P2ImMessageReceiveV1):
|
||||
|
||||
@@ -126,8 +126,9 @@ class QQOfficialPlatformAdapter(Platform):
|
||||
|
||||
def meta(self) -> PlatformMetadata:
|
||||
return PlatformMetadata(
|
||||
"qq_official",
|
||||
"QQ 机器人官方 API 适配器",
|
||||
name="qq_official",
|
||||
description="QQ 机器人官方 API 适配器",
|
||||
id=self.config.get("id"),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -99,8 +99,9 @@ class QQOfficialWebhookPlatformAdapter(Platform):
|
||||
|
||||
def meta(self) -> PlatformMetadata:
|
||||
return PlatformMetadata(
|
||||
"qq_official_webhook",
|
||||
"QQ 机器人官方 API 适配器",
|
||||
name="qq_official_webhook",
|
||||
description="QQ 机器人官方 API 适配器",
|
||||
id=self.config.get("id"),
|
||||
)
|
||||
|
||||
async def run(self):
|
||||
|
||||
@@ -80,8 +80,7 @@ class TelegramPlatformAdapter(Platform):
|
||||
@override
|
||||
def meta(self) -> PlatformMetadata:
|
||||
return PlatformMetadata(
|
||||
"telegram",
|
||||
"telegram 适配器",
|
||||
name="telegram", description="telegram 适配器", id=self.config.get("id")
|
||||
)
|
||||
|
||||
@override
|
||||
|
||||
@@ -43,8 +43,7 @@ class WebChatAdapter(Platform):
|
||||
self.imgs_dir = "data/webchat/imgs"
|
||||
|
||||
self.metadata = PlatformMetadata(
|
||||
"webchat",
|
||||
"webchat",
|
||||
name="webchat", description="webchat", id=self.config.get("id")
|
||||
)
|
||||
|
||||
async def send_by_session(
|
||||
|
||||
Regular → Executable
Regular → Executable
@@ -47,5 +47,29 @@ class StarMetadata:
|
||||
star_handler_full_names: List[str] = field(default_factory=list)
|
||||
"""注册的 Handler 的全名列表"""
|
||||
|
||||
supported_platforms: Dict[str, bool] = field(default_factory=dict)
|
||||
"""插件支持的平台ID字典,key为平台ID,value为是否支持"""
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"StarMetadata({self.name}, {self.desc}, {self.version}, {self.repo})"
|
||||
|
||||
def update_platform_compatibility(self, plugin_enable_config: dict) -> None:
|
||||
"""更新插件支持的平台列表
|
||||
|
||||
Args:
|
||||
plugin_enable_config: 平台插件启用配置,即platform_settings.plugin_enable配置项
|
||||
"""
|
||||
if not plugin_enable_config:
|
||||
return
|
||||
|
||||
# 清空之前的配置
|
||||
self.supported_platforms.clear()
|
||||
|
||||
# 遍历所有平台配置
|
||||
for platform_id, plugins in plugin_enable_config.items():
|
||||
# 检查该插件在当前平台的配置
|
||||
if self.name in plugins:
|
||||
self.supported_platforms[platform_id] = plugins[self.name]
|
||||
else:
|
||||
# 如果没有明确配置,默认为启用
|
||||
self.supported_platforms[platform_id] = True
|
||||
|
||||
@@ -30,21 +30,36 @@ class StarHandlerRegistry(Generic[T]):
|
||||
print(handler.handler_full_name)
|
||||
|
||||
def get_handlers_by_event_type(
|
||||
self, event_type: EventType, only_activated=True
|
||||
self, event_type: EventType, only_activated=True, platform_id=None
|
||||
) -> List[StarHandlerMetadata]:
|
||||
"""通过事件类型获取 Handler"""
|
||||
handlers = [
|
||||
handler
|
||||
for _, handler in self._handlers
|
||||
if handler.event_type == event_type
|
||||
and (
|
||||
not only_activated
|
||||
or (
|
||||
star_map[handler.handler_module_path]
|
||||
and star_map[handler.handler_module_path].activated
|
||||
)
|
||||
)
|
||||
]
|
||||
"""通过事件类型获取 Handler
|
||||
|
||||
Args:
|
||||
event_type: 事件类型
|
||||
only_activated: 是否只返回已激活的插件的处理器
|
||||
platform_id: 平台ID,如果提供此参数,将过滤掉在此平台不兼容的处理器
|
||||
|
||||
Returns:
|
||||
List[StarHandlerMetadata]: 处理器列表
|
||||
"""
|
||||
handlers = []
|
||||
for _, handler in self._handlers:
|
||||
if handler.event_type != event_type:
|
||||
continue
|
||||
|
||||
# 只激活的插件处理器
|
||||
if only_activated:
|
||||
plugin = star_map.get(handler.handler_module_path)
|
||||
if not (plugin and plugin.activated):
|
||||
continue
|
||||
|
||||
# 平台兼容性过滤
|
||||
if platform_id and event_type != EventType.OnAstrBotLoadedEvent:
|
||||
if not handler.is_enabled_for_platform(platform_id):
|
||||
continue
|
||||
|
||||
handlers.append(handler)
|
||||
|
||||
return handlers
|
||||
|
||||
def get_handler_by_full_name(self, full_name: str) -> StarHandlerMetadata:
|
||||
@@ -139,3 +154,32 @@ class StarHandlerMetadata:
|
||||
return self.extras_configs.get("priority", 0) < other.extras_configs.get(
|
||||
"priority", 0
|
||||
)
|
||||
|
||||
def is_enabled_for_platform(self, platform_id: str) -> bool:
|
||||
"""检查插件是否在指定平台启用
|
||||
|
||||
Args:
|
||||
platform_id: 平台ID,这是从event.get_platform_id()获取的,用于唯一标识平台实例
|
||||
|
||||
Returns:
|
||||
bool: 是否启用,True表示启用,False表示禁用
|
||||
"""
|
||||
plugin = star_map.get(self.handler_module_path)
|
||||
|
||||
# 如果插件元数据不存在,默认允许执行
|
||||
if not plugin or not plugin.name:
|
||||
return True
|
||||
|
||||
# 先检查插件是否被激活
|
||||
if not plugin.activated:
|
||||
return False
|
||||
|
||||
# 直接使用StarMetadata中缓存的supported_platforms判断平台兼容性
|
||||
if (
|
||||
hasattr(plugin, "supported_platforms")
|
||||
and platform_id in plugin.supported_platforms
|
||||
):
|
||||
return plugin.supported_platforms[platform_id]
|
||||
|
||||
# 如果没有缓存数据,默认允许执行
|
||||
return True
|
||||
|
||||
@@ -209,7 +209,31 @@ class PluginManager:
|
||||
|
||||
await self._unbind_plugin(smd.name, specified_module_path)
|
||||
|
||||
return await self.load(specified_module_path)
|
||||
result = await self.load(specified_module_path)
|
||||
|
||||
# 更新所有插件的平台兼容性
|
||||
await self.update_all_platform_compatibility()
|
||||
|
||||
return result
|
||||
|
||||
async def update_all_platform_compatibility(self):
|
||||
"""更新所有插件的平台兼容性设置"""
|
||||
# 获取最新的平台插件启用配置
|
||||
plugin_enable_config = self.config.get("platform_settings", {}).get(
|
||||
"plugin_enable", {}
|
||||
)
|
||||
logger.debug(
|
||||
f"更新所有插件的平台兼容性设置,平台数量: {len(plugin_enable_config)}"
|
||||
)
|
||||
|
||||
# 遍历所有插件,更新平台兼容性
|
||||
for plugin in self.context.get_all_stars():
|
||||
plugin.update_platform_compatibility(plugin_enable_config)
|
||||
logger.debug(
|
||||
f"插件 {plugin.name} 支持的平台: {list(plugin.supported_platforms.keys())}"
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
async def load(self, specified_module_path=None, specified_dir_name=None):
|
||||
"""载入插件。
|
||||
@@ -320,6 +344,12 @@ class PluginManager:
|
||||
metadata.root_dir_name = root_dir_name
|
||||
metadata.reserved = reserved
|
||||
|
||||
# 更新插件的平台兼容性
|
||||
plugin_enable_config = self.config.get("platform_settings", {}).get(
|
||||
"plugin_enable", {}
|
||||
)
|
||||
metadata.update_platform_compatibility(plugin_enable_config)
|
||||
|
||||
# 绑定 handler
|
||||
related_handlers = (
|
||||
star_handlers_registry.get_handlers_by_module_name(
|
||||
|
||||
@@ -38,6 +38,8 @@ class PluginRoute(Route):
|
||||
"/plugin/on": ("POST", self.on_plugin),
|
||||
"/plugin/reload": ("POST", self.reload_plugins),
|
||||
"/plugin/readme": ("GET", self.get_plugin_readme),
|
||||
"/plugin/platform_enable/get": ("GET", self.get_plugin_platform_enable),
|
||||
"/plugin/platform_enable/set": ("POST", self.set_plugin_platform_enable),
|
||||
}
|
||||
self.core_lifecycle = core_lifecycle
|
||||
self.plugin_manager = plugin_manager
|
||||
@@ -323,38 +325,131 @@ class PluginRoute(Route):
|
||||
async def get_plugin_readme(self):
|
||||
plugin_name = request.args.get("name")
|
||||
logger.debug(f"正在获取插件 {plugin_name} 的README文件内容")
|
||||
|
||||
|
||||
if not plugin_name:
|
||||
logger.warning("插件名称为空")
|
||||
return Response().error("插件名称不能为空").__dict__
|
||||
|
||||
|
||||
plugin_obj = None
|
||||
for plugin in self.plugin_manager.context.get_all_stars():
|
||||
if plugin.name == plugin_name:
|
||||
plugin_obj = plugin
|
||||
break
|
||||
|
||||
|
||||
if not plugin_obj:
|
||||
logger.warning(f"插件 {plugin_name} 不存在")
|
||||
return Response().error(f"插件 {plugin_name} 不存在").__dict__
|
||||
|
||||
plugin_dir = os.path.join(self.plugin_manager.plugin_store_path, plugin_obj.root_dir_name)
|
||||
|
||||
|
||||
plugin_dir = os.path.join(
|
||||
self.plugin_manager.plugin_store_path, plugin_obj.root_dir_name
|
||||
)
|
||||
|
||||
if not os.path.isdir(plugin_dir):
|
||||
logger.warning(f"无法找到插件目录: {plugin_dir}")
|
||||
return Response().error(f"无法找到插件 {plugin_name} 的目录").__dict__
|
||||
|
||||
|
||||
readme_path = os.path.join(plugin_dir, "README.md")
|
||||
|
||||
|
||||
if not os.path.isfile(readme_path):
|
||||
logger.warning(f"插件 {plugin_name} 没有README文件")
|
||||
return Response().error(f"插件 {plugin_name} 没有README文件").__dict__
|
||||
|
||||
|
||||
try:
|
||||
with open(readme_path, 'r', encoding='utf-8') as f:
|
||||
with open(readme_path, "r", encoding="utf-8") as f:
|
||||
readme_content = f.read()
|
||||
|
||||
return Response().ok({"content": readme_content}, "成功获取README内容").__dict__
|
||||
|
||||
return (
|
||||
Response()
|
||||
.ok({"content": readme_content}, "成功获取README内容")
|
||||
.__dict__
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"/api/plugin/readme: {traceback.format_exc()}")
|
||||
return Response().error(f"读取README文件失败: {str(e)}").__dict__
|
||||
|
||||
async def get_plugin_platform_enable(self):
|
||||
"""获取插件在各平台的可用性配置"""
|
||||
try:
|
||||
platform_enable = self.core_lifecycle.astrbot_config.get(
|
||||
"platform_settings", {}
|
||||
).get("plugin_enable", {})
|
||||
|
||||
# 获取所有可用平台
|
||||
platforms = []
|
||||
|
||||
for platform in self.core_lifecycle.astrbot_config.get("platform", []):
|
||||
platform_type = platform.get("type", "")
|
||||
platform_id = platform.get("id", "")
|
||||
|
||||
platforms.append(
|
||||
{
|
||||
"name": platform_id, # 使用type作为name,这是系统内部使用的平台名称
|
||||
"id": platform_id, # 保留id字段以便前端可以显示
|
||||
"type": platform_type,
|
||||
"display_name": f"{platform_type}({platform_id})",
|
||||
}
|
||||
)
|
||||
|
||||
adjusted_platform_enable = {}
|
||||
for platform_id, plugins in platform_enable.items():
|
||||
adjusted_platform_enable[platform_id] = plugins
|
||||
|
||||
# 获取所有插件,包括系统内部插件
|
||||
plugins = []
|
||||
for plugin in self.plugin_manager.context.get_all_stars():
|
||||
plugins.append(
|
||||
{
|
||||
"name": plugin.name,
|
||||
"desc": plugin.desc,
|
||||
"reserved": plugin.reserved, # 添加reserved标志
|
||||
}
|
||||
)
|
||||
|
||||
logger.debug(
|
||||
f"获取插件平台配置: 原始配置={platform_enable}, 调整后={adjusted_platform_enable}"
|
||||
)
|
||||
|
||||
return (
|
||||
Response()
|
||||
.ok(
|
||||
{
|
||||
"platforms": platforms,
|
||||
"plugins": plugins,
|
||||
"platform_enable": adjusted_platform_enable,
|
||||
}
|
||||
)
|
||||
.__dict__
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"/api/plugin/platform_enable/get: {traceback.format_exc()}")
|
||||
return Response().error(str(e)).__dict__
|
||||
|
||||
async def set_plugin_platform_enable(self):
|
||||
"""设置插件在各平台的可用性配置"""
|
||||
if DEMO_MODE:
|
||||
return (
|
||||
Response()
|
||||
.error("You are not permitted to do this operation in demo mode")
|
||||
.__dict__
|
||||
)
|
||||
|
||||
try:
|
||||
data = await request.json
|
||||
platform_enable = data.get("platform_enable", {})
|
||||
|
||||
# 更新配置
|
||||
config = self.core_lifecycle.astrbot_config
|
||||
platform_settings = config.get("platform_settings", {})
|
||||
platform_settings["plugin_enable"] = platform_enable
|
||||
config["platform_settings"] = platform_settings
|
||||
config.save_config()
|
||||
|
||||
# 更新插件的平台兼容性缓存
|
||||
await self.plugin_manager.update_all_platform_compatibility()
|
||||
|
||||
logger.info(f"插件平台可用性配置已更新: {platform_enable}")
|
||||
|
||||
return Response().ok(None, "插件平台可用性配置已更新").__dict__
|
||||
except Exception as e:
|
||||
logger.error(f"/api/plugin/platform_enable/set: {traceback.format_exc()}")
|
||||
return Response().error(str(e)).__dict__
|
||||
|
||||
@@ -41,6 +41,14 @@ const readmeDialog = reactive({
|
||||
pluginName: '',
|
||||
repoUrl: null
|
||||
});
|
||||
// 平台插件配置
|
||||
const platformEnableDialog = ref(false);
|
||||
const platformEnableData = reactive({
|
||||
platforms: [],
|
||||
plugins: [],
|
||||
platform_enable: {}
|
||||
});
|
||||
const loadingPlatformData = ref(false);
|
||||
|
||||
const plugin_handler_info_headers = [
|
||||
{ title: '行为类型', key: 'event_type_h' },
|
||||
@@ -238,6 +246,101 @@ const viewReadme = (plugin) => {
|
||||
readmeDialog.show = true;
|
||||
};
|
||||
|
||||
// 获取插件平台可用性配置
|
||||
const getPlatformEnableConfig = async () => {
|
||||
loadingPlatformData.value = true;
|
||||
try {
|
||||
const res = await axios.get('/api/plugin/platform_enable/get');
|
||||
if (res.data.status === "error") {
|
||||
toast(res.data.message, "error");
|
||||
return;
|
||||
}
|
||||
|
||||
platformEnableData.platforms = res.data.data.platforms;
|
||||
platformEnableData.plugins = res.data.data.plugins;
|
||||
platformEnableData.platform_enable = res.data.data.platform_enable;
|
||||
|
||||
// 如果没有平台,给出提示但仍显示对话框
|
||||
if (platformEnableData.platforms.length === 0) {
|
||||
toast("未添加任何平台适配器,请先在平台管理中添加平台", "warning");
|
||||
} else {
|
||||
// 确保每个平台都有一个配置对象
|
||||
platformEnableData.platforms.forEach(platform => {
|
||||
if (!platformEnableData.platform_enable[platform.name]) {
|
||||
platformEnableData.platform_enable[platform.name] = {};
|
||||
}
|
||||
|
||||
// 确保每个插件在每个平台都有一个配置项
|
||||
platformEnableData.plugins.forEach(plugin => {
|
||||
if (platformEnableData.platform_enable[platform.name][plugin.name] === undefined) {
|
||||
platformEnableData.platform_enable[platform.name][plugin.name] = true; // 默认启用
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
platformEnableDialog.value = true;
|
||||
} catch (err) {
|
||||
toast("获取平台插件配置失败: " + err, "error");
|
||||
} finally {
|
||||
loadingPlatformData.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 保存插件平台可用性配置
|
||||
const savePlatformEnableConfig = async () => {
|
||||
loadingPlatformData.value = true;
|
||||
try {
|
||||
const res = await axios.post('/api/plugin/platform_enable/set', {
|
||||
platform_enable: platformEnableData.platform_enable
|
||||
});
|
||||
|
||||
if (res.data.status === "error") {
|
||||
toast(res.data.message, "error");
|
||||
return;
|
||||
}
|
||||
|
||||
toast(res.data.message, "success");
|
||||
platformEnableDialog.value = false;
|
||||
} catch (err) {
|
||||
toast("保存平台插件配置失败: " + err, "error");
|
||||
} finally {
|
||||
loadingPlatformData.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 全选指定平台的所有插件
|
||||
const selectAllPluginsForPlatform = (platformName, isSelected, onlyReserved = null) => {
|
||||
// 确保平台存在于platform_enable中
|
||||
if (!platformEnableData.platform_enable[platformName]) {
|
||||
platformEnableData.platform_enable[platformName] = {};
|
||||
}
|
||||
|
||||
// 为所有插件设置相同的状态
|
||||
platformEnableData.plugins.forEach(plugin => {
|
||||
// 如果onlyReserved为null,处理所有插件
|
||||
// 如果onlyReserved为true,只处理系统插件
|
||||
// 如果onlyReserved为false,只处理非系统插件
|
||||
if (onlyReserved === null || plugin.reserved === onlyReserved) {
|
||||
platformEnableData.platform_enable[platformName][plugin.name] = isSelected;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 反选指定平台的所有插件
|
||||
const toggleAllPluginsForPlatform = (platformName) => {
|
||||
// 确保平台存在于platform_enable中
|
||||
if (!platformEnableData.platform_enable[platformName]) {
|
||||
platformEnableData.platform_enable[platformName] = {};
|
||||
}
|
||||
|
||||
// 对每个插件进行反选操作
|
||||
platformEnableData.plugins.forEach(plugin => {
|
||||
const currentState = platformEnableData.platform_enable[platformName][plugin.name];
|
||||
platformEnableData.platform_enable[platformName][plugin.name] = !currentState;
|
||||
});
|
||||
};
|
||||
|
||||
// 生命周期
|
||||
onMounted(async () => {
|
||||
await getExtensions();
|
||||
@@ -261,6 +364,9 @@ onMounted(async () => {
|
||||
<v-btn class="text-none ml-2" size="small" variant="flat" border @click="toggleShowReserved">
|
||||
{{ showReserved ? '隐藏系统保留插件' : '显示系统保留插件' }}
|
||||
</v-btn>
|
||||
<v-btn class="text-none ml-2" size="small" variant="flat" color="primary" border @click="getPlatformEnableConfig">
|
||||
平台命令配置
|
||||
</v-btn>
|
||||
<v-dialog max-width="500px" v-if="extension_data.message">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn v-bind="props" icon size="small" color="error" style="margin-left: auto;" variant="plain">
|
||||
@@ -298,6 +404,105 @@ onMounted(async () => {
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<!-- 插件平台配置对话框 -->
|
||||
<v-dialog v-model="platformEnableDialog" max-width="800" persistent>
|
||||
<v-card>
|
||||
<v-card-title>
|
||||
<span class="headline">平台命令可用性配置</span>
|
||||
</v-card-title>
|
||||
<v-card-subtitle>
|
||||
设置每个插件在不同平台上的可用性,勾选表示启用
|
||||
</v-card-subtitle>
|
||||
<v-card-text>
|
||||
<v-overlay
|
||||
:model-value="loadingPlatformData"
|
||||
class="align-center justify-center"
|
||||
persistent
|
||||
>
|
||||
<v-progress-circular
|
||||
color="primary"
|
||||
indeterminate
|
||||
size="64"
|
||||
></v-progress-circular>
|
||||
</v-overlay>
|
||||
|
||||
<div v-if="platformEnableData.platforms.length === 0" class="text-center pa-4">
|
||||
<v-icon icon="mdi-alert" color="warning" size="64" class="mb-4"></v-icon>
|
||||
<div class="text-h6 mb-2">未找到平台适配器</div>
|
||||
<div class="text-body-1 mb-4">请先在 <strong>平台管理</strong> 中添加并配置平台适配器,然后再设置插件的平台可用性</div>
|
||||
<v-btn color="primary" to="/platforms">前往平台管理</v-btn>
|
||||
</div>
|
||||
|
||||
<v-table v-else>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>插件名称</th>
|
||||
<th v-for="platform in platformEnableData.platforms" :key="platform.name">
|
||||
<div class="d-flex align-center">
|
||||
{{ platform.display_name }}
|
||||
<v-menu>
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn
|
||||
icon
|
||||
density="compact"
|
||||
variant="text"
|
||||
size="small"
|
||||
v-bind="props"
|
||||
class="ms-1"
|
||||
>
|
||||
<v-icon>mdi-dots-vertical</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-list>
|
||||
<v-list-item @click="selectAllPluginsForPlatform(platform.name, true)">
|
||||
<v-list-item-title>全选</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item @click="selectAllPluginsForPlatform(platform.name, true, false)">
|
||||
<v-list-item-title>全选普通插件</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item @click="selectAllPluginsForPlatform(platform.name, true, true)">
|
||||
<v-list-item-title>全选系统插件</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item @click="selectAllPluginsForPlatform(platform.name, false)">
|
||||
<v-list-item-title>全不选</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item @click="toggleAllPluginsForPlatform(platform.name)">
|
||||
<v-list-item-title>反选</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="plugin in platformEnableData.plugins" :key="plugin.name">
|
||||
<td>
|
||||
<div class="d-flex align-center">
|
||||
{{ plugin.name }}
|
||||
<v-chip v-if="plugin.reserved" color="primary" size="x-small" class="ml-2">系统</v-chip>
|
||||
</div>
|
||||
<div class="text-caption text-grey">{{ plugin.desc }}</div>
|
||||
</td>
|
||||
<td v-for="platform in platformEnableData.platforms" :key="platform.name">
|
||||
<v-checkbox
|
||||
v-model="platformEnableData.platform_enable[platform.name][plugin.name]"
|
||||
hide-details
|
||||
density="compact"
|
||||
></v-checkbox>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</v-table>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="grey" text @click="platformEnableDialog = false">关闭</v-btn>
|
||||
<v-btn v-if="platformEnableData.platforms.length > 0" color="primary" @click="savePlatformEnableConfig">保存</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- 配置对话框 -->
|
||||
<v-dialog v-model="configDialog" width="1000">
|
||||
<v-card>
|
||||
@@ -385,4 +590,13 @@ onMounted(async () => {
|
||||
:plugin-name="readmeDialog.pluginName"
|
||||
:repo-url="readmeDialog.repoUrl"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.plugin-handler-item {
|
||||
margin-bottom: 10px;
|
||||
padding: 5px;
|
||||
border-radius: 5px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user