Merge branch 'dev' into feature/kfc

This commit is contained in:
拾风
2025-12-01 16:06:47 +08:00
committed by GitHub
87 changed files with 6181 additions and 2355 deletions

View File

@@ -13,7 +13,7 @@ from src.chat.message_receive.storage import MessageStorage
from src.common.logger import get_logger
from src.config.config import global_config
from src.mood.mood_manager import mood_manager
from src.plugin_system.base.component_types import ChatMode
from src.plugin_system.base.component_types import ChatMode, ChatType
from src.plugins.built_in.affinity_flow_chatter.planner.plan_executor import ChatterPlanExecutor
from src.plugins.built_in.affinity_flow_chatter.planner.plan_filter import ChatterPlanFilter
from src.plugins.built_in.affinity_flow_chatter.planner.plan_generator import ChatterPlanGenerator
@@ -201,6 +201,10 @@ class ChatterActionPlanner:
reply_not_available = True
aggregate_should_act = False
# 检查私聊必回配置
is_private_chat = context and context.chat_type == ChatType.PRIVATE
force_reply = is_private_chat and global_config.chat.private_chat_inevitable_reply
if unread_messages:
# 直接使用消息中已计算的标志,无需重复计算兴趣值
for message in unread_messages:
@@ -219,9 +223,11 @@ class ChatterActionPlanner:
f"should_reply={message_should_reply}, should_act={message_should_act}"
)
if message_should_reply:
if message_should_reply or force_reply:
aggregate_should_act = True
reply_not_available = False
if force_reply:
logger.info(f"Focus模式 - 私聊必回已启用,强制回复消息 {message.message_id}")
break
if message_should_act:
@@ -394,12 +400,19 @@ class ChatterActionPlanner:
should_reply = False
target_message = None
# 检查私聊必回配置
is_private_chat = context and context.chat_type == ChatType.PRIVATE
force_reply = is_private_chat and global_config.chat.private_chat_inevitable_reply
for message in unread_messages:
message_should_reply = getattr(message, "should_reply", False)
if message_should_reply:
if message_should_reply or force_reply:
should_reply = True
target_message = message
logger.info(f"Normal模式 - 消息 {message.message_id} 达到reply阈值准备回复")
if force_reply:
logger.info(f"Normal模式 - 私聊必回已启用,强制回复消息 {message.message_id}")
else:
logger.info(f"Normal模式 - 消息 {message.message_id} 达到reply阈值准备回复")
break
if should_reply and target_message:
@@ -426,7 +439,6 @@ class ChatterActionPlanner:
# 4. 构建回复动作Normal模式使用respond动作
from src.common.data_models.info_data_model import ActionPlannerInfo, Plan
from src.plugin_system.base.component_types import ChatType
# Normal模式使用respond动作表示统一回应未读消息
# respond动作不需要target_message_id和action_message因为它是统一回应所有未读消息

View File

@@ -6,4 +6,5 @@ __plugin_meta__ = PluginMetadata(
usage="在 bot_config.toml 中将 asr_provider 设置为 'local' 即可启用",
version="0.1.0",
author="Elysia",
python_dependencies=["openai-whisper"],
)

View File

@@ -1,8 +1,6 @@
import asyncio
from typing import ClassVar
import whisper
from src.common.logger import get_logger
from src.plugin_system import BasePlugin, ComponentInfo, register_plugin
from src.plugin_system.base.base_tool import BaseTool
@@ -35,6 +33,8 @@ class LocalASRTool(BaseTool):
if _whisper_model is None and not _is_loading:
_is_loading = True
try:
import whisper
model_size = plugin_config.get("whisper", {}).get("model_size", "tiny")
device = plugin_config.get("whisper", {}).get("device", "cpu")
logger.info(f"正在预加载 Whisper ASR 模型: {model_size} ({device})")

View File

