Merge branch 'master' of https://github.com/advent259141/AstrBot
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
@@ -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__
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user