This commit is contained in:
advent259141
2025-06-13 03:54:35 +08:00
4 changed files with 351 additions and 1 deletions
@@ -8,6 +8,7 @@ from astrbot.core.message.components import At, AtAll
from astrbot.core.star.star_handler import star_handlers_registry, EventType
from astrbot.core.star.star import star_map
from astrbot.core.star.filter.permission import PermissionTypeFilter
from astrbot.core.star.session_plugin_manager import SessionPluginManager
@register_stage
@@ -160,6 +161,9 @@ class WakingCheckStage(Stage):
event.clear_extra()
# 根据会话配置过滤插件处理器
activated_handlers = SessionPluginManager.filter_handlers_by_session(event, activated_handlers)
event.set_extra("activated_handlers", activated_handlers)
event.set_extra("handlers_parsed_params", handlers_parsed_params)
+135
View File
@@ -0,0 +1,135 @@
"""
会话插件管理器 - 负责管理每个会话的插件启停状态
"""
from astrbot.core import sp, logger
from typing import Dict, List, Optional
from astrbot.core.platform.astr_message_event import AstrMessageEvent
class SessionPluginManager:
"""管理会话级别的插件启停状态"""
@staticmethod
def is_plugin_enabled_for_session(session_id: str, plugin_name: str) -> bool:
"""检查插件是否在指定会话中启用
Args:
session_id: 会话ID (unified_msg_origin)
plugin_name: 插件名称
Returns:
bool: True表示启用,False表示禁用
"""
# 获取会话插件配置
session_plugin_config = sp.get("session_plugin_config", {})
session_config = session_plugin_config.get(session_id, {})
enabled_plugins = session_config.get("enabled_plugins", [])
disabled_plugins = session_config.get("disabled_plugins", [])
# 如果插件在禁用列表中,返回False
if plugin_name in disabled_plugins:
return False
# 如果插件在启用列表中,返回True
if plugin_name in enabled_plugins:
return True
# 如果都没有配置,默认为启用(兼容性考虑)
return True
@staticmethod
def set_plugin_status_for_session(session_id: str, plugin_name: str, enabled: bool) -> None:
"""设置插件在指定会话中的启停状态
Args:
session_id: 会话ID (unified_msg_origin)
plugin_name: 插件名称
enabled: True表示启用,False表示禁用
"""
# 获取当前配置
session_plugin_config = sp.get("session_plugin_config", {})
if session_id not in session_plugin_config:
session_plugin_config[session_id] = {
"enabled_plugins": [],
"disabled_plugins": []
}
session_config = session_plugin_config[session_id]
enabled_plugins = session_config.get("enabled_plugins", [])
disabled_plugins = session_config.get("disabled_plugins", [])
if enabled:
# 启用插件
if plugin_name in disabled_plugins:
disabled_plugins.remove(plugin_name)
if plugin_name not in enabled_plugins:
enabled_plugins.append(plugin_name)
else:
# 禁用插件
if plugin_name in enabled_plugins:
enabled_plugins.remove(plugin_name)
if plugin_name not in disabled_plugins:
disabled_plugins.append(plugin_name)
# 保存配置
session_config["enabled_plugins"] = enabled_plugins
session_config["disabled_plugins"] = disabled_plugins
session_plugin_config[session_id] = session_config
sp.put("session_plugin_config", session_plugin_config)
logger.info(f"会话 {session_id} 的插件 {plugin_name} 状态已更新为: {'启用' if enabled else '禁用'}")
@staticmethod
def get_session_plugin_config(session_id: str) -> Dict[str, List[str]]:
"""获取指定会话的插件配置
Args:
session_id: 会话ID (unified_msg_origin)
Returns:
Dict[str, List[str]]: 包含enabled_plugins和disabled_plugins的字典
"""
session_plugin_config = sp.get("session_plugin_config", {})
return session_plugin_config.get(session_id, {
"enabled_plugins": [],
"disabled_plugins": []
})
@staticmethod
def filter_handlers_by_session(event: AstrMessageEvent, handlers: List) -> List:
"""根据会话配置过滤处理器列表
Args:
event: 消息事件
handlers: 原始处理器列表
Returns:
List: 过滤后的处理器列表
"""
from astrbot.core.star.star import star_map
session_id = event.unified_msg_origin
filtered_handlers = []
for handler in handlers:
# 获取处理器对应的插件
plugin = star_map.get(handler.handler_module_path)
if not plugin:
# 如果找不到插件元数据,允许执行(可能是系统插件)
filtered_handlers.append(handler)
continue
# 跳过保留插件(系统插件)
if plugin.reserved:
filtered_handlers.append(handler)
continue
# 检查插件是否在当前会话中启用
if SessionPluginManager.is_plugin_enabled_for_session(session_id, plugin.name):
filtered_handlers.append(handler)
else:
logger.debug(f"插件 {plugin.name} 在会话 {session_id} 中被禁用,跳过处理器 {handler.handler_name}")
return filtered_handlers
@@ -5,6 +5,7 @@ from quart import request
from astrbot.core.db import BaseDatabase
from astrbot.core.core_lifecycle import AstrBotCoreLifecycle
from astrbot.core.provider.entities import ProviderType
from astrbot.core.star.session_plugin_manager import SessionPluginManager
class SessionManagementRoute(Route):
@@ -20,6 +21,8 @@ class SessionManagementRoute(Route):
"/session/update_persona": ("POST", self.update_session_persona),
"/session/update_provider": ("POST", self.update_session_provider),
"/session/get_session_info": ("POST", self.get_session_info),
"/session/plugins": ("GET", self.get_session_plugins),
"/session/update_plugin": ("POST", self.update_session_plugin),
}
self.db_helper = db_helper
self.core_lifecycle = core_lifecycle
@@ -359,3 +362,82 @@ class SessionManagementRoute(Route):
error_msg = f"获取会话信息失败: {str(e)}\n{traceback.format_exc()}"
logger.error(error_msg)
return Response().error(f"获取会话信息失败: {str(e)}").__dict__
async def get_session_plugins(self):
"""获取指定会话的插件配置信息"""
try:
session_id = request.args.get("session_id")
if not session_id:
return Response().error("缺少必要参数: session_id").__dict__
# 获取所有已激活的插件
all_plugins = []
plugin_manager = self.core_lifecycle.star_context._star_manager
for plugin in plugin_manager.context.get_all_stars():
# 只显示已激活的插件,不包括保留插件
if plugin.activated and not plugin.reserved:
plugin_enabled = SessionPluginManager.is_plugin_enabled_for_session(session_id, plugin.name)
all_plugins.append({
"name": plugin.name,
"author": plugin.author,
"desc": plugin.desc,
"enabled": plugin_enabled,
})
return Response().ok({
"session_id": session_id,
"plugins": all_plugins,
}).__dict__
except Exception as e:
error_msg = f"获取会话插件配置失败: {str(e)}\n{traceback.format_exc()}"
logger.error(error_msg)
return Response().error(f"获取会话插件配置失败: {str(e)}").__dict__
async def update_session_plugin(self):
"""更新指定会话的插件启停状态"""
try:
data = await request.get_json()
session_id = data.get("session_id")
plugin_name = data.get("plugin_name")
enabled = data.get("enabled")
if not session_id:
return Response().error("缺少必要参数: session_id").__dict__
if not plugin_name:
return Response().error("缺少必要参数: plugin_name").__dict__
if enabled is None:
return Response().error("缺少必要参数: enabled").__dict__
# 验证插件是否存在且已激活
plugin_manager = self.core_lifecycle.star_context._star_manager
plugin = plugin_manager.context.get_registered_star(plugin_name)
if not plugin:
return Response().error(f"插件 {plugin_name} 不存在").__dict__
if not plugin.activated:
return Response().error(f"插件 {plugin_name} 未激活").__dict__
if plugin.reserved:
return Response().error(f"插件 {plugin_name} 是系统保留插件,无法管理").__dict__
# 使用 SessionPluginManager 更新插件状态
SessionPluginManager.set_plugin_status_for_session(session_id, plugin_name, enabled)
return Response().ok({
"message": f"插件 {plugin_name}{'启用' if enabled else '禁用'}",
"session_id": session_id,
"plugin_name": plugin_name,
"enabled": enabled,
}).__dict__
except Exception as e:
error_msg = f"更新会话插件状态失败: {str(e)}\n{traceback.format_exc()}"
logger.error(error_msg)
return Response().error(f"更新会话插件状态失败: {str(e)}").__dict__
+130 -1
View File
@@ -157,6 +157,19 @@
</v-select>
</template>
<!-- 插件管理 -->
<template v-slot:item.plugins="{ item }">
<v-btn
size="small"
variant="text"
color="primary"
@click="openPluginManager(item)"
:loading="item.loadingPlugins"
>
编辑
</v-btn>
</template>
<!-- 操作 -->
<template v-slot:item.actions="{ item }">
<v-btn
@@ -308,6 +321,65 @@
</v-card>
</v-dialog>
<!-- 插件管理对话框 -->
<v-dialog v-model="pluginDialog" max-width="800">
<v-card v-if="selectedSessionForPlugin">
<v-card-title class="bg-primary text-white py-3 px-4">
<v-icon color="white" class="me-2">mdi-pencil</v-icon>
<span>插件管理 - {{ selectedSessionForPlugin.session_name }}</span>
<v-spacer></v-spacer>
<v-btn icon variant="text" color="white" @click="pluginDialog = false">
<v-icon>mdi-close</v-icon>
</v-btn>
</v-card-title>
<v-card-text class="pa-4" v-if="!loadingPlugins">
<div v-if="sessionPlugins.length === 0" class="text-center py-8">
<v-icon size="64" color="grey-400">mdi-puzzle-outline</v-icon>
<div class="text-h6 mt-4 text-grey-600">暂无可用插件</div>
<div class="text-body-2 text-grey-500">目前没有激活的插件</div>
</div>
<v-list v-else>
<v-list-item
v-for="plugin in sessionPlugins"
:key="plugin.name"
class="px-0"
>
<template v-slot:prepend>
<v-icon :color="plugin.enabled ? 'success' : 'grey'">
{{ plugin.enabled ? 'mdi-check-circle' : 'mdi-circle-outline' }}
</v-icon>
</template>
<v-list-item-title class="font-weight-medium">
{{ plugin.name }}
</v-list-item-title>
<v-list-item-subtitle>
作者: {{ plugin.author }}
</v-list-item-subtitle>
<template v-slot:append>
<v-switch
:model-value="plugin.enabled"
hide-details
color="primary"
@update:model-value="(value) => togglePlugin(plugin, value)"
:loading="plugin.updating"
></v-switch>
</template>
</v-list-item>
</v-list>
</v-card-text>
<v-card-text v-else class="text-center py-8">
<v-progress-circular indeterminate color="primary" size="48"></v-progress-circular>
<div class="text-body-1 mt-4">加载插件列表中...</div>
</v-card-text>
</v-card>
</v-dialog>
<!-- 提示信息 -->
<v-snackbar v-model="snackbar" :timeout="3000" :color="snackbarColor">
{{ snackbarText }}
@@ -342,6 +414,12 @@ export default {
detailDialog: false,
selectedSession: null,
// 插件管理
pluginDialog: false,
selectedSessionForPlugin: null,
sessionPlugins: [],
loadingPlugins: false,
// 提示信息
snackbar: false,
snackbarText: '',
@@ -354,6 +432,7 @@ export default {
{ title: 'Chat Provider', key: 'chat_provider', sortable: false, width: '180px' },
{ title: 'STT Provider', key: 'stt_provider', sortable: false, width: '150px' },
{ title: 'TTS Provider', key: 'tts_provider', sortable: false, width: '150px' },
{ title: '插件管理', key: 'plugins', sortable: false, width: '120px' },
{ title: '操作', key: 'actions', sortable: false, width: '80px' },
],
}
@@ -433,7 +512,8 @@ export default {
const data = response.data.data;
this.sessions = data.sessions.map(session => ({
...session,
updating: false // 添加更新状态标志
updating: false, // 添加更新状态标志
loadingPlugins: false // 添加插件加载状态标志
}));
this.availablePersonas = data.available_personas;
this.availableChatProviders = data.available_chat_providers;
@@ -549,6 +629,55 @@ export default {
this.batchChatProvider = null;
},
async openPluginManager(session) {
this.selectedSessionForPlugin = session;
this.pluginDialog = true;
this.loadingPlugins = true;
this.sessionPlugins = [];
try {
const response = await axios.get('/api/session/plugins', {
params: { session_id: session.session_id }
});
if (response.data.status === 'ok') {
this.sessionPlugins = response.data.data.plugins.map(plugin => ({
...plugin,
updating: false
}));
} else {
this.showError(response.data.message || '加载插件列表失败');
}
} catch (error) {
this.showError(error.response?.data?.message || '加载插件列表失败');
}
this.loadingPlugins = false;
},
async togglePlugin(plugin, enabled) {
plugin.updating = true;
try {
const response = await axios.post('/api/session/update_plugin', {
session_id: this.selectedSessionForPlugin.session_id,
plugin_name: plugin.name,
enabled: enabled
});
if (response.data.status === 'ok') {
plugin.enabled = enabled;
this.showSuccess(`插件 ${plugin.name} ${enabled ? '已启用' : '已禁用'}`);
} else {
this.showError(response.data.message || '插件状态更新失败');
}
} catch (error) {
this.showError(error.response?.data?.message || '插件状态更新失败');
}
plugin.updating = false;
},
showSessionDetail(session) {
this.selectedSession = session;
this.detailDialog = true;