@@ -12,6 +12,8 @@ from src.chat.utils.prompt_component_manager import prompt_component_manager
from src.chat.utils.prompt_params import PromptParameters
from src.plugin_system.apis import (
chat_api,
component_state_api,
plugin_info_api,
plugin_manage_api,
)
from src.plugin_system.apis.logging_api import get_logger
@@ -29,6 +31,7 @@ from src.plugin_system.base.component_types import (
from src.plugin_system.base.config_types import ConfigField
from src.plugin_system.base.plus_command import PlusCommand
from src.plugin_system.utils.permission_decorators import require_permission
from src.plugin_system.apis.permission_api import permission_api
logger = get_logger("SystemManagement")
@@ -46,9 +49,15 @@ class SystemCommand(PlusCommand):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@require_permission("access", deny_message="❌ 你没有权限使用此命令")
async def execute(self, args: CommandArgs) -> tuple[bool, str | None, bool]:
"""执行系统管理命令"""
if not self.chat_stream.user_info:
logger.error("chat_stream缺失用户信息,请报告开发者")
return False, "chat_stream缺失用户信息,请报告开发者", True
has_permission = await permission_api.check_permission(platform=self.chat_stream.platform,user_id=self.chat_stream.user_info.user_id,permission_node="access")
if has_permission:
logger.warning("没有权限使用此命令")
return False, "没有权限使用此命令", True
if args.is_empty:
await self._show_help("all")
return True, "显示帮助信息", True
@@ -79,7 +88,7 @@ class SystemCommand(PlusCommand):
🔧 主要功能:
• `/system help` - 显示此帮助
• `/system permission` - 权限管理
• `/system plugin` - 插件管理
• `/system plugin` - 插件与组件管理
• `/system schedule` - 定时任务管理
• `/system prompt` - 提示词注入管理
"""
@@ -96,16 +105,18 @@ class SystemCommand(PlusCommand):
"""
elif target == "plugin":
help_text = """🔌 插件管理命令帮助
📋 基本操作:
• `/system plugin help` - 显示插件管理帮助
• `/system plugin report` - 查看系统插件报告
• `/system plugin rescan` - 重新扫描所有插件目录
⚙️ 插件控制:
• `/system plugin rescan` - 重新扫描所有插件目录
• `/system plugin load <插件名>` - 加载指定插件
• `/system plugin reload <插件名>` - 重新加载指定插件
• `/system plugin reload_all` - 重新加载所有插件
🎯 局部控制 (需要 `system.plugin.manage.local` 权限):
🔧 全局组件控制 (需要 `system.plugin.manage` 权限):
• `/system plugin enable <组件名>` - 全局启用组件
• `/system plugin disable <组件名>` - 全局禁用组件
🎯 局部组件控制 (需要 `system.plugin.manage.local` 权限):
• `/system plugin enable_local <名称> [group <群号> | private <QQ号>]` - 在指定会话局部启用组件
• `/system plugin disable_local <名称> [group <群号> | private <QQ号>]` - 在指定会话局部禁用组件
"""
@@ -154,6 +165,15 @@ class SystemCommand(PlusCommand):
await self._show_help("plugin")
elif action in ["report", "报告"]:
await self._show_system_report()
elif action in ["info", "详情"] and remaining_args:
await self._show_plugin_info(remaining_args[0])
elif action in ["list", "列表"]:
comp_type = remaining_args[0] if remaining_args else None
await self._list_components(comp_type)
elif action in ["search", "搜索"] and remaining_args:
await self._search_components(remaining_args[0])
elif action in ["disabled", "禁用列表"]:
await self._list_disabled_components()
elif action in ["rescan", "重扫"]:
await self._rescan_plugin_dirs()
elif action in ["load", "加载"] and len(remaining_args) > 0:
@@ -162,6 +182,10 @@ class SystemCommand(PlusCommand):
await self._reload_plugin(remaining_args[0])
elif action in ["reload_all", "重载全部"]:
await self._reload_all_plugins()
elif action in ["enable", "启用"] and len(remaining_args) >= 1:
await self._set_global_component_state(remaining_args[0], enabled=True)
elif action in ["disable", "禁用"] and len(remaining_args) >= 1:
await self._set_global_component_state(remaining_args[0], enabled=False)
elif action in ["enable_local", "局部启用"] and len(remaining_args) >= 1:
await self._set_local_component_state(remaining_args, enabled=True)
elif action in ["disable_local", "局部禁用"] and len(remaining_args) >= 1:
@@ -428,13 +452,198 @@ class SystemCommand(PlusCommand):
await self.send_text("\n".join(response_parts))
# =================================================================
# Permission Management Section
# Permission Management Section (Plugin Stats & Info)
# =================================================================
@require_permission("plugin.manage", deny_message="❌ 你没有权限查看插件详情")
async def _show_plugin_info(self, plugin_name: str):
"""显示单个插件的详细信息"""
details = plugin_info_api.get_plugin_details(plugin_name)
if not details:
# 尝试模糊匹配
all_plugins = plugin_info_api.list_plugins("loaded")
suggestions = [p for p in all_plugins if plugin_name.lower() in p.lower()]
if suggestions:
await self.send_text(
f"❌ 未找到插件 `{plugin_name}`\n"
f"你可能想找的是: {', '.join([f'`{s}`' for s in suggestions[:5]])}"
)
else:
await self.send_text(f"❌ 未找到插件 `{plugin_name}`")
return
response_parts = [
f"🔌 **插件详情: {details['display_name']}**",
f" • 内部名称: `{details['name']}`",
f" • 版本: {details['version']}",
f" • 作者: {details['author']}",
f" • 状态: {'✅ 已启用' if details['enabled'] else '❌ 已禁用'}",
f" • 加载状态: {details['status']}",
]
if details.get('description'):
response_parts.append(f" • 描述: {details['description']}")
if details.get('license'):
response_parts.append(f" • 许可证: {details['license']}")
# 组件信息
if details['components']:
response_parts.append(f"\n🧩 **组件列表** (共 {len(details['components'])} 个):")
for comp in details['components']:
status = "" if comp['enabled'] else ""
response_parts.append(f" {status} `{comp['name']}` ({comp['component_type']})")
if comp.get('description'):
response_parts.append(f" {comp['description'][:50]}...")
await self._send_long_message("\n".join(response_parts))
@require_permission("plugin.manage", deny_message="❌ 你没有权限查看组件列表")
async def _list_components(self, comp_type_str: str | None):
"""列出指定类型的组件"""
# 显示可用类型帮助
available_types = [t.value for t in ComponentType]
if comp_type_str:
# 尝试匹配组件类型
comp_type = None
for t in ComponentType:
if t.value.lower() == comp_type_str.lower() or t.name.lower() == comp_type_str.lower():
comp_type = t
break
if not comp_type:
await self.send_text(
f"❌ 未知的组件类型: `{comp_type_str}`\n"
f"可用类型: {', '.join([f'`{t}`' for t in available_types])}"
)
return
components = plugin_info_api.list_components(comp_type, enabled_only=False)
title = f"🧩 **{comp_type.value} 组件列表** (共 {len(components)} 个)"
else:
# 列出所有类型的统计
response_parts = ["🧩 **组件类型概览**", ""]
for t in ComponentType:
comps = plugin_info_api.list_components(t, enabled_only=False)
enabled = sum(1 for c in comps if c['enabled'])
if comps:
response_parts.append(f"• **{t.value}**: {enabled}/{len(comps)} 启用")
response_parts.append(f"\n💡 使用 `/system plugin list <类型>` 查看详情")
response_parts.append(f"可用类型: {', '.join([f'`{t}`' for t in available_types])}")
await self.send_text("\n".join(response_parts))
return
if not components:
await self.send_text(f"📭 没有找到 {comp_type.value} 类型的组件")
return
response_parts = [title, ""]
for comp in components:
status = "" if comp['enabled'] else ""
response_parts.append(f"{status} `{comp['name']}` (来自: `{comp['plugin_name']}`)")
await self._send_long_message("\n".join(response_parts))
@require_permission("plugin.manage", deny_message="❌ 你没有权限搜索组件")
async def _search_components(self, keyword: str):
"""搜索组件"""
results = plugin_info_api.search_components_by_name(keyword, case_sensitive=False)
if not results:
await self.send_text(f"🔍 未找到包含 `{keyword}` 的组件")
return
response_parts = [f"🔍 **搜索结果** (关键词: `{keyword}`, 共 {len(results)} 个)", ""]
for comp in results:
status = "" if comp['enabled'] else ""
response_parts.append(
f"{status} `{comp['name']}` ({comp['component_type']})\n"
f" 来自: `{comp['plugin_name']}`"
)
await self._send_long_message("\n".join(response_parts))
@require_permission("plugin.manage", deny_message="❌ 你没有权限查看禁用组件")
async def _list_disabled_components(self):
"""列出所有禁用的组件"""
disabled = component_state_api.get_disabled_components()
if not disabled:
await self.send_text("✅ 当前没有被禁用的组件")
return
response_parts = [f"🚫 **禁用组件列表** (共 {len(disabled)} 个)", ""]
# 按插件分组
by_plugin: dict[str, list] = {}
for comp in disabled:
plugin_name = comp.plugin_name
if plugin_name not in by_plugin:
by_plugin[plugin_name] = []
by_plugin[plugin_name].append(comp)
for plugin_name, comps in by_plugin.items():
response_parts.append(f"🔌 **{plugin_name}**:")
for comp in comps:
response_parts.append(f" ❌ `{comp.name}` ({comp.component_type.value})")
await self._send_long_message("\n".join(response_parts))
@require_permission("plugin.manage", deny_message="❌ 你没有权限管理组件状态")
async def _set_global_component_state(self, comp_name: str, enabled: bool):
"""全局启用或禁用组件"""
# 搜索组件
found_components = plugin_info_api.search_components_by_name(comp_name, exact_match=True)
if not found_components:
# 尝试模糊搜索给出建议
fuzzy_results = plugin_info_api.search_components_by_name(comp_name, exact_match=False)
if fuzzy_results:
suggestions = ", ".join([f"`{c['name']}`" for c in fuzzy_results[:5]])
await self.send_text(f"❌ 未找到名为 `{comp_name}` 的组件\n你可能想找的是: {suggestions}")
else:
await self.send_text(f"❌ 未找到名为 `{comp_name}` 的组件")
return
if len(found_components) > 1:
suggestions = "\n".join([f"- `{c['name']}` (类型: {c['component_type']})" for c in found_components])
await self.send_text(f"❌ 发现多个名为 `{comp_name}` 的组件,操作已取消。\n找到的组件:\n{suggestions}")
return
component_info = found_components[0]
comp_type_str = component_info["component_type"]
component_type = ComponentType(comp_type_str)
# 禁用保护
if not enabled:
protected_types = [
ComponentType.INTEREST_CALCULATOR,
ComponentType.PROMPT,
ComponentType.ROUTER,
]
if component_type in protected_types:
await self.send_text(f"❌ 无法禁用核心组件 `{comp_name}` ({comp_type_str})")
return
# 执行操作
success = await component_state_api.set_component_enabled(comp_name, component_type, enabled)
action_text = "启用" if enabled else "禁用"
if success:
await self.send_text(f"✅ 已全局{action_text}组件 `{comp_name}` ({comp_type_str})")
else:
if component_type == ComponentType.CHATTER and not enabled:
await self.send_text(f"❌ 无法禁用最后一个 Chatter 组件 `{comp_name}`")
else:
await self.send_text(f"{action_text}组件 `{comp_name}` 失败,请检查日志")
@require_permission("plugin.manage", deny_message="❌ 你没有权限查看插件报告")
async def _show_system_report(self):
"""显示系统插件报告"""
report = plugin_manage_api.get_system_report()
report = plugin_info_api.get_system_report()
response_parts = [
"📊 **系统插件报告**",
@@ -509,7 +718,7 @@ class SystemCommand(PlusCommand):
stream_id = self.message.chat_info.stream_id # 默认作用于当前会话
# 1. 搜索组件
found_components = plugin_manage_api.search_components_by_name(comp_name, exact_match=True)
found_components = plugin_info_api.search_components_by_name(comp_name, exact_match=True)
if not found_components:
await self.send_text(f"❌ 未找到名为 '{comp_name}' 的组件。")
@@ -563,7 +772,7 @@ class SystemCommand(PlusCommand):
stream_id = target_stream.stream_id
# 4. 执行操作
success = plugin_manage_api.set_component_enabled_local(
success = component_state_api.set_component_enabled_local(
stream_id=stream_id,
name=comp_name,
component_type=component_type,
@@ -782,13 +991,13 @@ class SystemCommand(PlusCommand):
@register_plugin
class SystemManagementPlugin(BasePlugin):
plugin_name: str = "system_management"
enable_plugin: bool = True
enable_plugin: bool = False
dependencies: ClassVar[list[str]] = [] # 插件依赖列表
python_dependencies: ClassVar[list[str]] = [] # Python包依赖列表现在使用内置API
config_file_name: str = "config.toml" # 配置文件名
config_schema: ClassVar[dict] = {
"plugin": {
"enabled": ConfigField(bool, default=True, description="是否启用插件"),
"enabled": ConfigField(bool, default=False, description="是否启用插件"),
}
}