Feature: 支持在配置文件配置可用的插件组 (#2505)
* feat: 增加可用插件集合配置项 * remove: 旧版平台可用性配置 已经基于多配置文件实现。 * feat: 应用配置文件插件可用性配置 * perf: hoist if from if
This commit is contained in:
@@ -7,12 +7,15 @@ from .run_context import TContext
|
||||
class HandoffTool(FunctionTool, Generic[TContext]):
|
||||
"""Handoff tool for delegating tasks to another agent."""
|
||||
|
||||
def __init__(self, agent: Agent[TContext], parameters: dict | None = None):
|
||||
def __init__(
|
||||
self, agent: Agent[TContext], parameters: dict | None = None, **kwargs
|
||||
):
|
||||
self.agent = agent
|
||||
super().__init__(
|
||||
name=f"transfer_to_{agent.name}",
|
||||
parameters=parameters or self.default_parameters(),
|
||||
description=agent.instructions or self.default_description(agent.name),
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
def default_parameters(self) -> dict:
|
||||
|
||||
@@ -13,7 +13,6 @@ DB_PATH = os.path.join(get_astrbot_data_path(), "data_v4.db")
|
||||
DEFAULT_CONFIG = {
|
||||
"config_version": 2,
|
||||
"platform_settings": {
|
||||
"plugin_enable": {},
|
||||
"unique_session": False,
|
||||
"rate_limit": {
|
||||
"time": 60,
|
||||
@@ -53,7 +52,7 @@ DEFAULT_CONFIG = {
|
||||
"default_provider_id": "",
|
||||
"default_image_caption_provider_id": "",
|
||||
"image_caption_prompt": "Please describe the image using Chinese.",
|
||||
"provider_pool": ["all"], # "all" 表示使用所有可用的提供者
|
||||
"provider_pool": ["*"], # "*" 表示使用所有可用的提供者
|
||||
"wake_prefix": "",
|
||||
"web_search": False,
|
||||
"websearch_provider": "default",
|
||||
@@ -63,7 +62,7 @@ DEFAULT_CONFIG = {
|
||||
"identifier": False,
|
||||
"datetime_system_prompt": True,
|
||||
"default_personality": "default",
|
||||
"persona_pool": ["all"],
|
||||
"persona_pool": ["*"],
|
||||
"prompt_prefix": "",
|
||||
"max_context_length": -1,
|
||||
"dequeue_context_length": 1,
|
||||
@@ -122,6 +121,7 @@ DEFAULT_CONFIG = {
|
||||
"timezone": "Asia/Shanghai",
|
||||
"callback_api_base": "",
|
||||
"default_kb_collection": "", # 默认知识库名称
|
||||
"plugin_set": ["*"], # "*" 表示使用所有可用的插件, 空列表表示不使用任何插件
|
||||
}
|
||||
|
||||
|
||||
@@ -387,9 +387,6 @@ CONFIG_METADATA_2 = {
|
||||
"platform_settings": {
|
||||
"type": "object",
|
||||
"items": {
|
||||
"plugin_enable": {
|
||||
"invisible": True, # 隐藏插件启用配置
|
||||
},
|
||||
"unique_session": {
|
||||
"type": "bool",
|
||||
},
|
||||
@@ -2129,8 +2126,25 @@ CONFIG_METADATA_3 = {
|
||||
},
|
||||
},
|
||||
},
|
||||
"plugin_group": {
|
||||
"name": "插件配置",
|
||||
"metadata": {
|
||||
"plugin": {
|
||||
"description": "插件",
|
||||
"type": "object",
|
||||
"items": {
|
||||
"plugin_set": {
|
||||
"description": "可用插件",
|
||||
"type": "bool",
|
||||
"hint": "默认启用全部未被禁用的插件。若插件在插件页面被禁用,则此处的选择不会生效。",
|
||||
"_special": "select_plugin_set",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"ext_group": {
|
||||
"name": "扩展配置",
|
||||
"name": "扩展功能",
|
||||
"metadata": {
|
||||
"segmented_reply": {
|
||||
"description": "分段回复",
|
||||
|
||||
@@ -4,7 +4,6 @@ from astrbot.core.message.message_event_result import (
|
||||
)
|
||||
|
||||
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 .rate_limit_check.stage import RateLimitStage
|
||||
@@ -21,7 +20,6 @@ STAGES_ORDER = [
|
||||
"SessionStatusCheckStage", # 检查会话是否整体启用
|
||||
"RateLimitStage", # 检查会话是否超过频率限制
|
||||
"ContentSafetyCheckStage", # 检查内容安全
|
||||
"PlatformCompatibilityStage", # 检查所有处理器的平台兼容性
|
||||
"PreProcessStage", # 预处理
|
||||
"ProcessStage", # 交由 Stars 处理(a.k.a 插件),或者 LLM 调用
|
||||
"ResultDecorateStage", # 处理结果,比如添加回复前缀、t2i、转换为语音 等
|
||||
@@ -34,7 +32,6 @@ __all__ = [
|
||||
"SessionStatusCheckStage",
|
||||
"RateLimitStage",
|
||||
"ContentSafetyCheckStage",
|
||||
"PlatformCompatibilityStage",
|
||||
"PreProcessStage",
|
||||
"ProcessStage",
|
||||
"ResultDecorateStage",
|
||||
|
||||
@@ -77,10 +77,9 @@ async def call_event_hook(
|
||||
|
||||
Returns:
|
||||
bool: 如果事件被终止,返回 True
|
||||
"""
|
||||
platform_id = event.get_platform_id()
|
||||
# """
|
||||
handlers = star_handlers_registry.get_handlers_by_event_type(
|
||||
hook_type, platform_id=platform_id
|
||||
hook_type, plugins_name=event.plugins_name
|
||||
)
|
||||
for handler in handlers:
|
||||
try:
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
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.star.star_handler import StarHandlerMetadata
|
||||
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:
|
||||
if not isinstance(handler, StarHandlerMetadata):
|
||||
continue
|
||||
# 检查处理器是否在当前平台启用
|
||||
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} 为平台不兼容"
|
||||
)
|
||||
# 设置处理器为平台不兼容状态
|
||||
# TODO: 更好的标记方式
|
||||
handler.platform_compatible = False
|
||||
else:
|
||||
# 确保处理器为平台兼容状态
|
||||
handler.platform_compatible = True
|
||||
|
||||
# 更新已激活的处理器列表
|
||||
event.set_extra("activated_handlers", activated_handlers)
|
||||
@@ -32,6 +32,7 @@ from astrbot.core.utils.metrics import Metric
|
||||
from ...context import PipelineContext, call_event_hook, call_handler
|
||||
from ..stage import Stage
|
||||
from astrbot.core.provider.register import llm_tools
|
||||
from astrbot.core.star.star_handler import star_map
|
||||
from astrbot.core.astr_agent_context import AstrAgentContext
|
||||
|
||||
try:
|
||||
@@ -422,9 +423,20 @@ class LLMRequestSubStage(Stage):
|
||||
req.image_urls = []
|
||||
if req.func_tool:
|
||||
provider_cfg = provider.provider_config.get("modalities", ["tool_use"])
|
||||
# 如果模型不支持工具使用,但请求中包含工具列表,则清空。
|
||||
if "tool_use" not in provider_cfg:
|
||||
logger.debug(f"用户设置提供商 {provider} 不支持工具使用,清空工具列表。")
|
||||
req.func_tool = None
|
||||
# 插件可用性设置
|
||||
if event.plugins_name is not None and req.func_tool:
|
||||
new_tool_set = ToolSet()
|
||||
for tool in req.func_tool.tools:
|
||||
plugin = star_map.get(tool.handler_module_path)
|
||||
if not plugin:
|
||||
continue
|
||||
if plugin.name in event.plugins_name or plugin.reserved:
|
||||
new_tool_set.add_tool(tool)
|
||||
req.func_tool = new_tool_set
|
||||
|
||||
# run agent
|
||||
agent_runner = AgentRunner()
|
||||
|
||||
@@ -33,16 +33,6 @@ class StarRequestSubStage(Stage):
|
||||
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:
|
||||
|
||||
@@ -214,7 +214,7 @@ class RespondStage(Stage):
|
||||
)
|
||||
|
||||
handlers = star_handlers_registry.get_handlers_by_event_type(
|
||||
EventType.OnAfterMessageSentEvent, platform_id=event.get_platform_id()
|
||||
EventType.OnAfterMessageSentEvent, plugins_name=event.plugins_name
|
||||
)
|
||||
for handler in handlers:
|
||||
try:
|
||||
|
||||
@@ -99,7 +99,7 @@ class ResultDecorateStage(Stage):
|
||||
|
||||
# 发送消息前事件钩子
|
||||
handlers = star_handlers_registry.get_handlers_by_event_type(
|
||||
EventType.OnDecoratingResultEvent, platform_id=event.get_platform_id()
|
||||
EventType.OnDecoratingResultEvent, plugins_name=event.plugins_name
|
||||
)
|
||||
for handler in handlers:
|
||||
try:
|
||||
|
||||
@@ -112,8 +112,17 @@ class WakingCheckStage(Stage):
|
||||
activated_handlers = []
|
||||
handlers_parsed_params = {} # 注册了指令的 handler
|
||||
|
||||
# 将 plugins_name 设置到 event 中
|
||||
enabled_plugins_name = self.ctx.astrbot_config.get("plugin_set", ["*"])
|
||||
if enabled_plugins_name == ["*"]:
|
||||
# 如果是 *,则表示所有插件都启用
|
||||
event.plugins_name = None
|
||||
else:
|
||||
event.plugins_name = enabled_plugins_name
|
||||
logger.debug(f"enabled_plugins_name: {enabled_plugins_name}")
|
||||
|
||||
for handler in star_handlers_registry.get_handlers_by_event_type(
|
||||
EventType.AdapterMessageEvent
|
||||
EventType.AdapterMessageEvent, plugins_name=event.plugins_name
|
||||
):
|
||||
# filter 需满足 AND 逻辑关系
|
||||
passed = True
|
||||
|
||||
@@ -6,6 +6,7 @@ import uuid
|
||||
|
||||
from typing import List, Union, Optional, AsyncGenerator
|
||||
|
||||
from astrbot import logger
|
||||
from astrbot.core.db.po import Conversation
|
||||
from astrbot.core.message.components import (
|
||||
Plain,
|
||||
@@ -64,6 +65,9 @@ class AstrMessageEvent(abc.ABC):
|
||||
self.call_llm = False
|
||||
"""是否在此消息事件中禁止默认的 LLM 请求"""
|
||||
|
||||
self.plugins_name: list[str] | None = None
|
||||
"""该事件启用的插件名称列表。None 表示所有插件都启用。空列表表示没有启用任何插件。"""
|
||||
|
||||
# back_compability
|
||||
self.platform = platform_meta
|
||||
|
||||
@@ -181,6 +185,7 @@ class AstrMessageEvent(abc.ABC):
|
||||
"""
|
||||
清除额外的信息。
|
||||
"""
|
||||
logger.info(f"清除 {self.get_platform_name()} 的额外信息: {self._extras}")
|
||||
self._extras.clear()
|
||||
|
||||
def is_private_chat(self) -> bool:
|
||||
|
||||
@@ -412,7 +412,7 @@ def register_agent(
|
||||
"""
|
||||
tools_ = tools or []
|
||||
|
||||
def decorator(_):
|
||||
def decorator(awaitable: Awaitable):
|
||||
AstrAgent = Agent[AstrAgentContext]
|
||||
agent = AstrAgent(
|
||||
name=name,
|
||||
@@ -420,7 +420,9 @@ def register_agent(
|
||||
tools=tools_,
|
||||
run_hooks=run_hooks or BaseAgentRunHooks[AstrAgentContext](),
|
||||
)
|
||||
llm_tools.func_list.append(HandoffTool(agent=agent))
|
||||
handoff_tool = HandoffTool(agent=agent)
|
||||
handoff_tool.handler=awaitable
|
||||
llm_tools.func_list.append(handoff_tool)
|
||||
return RegisteringAgent(agent)
|
||||
|
||||
return decorator
|
||||
|
||||
@@ -56,32 +56,8 @@ 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"Plugin {self.name} ({self.version}) by {self.author}: {self.desc}"
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"Plugin {self.name} ({self.version}) by {self.author}: {self.desc}"
|
||||
|
||||
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
|
||||
|
||||
@@ -7,6 +7,7 @@ from .star import star_map
|
||||
|
||||
T = TypeVar("T", bound="StarHandlerMetadata")
|
||||
|
||||
|
||||
class StarHandlerRegistry(Generic[T]):
|
||||
def __init__(self):
|
||||
self.star_handlers_map: Dict[str, StarHandlerMetadata] = {}
|
||||
@@ -26,7 +27,10 @@ class StarHandlerRegistry(Generic[T]):
|
||||
print(handler.handler_full_name)
|
||||
|
||||
def get_handlers_by_event_type(
|
||||
self, event_type: EventType, only_activated=True, platform_id=None
|
||||
self,
|
||||
event_type: EventType,
|
||||
only_activated=True,
|
||||
plugins_name: list[str] | None = None,
|
||||
) -> List[StarHandlerMetadata]:
|
||||
handlers = []
|
||||
for handler in self._handlers:
|
||||
@@ -36,8 +40,15 @@ class StarHandlerRegistry(Generic[T]):
|
||||
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):
|
||||
if plugins_name is not None and plugins_name != ["*"]:
|
||||
plugin = star_map.get(handler.handler_module_path)
|
||||
if not plugin:
|
||||
continue
|
||||
if (
|
||||
plugin.name not in plugins_name
|
||||
and event_type != EventType.OnAstrBotLoadedEvent
|
||||
and not plugin.reserved
|
||||
):
|
||||
continue
|
||||
handlers.append(handler)
|
||||
return handlers
|
||||
@@ -49,7 +60,8 @@ class StarHandlerRegistry(Generic[T]):
|
||||
self, module_name: str
|
||||
) -> List[StarHandlerMetadata]:
|
||||
return [
|
||||
handler for handler in self._handlers
|
||||
handler
|
||||
for handler in self._handlers
|
||||
if handler.handler_module_path == module_name
|
||||
]
|
||||
|
||||
@@ -67,6 +79,7 @@ class StarHandlerRegistry(Generic[T]):
|
||||
def __len__(self):
|
||||
return len(self._handlers)
|
||||
|
||||
|
||||
star_handlers_registry = StarHandlerRegistry()
|
||||
|
||||
|
||||
@@ -119,32 +132,3 @@ 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
|
||||
|
||||
@@ -337,30 +337,8 @@ class PluginManager:
|
||||
|
||||
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):
|
||||
"""载入插件。
|
||||
当 specified_module_path 或者 specified_dir_name 不为 None 时,只载入指定的插件。
|
||||
@@ -480,12 +458,6 @@ 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)
|
||||
|
||||
assert metadata.module_path is not None, (
|
||||
f"插件 {metadata.name} 的模块路径为空。"
|
||||
)
|
||||
|
||||
@@ -40,8 +40,6 @@ 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
|
||||
@@ -482,90 +480,3 @@ class PluginRoute(Route):
|
||||
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__
|
||||
|
||||
@@ -5,6 +5,7 @@ import ListConfigItem from './ListConfigItem.vue'
|
||||
import ProviderSelector from './ProviderSelector.vue'
|
||||
import PersonaSelector from './PersonaSelector.vue'
|
||||
import KnowledgeBaseSelector from './KnowledgeBaseSelector.vue'
|
||||
import PluginSetSelector from './PluginSetSelector.vue'
|
||||
import { useI18n } from '@/i18n/composables'
|
||||
|
||||
|
||||
@@ -240,7 +241,34 @@ function hasVisibleItemsAfter(items, currentIndex) {
|
||||
v-model="createSelectorModel(itemKey).value"
|
||||
/>
|
||||
</div>
|
||||
<div v-else-if="itemMeta?._special === 'select_plugin_set'">
|
||||
<PluginSetSelector
|
||||
v-model="createSelectorModel(itemKey).value"
|
||||
/>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<!-- Plugin Set Selector 全宽显示区域 -->
|
||||
<v-row v-if="!itemMeta?.invisible && itemMeta?._special === 'select_plugin_set'" class="plugin-set-display-row">
|
||||
<v-col cols="12" class="plugin-set-display">
|
||||
<div v-if="createSelectorModel(itemKey).value && createSelectorModel(itemKey).value.length > 0" class="selected-plugins-full-width">
|
||||
<div class="plugins-header">
|
||||
<small class="text-grey">已选择的插件:</small>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap ga-2 mt-2">
|
||||
<v-chip
|
||||
v-for="plugin in (createSelectorModel(itemKey).value || [])"
|
||||
:key="plugin"
|
||||
size="small"
|
||||
label
|
||||
color="primary"
|
||||
variant="outlined"
|
||||
>
|
||||
{{ plugin === '*' ? '所有插件' : plugin }}
|
||||
</v-chip>
|
||||
</div>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
@@ -386,6 +414,26 @@ function hasVisibleItemsAfter(items, currentIndex) {
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.plugin-set-display-row {
|
||||
margin: 16px;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.plugin-set-display {
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.selected-plugins-full-width {
|
||||
background-color: rgba(var(--v-theme-primary), 0.05);
|
||||
border: 1px solid rgba(var(--v-theme-primary), 0.1);
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.plugins-header {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.nested-object {
|
||||
padding-left: 8px;
|
||||
|
||||
@@ -0,0 +1,226 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- 顶部操作区域 -->
|
||||
<div class="d-flex align-center justify-space-between mb-2">
|
||||
<div class="flex-grow-1">
|
||||
<span v-if="!modelValue || modelValue.length === 0" style="color: rgb(var(--v-theme-primaryText));">
|
||||
未启用任何插件
|
||||
</span>
|
||||
<span v-else-if="isAllPlugins" style="color: rgb(var(--v-theme-primaryText));">
|
||||
启用所有插件 (*)
|
||||
</span>
|
||||
<span v-else style="color: rgb(var(--v-theme-primaryText));">
|
||||
已选择 {{ modelValue.length }} 个插件
|
||||
</span>
|
||||
</div>
|
||||
<v-btn size="small" color="primary" variant="tonal" @click="openDialog">
|
||||
{{ buttonText }}
|
||||
</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Plugin Set Selection Dialog -->
|
||||
<v-dialog v-model="dialog" max-width="700px">
|
||||
<v-card>
|
||||
<v-card-title class="text-h3 py-4" style="font-weight: normal;">
|
||||
选择插件集合
|
||||
</v-card-title>
|
||||
|
||||
<v-card-text class="pa-4">
|
||||
<v-progress-linear v-if="loading" indeterminate color="primary"></v-progress-linear>
|
||||
|
||||
<div v-if="!loading">
|
||||
<!-- 预设选项 -->
|
||||
<v-radio-group v-model="selectionMode" class="mb-4" hide-details>
|
||||
<v-radio
|
||||
value="all"
|
||||
label="启用所有插件"
|
||||
color="primary"
|
||||
></v-radio>
|
||||
<v-radio
|
||||
value="none"
|
||||
label="不启用任何插件"
|
||||
color="primary"
|
||||
></v-radio>
|
||||
<v-radio
|
||||
value="custom"
|
||||
label="自定义选择"
|
||||
color="primary"
|
||||
></v-radio>
|
||||
</v-radio-group>
|
||||
|
||||
<!-- 自定义选择时显示插件列表 -->
|
||||
<div v-if="selectionMode === 'custom'" style="max-height: 300px; overflow-y: auto;">
|
||||
<v-list v-if="pluginList.length > 0" density="compact">
|
||||
<v-list-item
|
||||
v-for="plugin in pluginList"
|
||||
:key="plugin.name"
|
||||
rounded="md"
|
||||
class="ma-1">
|
||||
<template v-slot:prepend>
|
||||
<v-checkbox
|
||||
v-model="selectedPlugins"
|
||||
:value="plugin.name"
|
||||
color="primary"
|
||||
hide-details
|
||||
></v-checkbox>
|
||||
</template>
|
||||
|
||||
<v-list-item-title>{{ plugin.name }}</v-list-item-title>
|
||||
<v-list-item-subtitle>
|
||||
{{ plugin.desc || '无描述' }}
|
||||
<v-chip v-if="!plugin.activated" size="x-small" color="grey" class="ml-1">
|
||||
未激活
|
||||
</v-chip>
|
||||
</v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
|
||||
<div class="pl-8 pt-2">
|
||||
<small>*不显示系统插件和已经在插件页禁用的插件。</small>
|
||||
</div>
|
||||
</v-list>
|
||||
|
||||
<div v-else class="text-center py-8">
|
||||
<v-icon size="64" color="grey-lighten-1">mdi-puzzle-outline</v-icon>
|
||||
<p class="text-grey mt-4">暂无可用的插件</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</v-card-text>
|
||||
|
||||
<v-card-actions class="pa-4">
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn variant="text" @click="cancelSelection">取消</v-btn>
|
||||
<v-btn
|
||||
color="primary"
|
||||
@click="confirmSelection">
|
||||
确认选择
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import axios from 'axios'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
buttonText: {
|
||||
type: String,
|
||||
default: '选择插件集合...'
|
||||
},
|
||||
maxDisplayItems: {
|
||||
type: Number,
|
||||
default: 3
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
const dialog = ref(false)
|
||||
const pluginList = ref([])
|
||||
const loading = ref(false)
|
||||
const selectionMode = ref('custom') // 'all', 'none', 'custom'
|
||||
const selectedPlugins = ref([])
|
||||
|
||||
// 判断是否为"所有插件"模式
|
||||
const isAllPlugins = computed(() => {
|
||||
return props.modelValue && props.modelValue.length === 1 && props.modelValue[0] === '*'
|
||||
})
|
||||
|
||||
// 移除插件
|
||||
function removePlugin(pluginName) {
|
||||
if (props.modelValue && props.modelValue.length > 0) {
|
||||
const newValue = props.modelValue.filter(name => name !== pluginName)
|
||||
emit('update:modelValue', newValue)
|
||||
}
|
||||
}
|
||||
|
||||
// 监听 modelValue 变化,同步内部状态
|
||||
watch(() => props.modelValue, (newValue) => {
|
||||
if (!newValue || newValue.length === 0) {
|
||||
selectionMode.value = 'none'
|
||||
selectedPlugins.value = []
|
||||
} else if (newValue.length === 1 && newValue[0] === '*') {
|
||||
selectionMode.value = 'all'
|
||||
selectedPlugins.value = []
|
||||
} else {
|
||||
selectionMode.value = 'custom'
|
||||
selectedPlugins.value = [...newValue]
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
async function openDialog() {
|
||||
dialog.value = true
|
||||
await loadPlugins()
|
||||
}
|
||||
|
||||
async function loadPlugins() {
|
||||
loading.value = true
|
||||
try {
|
||||
const response = await axios.get('/api/plugin/get')
|
||||
if (response.data.status === 'ok') {
|
||||
// 只显示已激活且非系统的插件,并按名称排序
|
||||
pluginList.value = (response.data.data || [])
|
||||
.filter(plugin => plugin.activated && !plugin.reserved)
|
||||
.sort((a, b) => a.name.localeCompare(b.name))
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载插件列表失败:', error)
|
||||
pluginList.value = []
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function confirmSelection() {
|
||||
let newValue = []
|
||||
|
||||
switch (selectionMode.value) {
|
||||
case 'all':
|
||||
newValue = ['*']
|
||||
break
|
||||
case 'none':
|
||||
newValue = []
|
||||
break
|
||||
case 'custom':
|
||||
newValue = [...selectedPlugins.value]
|
||||
break
|
||||
}
|
||||
|
||||
emit('update:modelValue', newValue)
|
||||
dialog.value = false
|
||||
}
|
||||
|
||||
function cancelSelection() {
|
||||
// 恢复到原始状态
|
||||
const currentValue = props.modelValue || []
|
||||
if (currentValue.length === 0) {
|
||||
selectionMode.value = 'none'
|
||||
selectedPlugins.value = []
|
||||
} else if (currentValue.length === 1 && currentValue[0] === '*') {
|
||||
selectionMode.value = 'all'
|
||||
selectedPlugins.value = []
|
||||
} else {
|
||||
selectionMode.value = 'custom'
|
||||
selectedPlugins.value = [...currentValue]
|
||||
}
|
||||
|
||||
dialog.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.v-list-item {
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.v-list-item:hover {
|
||||
background-color: rgba(var(--v-theme-primary), 0.04);
|
||||
}
|
||||
</style>
|
||||
@@ -16,7 +16,6 @@
|
||||
"buttons": {
|
||||
"showSystemPlugins": "Show System Extensions",
|
||||
"hideSystemPlugins": "Hide System Extensions",
|
||||
"platformConfig": "Platform Command Config",
|
||||
"install": "Install",
|
||||
"uninstall": "Uninstall",
|
||||
"update": "Update",
|
||||
@@ -88,18 +87,6 @@
|
||||
"title": "Error Information",
|
||||
"checkConsole": "Please check console for details"
|
||||
},
|
||||
"platformConfig": {
|
||||
"title": "Platform Command Availability Configuration",
|
||||
"description": "Set the availability of each extension on different platforms, check to enable",
|
||||
"noAdapters": "No Platform Adapters Found",
|
||||
"noAdaptersDesc": "Please add and configure platform adapters in Platform Management first, then set extension platform availability",
|
||||
"goPlatforms": "Go to Platform Management",
|
||||
"selectAll": "Select All",
|
||||
"selectAllNormal": "Select All Normal Extensions",
|
||||
"selectAllSystem": "Select All System Extensions",
|
||||
"selectNone": "Select None",
|
||||
"toggleAll": "Toggle All"
|
||||
},
|
||||
"config": {
|
||||
"title": "Extension Configuration",
|
||||
"noConfig": "This extension has no configuration"
|
||||
@@ -137,8 +124,6 @@
|
||||
"installing": "Installing extension from file",
|
||||
"installingFromUrl": "Installing extension from URL...",
|
||||
"installFailed": "Extension installation failed:",
|
||||
"getPlatformConfigFailed": "Failed to get platform extension config:",
|
||||
"savePlatformConfigFailed": "Failed to save platform extension config:",
|
||||
"getMarketDataFailed": "Failed to get extension market data:",
|
||||
"hasUpdate": "New version available:",
|
||||
"confirmDelete": "Are you sure you want to delete this extension?",
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
"buttons": {
|
||||
"showSystemPlugins": "显示系统插件",
|
||||
"hideSystemPlugins": "隐藏系统插件",
|
||||
"platformConfig": "平台命令配置",
|
||||
"install": "安装",
|
||||
"uninstall": "卸载",
|
||||
"update": "更新",
|
||||
@@ -88,18 +87,6 @@
|
||||
"title": "错误信息",
|
||||
"checkConsole": "详情请检查控制台"
|
||||
},
|
||||
"platformConfig": {
|
||||
"title": "平台命令可用性配置",
|
||||
"description": "设置每个插件在不同平台上的可用性,勾选表示启用",
|
||||
"noAdapters": "未找到平台适配器",
|
||||
"noAdaptersDesc": "请先在 平台管理 中添加并配置平台适配器,然后再设置插件的平台可用性",
|
||||
"goPlatforms": "前往平台管理",
|
||||
"selectAll": "全选",
|
||||
"selectAllNormal": "全选普通插件",
|
||||
"selectAllSystem": "全选系统插件",
|
||||
"selectNone": "全不选",
|
||||
"toggleAll": "反选"
|
||||
},
|
||||
"config": {
|
||||
"title": "插件配置",
|
||||
"noConfig": "这个插件没有配置"
|
||||
@@ -137,8 +124,6 @@
|
||||
"installing": "正在从文件安装插件",
|
||||
"installingFromUrl": "正在从链接安装插件...",
|
||||
"installFailed": "安装插件失败:",
|
||||
"getPlatformConfigFailed": "获取平台插件配置失败:",
|
||||
"savePlatformConfigFailed": "保存平台插件配置失败:",
|
||||
"getMarketDataFailed": "获取插件市场数据失败:",
|
||||
"hasUpdate": "有新版本:",
|
||||
"confirmDelete": "确定要删除插件吗?",
|
||||
|
||||
@@ -45,14 +45,6 @@ const readmeDialog = reactive({
|
||||
pluginName: '',
|
||||
repoUrl: null
|
||||
});
|
||||
// 平台插件配置
|
||||
const platformEnableDialog = ref(false);
|
||||
const platformEnableData = reactive({
|
||||
platforms: [],
|
||||
plugins: [],
|
||||
platform_enable: {}
|
||||
});
|
||||
const loadingPlatformData = ref(false);
|
||||
|
||||
// 新增变量支持列表视图
|
||||
const isListView = ref(false);
|
||||
@@ -326,100 +318,7 @@ 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(tm('dialogs.platformConfig.noAdaptersDesc'), "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(tm('messages.getPlatformConfigFailed') + " " + 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(tm('messages.savePlatformConfigFailed') + " " + 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;
|
||||
});
|
||||
};
|
||||
|
||||
const open = (link) => {
|
||||
if (link) {
|
||||
@@ -685,11 +584,6 @@ onMounted(async () => {
|
||||
{{ showReserved ? tm('buttons.hideSystemPlugins') : tm('buttons.showSystemPlugins') }}
|
||||
</v-btn>
|
||||
|
||||
<v-btn class="ml-2" variant="tonal" @click="getPlatformEnableConfig">
|
||||
<v-icon>mdi-cog</v-icon>
|
||||
{{ tm('buttons.platformConfig') }}
|
||||
</v-btn>
|
||||
|
||||
<v-btn class="ml-2" color="primary" variant="tonal" @click="dialog = true">
|
||||
<v-icon>mdi-plus</v-icon>
|
||||
{{ tm('buttons.install') }}
|
||||
@@ -965,96 +859,6 @@ onMounted(async () => {
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<!-- 插件平台配置对话框 -->
|
||||
<v-dialog v-model="platformEnableDialog" max-width="900" persistent>
|
||||
<v-card class="rounded-lg">
|
||||
<v-toolbar color="primary" density="comfortable" flat>
|
||||
<v-toolbar-title class="text-white">{{ tm('dialogs.platformConfig.title') }}</v-toolbar-title>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn icon @click="platformEnableDialog = false" variant="text" color="white">
|
||||
<v-icon>mdi-close</v-icon>
|
||||
</v-btn>
|
||||
</v-toolbar>
|
||||
|
||||
<v-card-text class="pt-4">
|
||||
<p class="text-body-2 mb-4">{{ tm('dialogs.platformConfig.description') }}</p>
|
||||
|
||||
<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-8">
|
||||
<v-icon icon="mdi-alert" color="warning" size="64" class="mb-4"></v-icon>
|
||||
<div class="text-h5 mb-2">{{ tm('dialogs.platformConfig.noAdapters') }}</div>
|
||||
<div class="text-body-1 mb-4">{{ tm('dialogs.platformConfig.noAdaptersDesc') }}</div>
|
||||
<v-btn color="primary" to="/platforms" variant="elevated">{{ tm('dialogs.platformConfig.goPlatforms')
|
||||
}}</v-btn>
|
||||
</div>
|
||||
|
||||
<v-sheet v-else class="rounded-lg overflow-hidden">
|
||||
<v-table hover class="elevation-1">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-left">{{ tm('table.headers.name') }}</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>{{ tm('dialogs.platformConfig.selectAll') }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item @click="selectAllPluginsForPlatform(platform.name, true, false)">
|
||||
<v-list-item-title>{{ tm('dialogs.platformConfig.selectAllNormal') }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item @click="selectAllPluginsForPlatform(platform.name, true, true)">
|
||||
<v-list-item-title>{{ tm('dialogs.platformConfig.selectAllSystem') }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item @click="selectAllPluginsForPlatform(platform.name, false)">
|
||||
<v-list-item-title>{{ tm('dialogs.platformConfig.selectNone') }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item @click="toggleAllPluginsForPlatform(platform.name)">
|
||||
<v-list-item-title>{{ tm('dialogs.platformConfig.toggleAll') }}</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">{{ tm('status.system')
|
||||
}}</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-sheet>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="grey" text @click="platformEnableDialog = false">{{ tm('buttons.close') }}</v-btn>
|
||||
<v-btn v-if="platformEnableData.platforms.length > 0" color="primary" @click="savePlatformEnableConfig">{{
|
||||
tm('buttons.save') }}</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- 配置对话框 -->
|
||||
<v-dialog v-model="configDialog" width="1000">
|
||||
<v-card>
|
||||
|
||||
Reference in New Issue
Block a user