refactor: custom rules

This commit is contained in:
Soulter
2025-11-27 01:27:33 +08:00
parent 3989a6669c
commit bf5a6aeaff
8 changed files with 1138 additions and 1858 deletions

View File

@@ -1,16 +1,24 @@
import traceback
from quart import request from quart import request
from sqlalchemy.ext.asyncio import AsyncSession
from sqlmodel import col, select
from astrbot.core import logger, sp from astrbot.core import logger, sp
from astrbot.core.core_lifecycle import AstrBotCoreLifecycle from astrbot.core.core_lifecycle import AstrBotCoreLifecycle
from astrbot.core.db import BaseDatabase from astrbot.core.db import BaseDatabase
from astrbot.core.db.po import ConversationV2, Preference
from astrbot.core.provider.entities import ProviderType from astrbot.core.provider.entities import ProviderType
from astrbot.core.star.session_llm_manager import SessionServiceManager
from astrbot.core.star.session_plugin_manager import SessionPluginManager
from .route import Response, Route, RouteContext from .route import Response, Route, RouteContext
AVAILABLE_SESSION_RULE_KEYS = [
"session_service_config",
"session_plugin_config",
"kb_config",
f"provider_perf_{ProviderType.CHAT_COMPLETION.value}",
f"provider_perf_{ProviderType.SPEECH_TO_TEXT.value}",
f"provider_perf_{ProviderType.TEXT_TO_SPEECH.value}",
]
class SessionManagementRoute(Route): class SessionManagementRoute(Route):
def __init__( def __init__(
@@ -22,667 +30,325 @@ class SessionManagementRoute(Route):
super().__init__(context) super().__init__(context)
self.db_helper = db_helper self.db_helper = db_helper
self.routes = { self.routes = {
"/session/list": ("GET", self.list_sessions), "/session/list-rule": ("GET", self.list_session_rule),
"/session/update_persona": ("POST", self.update_session_persona), "/session/update-rule": ("POST", self.update_session_rule),
"/session/update_provider": ("POST", self.update_session_provider), "/session/delete-rule": ("POST", self.delete_session_rule),
"/session/plugins": ("GET", self.get_session_plugins), "/session/batch-delete-rule": ("POST", self.batch_delete_session_rule),
"/session/update_plugin": ("POST", self.update_session_plugin), "/session/active-umos": ("GET", self.list_umos),
"/session/update_llm": ("POST", self.update_session_llm),
"/session/update_tts": ("POST", self.update_session_tts),
"/session/update_name": ("POST", self.update_session_name),
"/session/update_status": ("POST", self.update_session_status),
"/session/delete": ("POST", self.delete_session),
} }
self.conv_mgr = core_lifecycle.conversation_manager self.conv_mgr = core_lifecycle.conversation_manager
self.core_lifecycle = core_lifecycle self.core_lifecycle = core_lifecycle
self.register_routes() self.register_routes()
async def list_sessions(self): async def _get_umo_rules(
"""获取所有会话的列表,包括 persona 和 provider 信息""" self, page: int = 1, page_size: int = 10, search: str = ""
try: ) -> tuple[dict, int]:
page = int(request.args.get("page", 1)) """获取所有带有自定义规则的 umo 及其规则内容(支持分页和搜索)。
page_size = int(request.args.get("page_size", 20))
search_query = request.args.get("search", "")
platform = request.args.get("platform", "")
# 获取活跃的会话数据(处于对话内的会话) 如果某个 umo 在 preference 中有以下字段,则表示有自定义规则:
sessions_data, total = await self.db_helper.get_session_conversations(
page, 1. session_service_config (包含了 是否启用这个umo, 这个umo是否启用 llm, 这个umo是否启用tts, umo自定义名称。)
page_size, 2. session_plugin_config (包含了 这个 umo 的 plugin set)
search_query, 3. provider_perf_{ProviderType.value} (包含了这个 umo 所选择使用的 provider 信息)
platform, 4. kb_config (包含了这个 umo 的知识库相关配置)
Args:
page: 页码,从 1 开始
page_size: 每页数量
search: 搜索关键词,匹配 umo 或 custom_name
Returns:
tuple[dict, int]: (umo_rules, total) - 分页后的 umo 规则和总数
"""
umo_rules = {}
async with self.db_helper.get_db() as session:
session: AsyncSession
result = await session.execute(
select(Preference).where(
col(Preference.scope) == "umo",
col(Preference.key).in_(AVAILABLE_SESSION_RULE_KEYS),
)
)
prefs = result.scalars().all()
for pref in prefs:
umo_id = pref.scope_id
if umo_id not in umo_rules:
umo_rules[umo_id] = {}
umo_rules[umo_id][pref.key] = pref.value["val"]
# 搜索过滤
if search:
search_lower = search.lower()
filtered_rules = {}
for umo_id, rules in umo_rules.items():
# 匹配 umo
if search_lower in umo_id.lower():
filtered_rules[umo_id] = rules
continue
# 匹配 custom_name
svc_config = rules.get("session_service_config", {})
custom_name = svc_config.get("custom_name", "") if svc_config else ""
if custom_name and search_lower in custom_name.lower():
filtered_rules[umo_id] = rules
umo_rules = filtered_rules
# 获取总数
total = len(umo_rules)
# 分页处理
all_umo_ids = list(umo_rules.keys())
start_idx = (page - 1) * page_size
end_idx = start_idx + page_size
paginated_umo_ids = all_umo_ids[start_idx:end_idx]
# 只返回分页后的数据
paginated_rules = {umo_id: umo_rules[umo_id] for umo_id in paginated_umo_ids}
return paginated_rules, total
async def list_session_rule(self):
"""获取所有自定义的规则(支持分页和搜索)
返回已配置规则的 umo 列表及其规则内容,以及可用的 personas 和 providers
Query 参数:
page: 页码,默认为 1
page_size: 每页数量,默认为 10
search: 搜索关键词,匹配 umo 或 custom_name
"""
try:
# 获取分页和搜索参数
page = request.args.get("page", 1, type=int)
page_size = request.args.get("page_size", 10, type=int)
search = request.args.get("search", "", type=str).strip()
# 参数校验
if page < 1:
page = 1
if page_size < 1:
page_size = 10
if page_size > 100:
page_size = 100
umo_rules, total = await self._get_umo_rules(
page=page, page_size=page_size, search=search
) )
# 构建规则列表
rules_list = []
for umo, rules in umo_rules.items():
rule_info = {
"umo": umo,
"rules": rules,
}
# 解析 umo 格式: 平台:消息类型:会话ID
parts = umo.split(":")
if len(parts) >= 3:
rule_info["platform"] = parts[0]
rule_info["message_type"] = parts[1]
rule_info["session_id"] = parts[2]
rules_list.append(rule_info)
# 获取可用的 providers 和 personas
provider_manager = self.core_lifecycle.provider_manager provider_manager = self.core_lifecycle.provider_manager
persona_mgr = self.core_lifecycle.persona_mgr persona_mgr = self.core_lifecycle.persona_mgr
personas = persona_mgr.personas_v3
sessions = []
# 循环补充非数据库信息,如 provider 和 session 状态
for data in sessions_data:
session_id = data["session_id"]
conversation_id = data["conversation_id"]
conv_persona_id = data["persona_id"]
title = data["title"]
persona_name = data["persona_name"]
# 处理 persona 显示
if persona_name is None:
if conv_persona_id is None:
if default_persona := persona_mgr.selected_default_persona_v3:
persona_name = default_persona["name"]
else:
persona_name = "[%None]"
session_info = {
"session_id": session_id,
"conversation_id": conversation_id,
"persona_id": persona_name,
"chat_provider_id": None,
"stt_provider_id": None,
"tts_provider_id": None,
"session_enabled": SessionServiceManager.is_session_enabled(
session_id,
),
"llm_enabled": SessionServiceManager.is_llm_enabled_for_session(
session_id,
),
"tts_enabled": SessionServiceManager.is_tts_enabled_for_session(
session_id,
),
"platform": session_id.split(":")[0]
if ":" in session_id
else "unknown",
"message_type": session_id.split(":")[1]
if session_id.count(":") >= 1
else "unknown",
"session_name": SessionServiceManager.get_session_display_name(
session_id,
),
"session_raw_name": session_id.split(":")[2]
if session_id.count(":") >= 2
else session_id,
"title": title,
}
# 获取 provider 信息
chat_provider = provider_manager.get_using_provider(
provider_type=ProviderType.CHAT_COMPLETION,
umo=session_id,
)
tts_provider = provider_manager.get_using_provider(
provider_type=ProviderType.TEXT_TO_SPEECH,
umo=session_id,
)
stt_provider = provider_manager.get_using_provider(
provider_type=ProviderType.SPEECH_TO_TEXT,
umo=session_id,
)
if chat_provider:
meta = chat_provider.meta()
session_info["chat_provider_id"] = meta.id
if tts_provider:
meta = tts_provider.meta()
session_info["tts_provider_id"] = meta.id
if stt_provider:
meta = stt_provider.meta()
session_info["stt_provider_id"] = meta.id
sessions.append(session_info)
# 获取可用的 personas 和 providers 列表
available_personas = [ available_personas = [
{"name": p["name"], "prompt": p.get("prompt", "")} for p in personas {"name": p["name"], "prompt": p.get("prompt", "")}
for p in persona_mgr.personas_v3
] ]
available_chat_providers = [] available_chat_providers = [
for provider in provider_manager.provider_insts:
meta = provider.meta()
available_chat_providers.append(
{
"id": meta.id,
"name": meta.id,
"model": meta.model,
"type": meta.type,
},
)
available_stt_providers = []
for provider in provider_manager.stt_provider_insts:
meta = provider.meta()
available_stt_providers.append(
{
"id": meta.id,
"name": meta.id,
"model": meta.model,
"type": meta.type,
},
)
available_tts_providers = []
for provider in provider_manager.tts_provider_insts:
meta = provider.meta()
available_tts_providers.append(
{
"id": meta.id,
"name": meta.id,
"model": meta.model,
"type": meta.type,
},
)
result = {
"sessions": sessions,
"available_personas": available_personas,
"available_chat_providers": available_chat_providers,
"available_stt_providers": available_stt_providers,
"available_tts_providers": available_tts_providers,
"pagination": {
"page": page,
"page_size": page_size,
"total": total,
"total_pages": (total + page_size - 1) // page_size
if page_size > 0
else 0,
},
}
return Response().ok(result).__dict__
except Exception as e:
error_msg = f"获取会话列表失败: {e!s}\n{traceback.format_exc()}"
logger.error(error_msg)
return Response().error(f"获取会话列表失败: {e!s}").__dict__
async def _update_single_session_persona(self, session_id: str, persona_name: str):
"""更新单个会话的 persona 的内部方法"""
conversation_manager = self.core_lifecycle.star_context.conversation_manager
conversation_id = await conversation_manager.get_curr_conversation_id(
session_id,
)
conv = None
if conversation_id:
conv = await conversation_manager.get_conversation(
unified_msg_origin=session_id,
conversation_id=conversation_id,
)
if not conv or not conversation_id:
conversation_id = await conversation_manager.new_conversation(session_id)
# 更新 persona
await conversation_manager.update_conversation_persona_id(
session_id,
persona_name,
)
async def _handle_batch_operation(
self,
session_ids: list,
operation_func,
operation_name: str,
**kwargs,
):
"""通用的批量操作处理方法"""
success_count = 0
error_sessions = []
for session_id in session_ids:
try:
await operation_func(session_id, **kwargs)
success_count += 1
except Exception as e:
logger.error(f"批量{operation_name} 会话 {session_id} 失败: {e!s}")
error_sessions.append(session_id)
if error_sessions:
return (
Response()
.ok(
{
"message": f"批量更新完成,成功: {success_count},失败: {len(error_sessions)}",
"success_count": success_count,
"error_count": len(error_sessions),
"error_sessions": error_sessions,
},
)
.__dict__
)
return (
Response()
.ok(
{ {
"message": f"成功批量{operation_name} {success_count} 个会话", "id": p.meta().id,
"success_count": success_count, "name": p.meta().id,
}, "model": p.meta().model,
) }
.__dict__ for p in provider_manager.provider_insts
) ]
async def update_session_persona(self): available_stt_providers = [
"""更新指定会话的 persona支持批量操作""" {
try: "id": p.meta().id,
data = await request.get_json() "name": p.meta().id,
is_batch = data.get("is_batch", False) "model": p.meta().model,
persona_name = data.get("persona_name") }
for p in provider_manager.stt_provider_insts
]
if persona_name is None: available_tts_providers = [
return Response().error("缺少必要参数: persona_name").__dict__ {
"id": p.meta().id,
"name": p.meta().id,
"model": p.meta().model,
}
for p in provider_manager.tts_provider_insts
]
if is_batch:
session_ids = data.get("session_ids", [])
if not session_ids:
return Response().error("缺少必要参数: session_ids").__dict__
return await self._handle_batch_operation(
session_ids,
self._update_single_session_persona,
"更新人格",
persona_name=persona_name,
)
session_id = data.get("session_id")
if not session_id:
return Response().error("缺少必要参数: session_id").__dict__
await self._update_single_session_persona(session_id, persona_name)
return ( return (
Response() Response()
.ok( .ok(
{ {
"message": f"成功更新会话 {session_id} 的人格为 {persona_name}", "rules": rules_list,
}, "total": total,
"page": page,
"page_size": page_size,
"available_personas": available_personas,
"available_chat_providers": available_chat_providers,
"available_stt_providers": available_stt_providers,
"available_tts_providers": available_tts_providers,
"available_rule_keys": AVAILABLE_SESSION_RULE_KEYS,
}
) )
.__dict__ .__dict__
) )
except Exception as e: except Exception as e:
error_msg = f"更新会话人格失败: {e!s}\n{traceback.format_exc()}" logger.error(f"获取规则列表失败: {e!s}")
logger.error(error_msg) return Response().error(f"获取规则列表失败: {e!s}").__dict__
return Response().error(f"更新会话人格失败: {e!s}").__dict__
async def _update_single_session_provider( async def update_session_rule(self):
self, """更新某个 umo 的自定义规则
session_id: str,
provider_id: str,
provider_type_enum,
):
"""更新单个会话的 provider 的内部方法"""
provider_manager = self.core_lifecycle.star_context.provider_manager
await provider_manager.set_provider(
provider_id=provider_id,
provider_type=provider_type_enum,
umo=session_id,
)
async def update_session_provider(self): 请求体:
"""更新指定会话的 provider支持批量操作""" {
"umo": "平台:消息类型:会话ID",
"rule_key": "session_service_config" | "session_plugin_config" | "kb_config" | "provider_perf_xxx",
"rule_value": {...} // 规则值,具体结构根据 rule_key 不同而不同
}
"""
try: try:
data = await request.get_json() data = await request.get_json()
is_batch = data.get("is_batch", False) umo = data.get("umo")
provider_id = data.get("provider_id") rule_key = data.get("rule_key")
provider_type = data.get("provider_type") rule_value = data.get("rule_value")
if not provider_id or not provider_type: if not umo:
return Response().error("缺少必要参数: umo").__dict__
if not rule_key:
return Response().error("缺少必要参数: rule_key").__dict__
if rule_key not in AVAILABLE_SESSION_RULE_KEYS:
return Response().error(f"不支持的规则键: {rule_key}").__dict__
# 使用 shared preferences 更新规则
await sp.session_put(umo, rule_key, rule_value)
return (
Response()
.ok({"message": f"规则 {rule_key} 已更新", "umo": umo})
.__dict__
)
except Exception as e:
logger.error(f"更新会话规则失败: {e!s}")
return Response().error(f"更新会话规则失败: {e!s}").__dict__
async def delete_session_rule(self):
"""删除某个 umo 的自定义规则
请求体:
{
"umo": "平台:消息类型:会话ID",
"rule_key": "session_service_config" | "session_plugin_config" | ... (可选,不传则删除所有规则)
}
"""
try:
data = await request.get_json()
umo = data.get("umo")
rule_key = data.get("rule_key")
if not umo:
return Response().error("缺少必要参数: umo").__dict__
if rule_key:
# 删除单个规则
if rule_key not in AVAILABLE_SESSION_RULE_KEYS:
return Response().error(f"不支持的规则键: {rule_key}").__dict__
await sp.session_remove(umo, rule_key)
return ( return (
Response() Response()
.error("缺少必要参数: provider_id, provider_type") .ok({"message": f"规则 {rule_key} 已删除", "umo": umo})
.__dict__ .__dict__
) )
else:
# 删除该 umo 的所有规则
await sp.clear_async("umo", umo)
return Response().ok({"message": "所有规则已删除", "umo": umo}).__dict__
except Exception as e:
logger.error(f"删除会话规则失败: {e!s}")
return Response().error(f"删除会话规则失败: {e!s}").__dict__
# 转换 provider_type 字符串为枚举 async def batch_delete_session_rule(self):
if provider_type == "chat_completion": """批量删除多个 umo 的自定义规则
provider_type_enum = ProviderType.CHAT_COMPLETION
elif provider_type == "speech_to_text": 请求体:
provider_type_enum = ProviderType.SPEECH_TO_TEXT {
elif provider_type == "text_to_speech": "umos": ["平台:消息类型:会话ID", ...] // umo 列表
provider_type_enum = ProviderType.TEXT_TO_SPEECH }
"""
try:
data = await request.get_json()
umos = data.get("umos", [])
if not umos:
return Response().error("缺少必要参数: umos").__dict__
if not isinstance(umos, list):
return Response().error("参数 umos 必须是数组").__dict__
# 批量删除
deleted_count = 0
failed_umos = []
for umo in umos:
try:
await sp.clear_async("umo", umo)
deleted_count += 1
except Exception as e:
logger.error(f"删除 umo {umo} 的规则失败: {e!s}")
failed_umos.append(umo)
if failed_umos:
return (
Response()
.ok(
{
"message": f"已删除 {deleted_count} 条规则,{len(failed_umos)} 条删除失败",
"deleted_count": deleted_count,
"failed_umos": failed_umos,
}
)
.__dict__
)
else: else:
return ( return (
Response() Response()
.error(f"不支持的 provider_type: {provider_type}") .ok(
.__dict__
)
if is_batch:
session_ids = data.get("session_ids", [])
if not session_ids:
return Response().error("缺少必要参数: session_ids").__dict__
return await self._handle_batch_operation(
session_ids,
self._update_single_session_provider,
f"更新 {provider_type} 提供商",
provider_id=provider_id,
provider_type_enum=provider_type_enum,
)
session_id = data.get("session_id")
if not session_id:
return Response().error("缺少必要参数: session_id").__dict__
await self._update_single_session_provider(
session_id,
provider_id,
provider_type_enum,
)
return (
Response()
.ok(
{
"message": f"成功更新会话 {session_id}{provider_type} 提供商为 {provider_id}",
},
)
.__dict__
)
except Exception as e:
error_msg = f"更新会话提供商失败: {e!s}\n{traceback.format_exc()}"
logger.error(error_msg)
return Response().error(f"更新会话提供商失败: {e!s}").__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.plugin_manager
for plugin in plugin_manager.context.get_all_stars():
# 只显示已激活的插件,不包括保留插件
if plugin.activated and not plugin.reserved:
plugin_name = plugin.name or ""
plugin_enabled = SessionPluginManager.is_plugin_enabled_for_session(
session_id,
plugin_name,
)
all_plugins.append(
{ {
"name": plugin_name, "message": f"已删除 {deleted_count} 条规则",
"author": plugin.author, "deleted_count": deleted_count,
"desc": plugin.desc, }
"enabled": plugin_enabled,
},
) )
return (
Response()
.ok(
{
"session_id": session_id,
"plugins": all_plugins,
},
)
.__dict__
)
except Exception as e:
error_msg = f"获取会话插件配置失败: {e!s}\n{traceback.format_exc()}"
logger.error(error_msg)
return Response().error(f"获取会话插件配置失败: {e!s}").__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.plugin_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__ .__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: except Exception as e:
error_msg = f"更新会话插件状态失败: {e!s}\n{traceback.format_exc()}" logger.error(f"批量删除会话规则失败: {e!s}")
logger.error(error_msg) return Response().error(f"批量删除会话规则失败: {e!s}").__dict__
return Response().error(f"更新会话插件状态失败: {e!s}").__dict__
async def _update_single_session_llm(self, session_id: str, enabled: bool): async def list_umos(self):
"""更新单个会话的LLM状态的内部方法""" """列出所有有对话记录的 umo从 Conversations 表中找
SessionServiceManager.set_llm_status_for_session(session_id, enabled)
async def update_session_llm(self): 仅返回 umo 字符串列表,用于用户在创建规则时选择 umo
"""更新指定会话的LLM启停状态支持批量操作""" """
try: try:
data = await request.get_json() # 从 Conversation 表获取所有 distinct user_id (即 umo)
is_batch = data.get("is_batch", False) async with self.db_helper.get_db() as session:
enabled = data.get("enabled") session: AsyncSession
result = await session.execute(
if enabled is None: select(ConversationV2.user_id)
return Response().error("缺少必要参数: enabled").__dict__ .distinct()
.order_by(ConversationV2.user_id)
if is_batch:
session_ids = data.get("session_ids", [])
if not session_ids:
return Response().error("缺少必要参数: session_ids").__dict__
result = await self._handle_batch_operation(
session_ids,
self._update_single_session_llm,
f"{'启用' if enabled else '禁用'}LLM",
enabled=enabled,
) )
return result umos = [row[0] for row in result.fetchall()]
session_id = data.get("session_id")
if not session_id:
return Response().error("缺少必要参数: session_id").__dict__
await self._update_single_session_llm(session_id, enabled)
return (
Response()
.ok(
{
"message": f"LLM已{'启用' if enabled else '禁用'}",
"session_id": session_id,
"llm_enabled": enabled,
},
)
.__dict__
)
return Response().ok({"umos": umos}).__dict__
except Exception as e: except Exception as e:
error_msg = f"更新会话LLM状态失败: {e!s}\n{traceback.format_exc()}" logger.error(f"获取 UMO 列表失败: {e!s}")
logger.error(error_msg) return Response().error(f"获取 UMO 列表失败: {e!s}").__dict__
return Response().error(f"更新会话LLM状态失败: {e!s}").__dict__
async def _update_single_session_tts(self, session_id: str, enabled: bool):
"""更新单个会话的TTS状态的内部方法"""
SessionServiceManager.set_tts_status_for_session(session_id, enabled)
async def update_session_tts(self):
"""更新指定会话的TTS启停状态支持批量操作"""
try:
data = await request.get_json()
is_batch = data.get("is_batch", False)
enabled = data.get("enabled")
if enabled is None:
return Response().error("缺少必要参数: enabled").__dict__
if is_batch:
session_ids = data.get("session_ids", [])
if not session_ids:
return Response().error("缺少必要参数: session_ids").__dict__
result = await self._handle_batch_operation(
session_ids,
self._update_single_session_tts,
f"{'启用' if enabled else '禁用'}TTS",
enabled=enabled,
)
return result
session_id = data.get("session_id")
if not session_id:
return Response().error("缺少必要参数: session_id").__dict__
await self._update_single_session_tts(session_id, enabled)
return (
Response()
.ok(
{
"message": f"TTS已{'启用' if enabled else '禁用'}",
"session_id": session_id,
"tts_enabled": enabled,
},
)
.__dict__
)
except Exception as e:
error_msg = f"更新会话TTS状态失败: {e!s}\n{traceback.format_exc()}"
logger.error(error_msg)
return Response().error(f"更新会话TTS状态失败: {e!s}").__dict__
async def update_session_name(self):
"""更新指定会话的自定义名称"""
try:
data = await request.get_json()
session_id = data.get("session_id")
custom_name = data.get("custom_name", "")
if not session_id:
return Response().error("缺少必要参数: session_id").__dict__
# 使用 SessionServiceManager 更新会话名称
SessionServiceManager.set_session_custom_name(session_id, custom_name)
return (
Response()
.ok(
{
"message": f"会话名称已更新为: {custom_name if custom_name.strip() else '已清除自定义名称'}",
"session_id": session_id,
"custom_name": custom_name,
"display_name": SessionServiceManager.get_session_display_name(
session_id,
),
},
)
.__dict__
)
except Exception as e:
error_msg = f"更新会话名称失败: {e!s}\n{traceback.format_exc()}"
logger.error(error_msg)
return Response().error(f"更新会话名称失败: {e!s}").__dict__
async def update_session_status(self):
"""更新指定会话的整体启停状态"""
try:
data = await request.get_json()
session_id = data.get("session_id")
session_enabled = data.get("session_enabled")
if not session_id:
return Response().error("缺少必要参数: session_id").__dict__
if session_enabled is None:
return Response().error("缺少必要参数: session_enabled").__dict__
# 使用 SessionServiceManager 更新会话整体状态
SessionServiceManager.set_session_status(session_id, session_enabled)
return (
Response()
.ok(
{
"message": f"会话整体状态已更新为: {'启用' if session_enabled else '禁用'}",
"session_id": session_id,
"session_enabled": session_enabled,
},
)
.__dict__
)
except Exception as e:
error_msg = f"更新会话整体状态失败: {e!s}\n{traceback.format_exc()}"
logger.error(error_msg)
return Response().error(f"更新会话整体状态失败: {e!s}").__dict__
async def delete_session(self):
"""删除指定会话及其所有相关数据"""
try:
data = await request.get_json()
session_id = data.get("session_id")
if not session_id:
return Response().error("缺少必要参数: session_id").__dict__
# 删除会话的所有相关数据
conversation_manager = self.core_lifecycle.conversation_manager
# 1. 删除会话的所有对话
try:
await conversation_manager.delete_conversations_by_user_id(session_id)
except Exception as e:
logger.warning(f"删除会话 {session_id} 的对话失败: {e!s}")
# 2. 清除会话的偏好设置数据(清空该会话的所有配置)
try:
await sp.clear_async("umo", session_id)
except Exception as e:
logger.warning(f"清除会话 {session_id} 的偏好设置失败: {e!s}")
return (
Response()
.ok(
{
"message": f"会话 {session_id} 及其相关所有对话数据已成功删除",
"session_id": session_id,
},
)
.__dict__
)
except Exception as e:
error_msg = f"删除会话失败: {e!s}\n{traceback.format_exc()}"
logger.error(error_msg)
return Response().error(f"删除会话失败: {e!s}").__dict__

View File

@@ -8,7 +8,7 @@
"chat": "Chat", "chat": "Chat",
"extension": "Extensions", "extension": "Extensions",
"conversation": "Conversations", "conversation": "Conversations",
"sessionManagement": "Session Management", "sessionManagement": "Custom Rules",
"console": "Console", "console": "Console",
"alkaid": "Alkaid Lab", "alkaid": "Alkaid Lab",
"knowledgeBase": "Knowledge Base", "knowledgeBase": "Knowledge Base",

View File

@@ -1,124 +1,96 @@
{ {
"title": "Session Management", "title": "Custom Rules",
"subtitle": "Manage active sessions and configurations", "subtitle": "Set custom rules for specific sessions, which take priority over global settings",
"buttons": { "buttons": {
"refresh": "Refresh", "refresh": "Refresh",
"edit": "Edit", "edit": "Edit",
"apply": "Apply Batch Settings", "editRule": "Edit Rules",
"editName": "Edit Session Name", "deleteAllRules": "Delete All Rules",
"addRule": "Add Rule",
"save": "Save", "save": "Save",
"cancel": "Cancel", "cancel": "Cancel",
"delete": "Delete" "delete": "Delete",
"clear": "Clear",
"next": "Next",
"editCustomName": "Edit Note",
"batchDelete": "Batch Delete"
}, },
"sessions": { "customRules": {
"activeSessions": "Active Sessions", "title": "Custom Rules",
"sessionCount": "sessions", "rulesCount": "rules",
"noActiveSessions": "No active sessions", "hasRules": "Configured",
"noActiveSessionsDesc": "Sessions will appear here when users interact with the bot" "noRules": "No Custom Rules",
"noRulesDesc": "Click 'Add Rule' to configure custom rules for specific sessions",
"serviceConfig": "Service Config",
"pluginConfig": "Plugin Config",
"kbConfig": "Knowledge Base",
"providerConfig": "Provider Config",
"configured": "Configured",
"noCustomName": "No note set"
},
"quickEditName": {
"title": "Edit Note"
}, },
"search": { "search": {
"placeholder": "Search sessions...", "placeholder": "Search sessions..."
"platformFilter": "Platform Filter"
}, },
"table": { "table": {
"headers": { "headers": {
"sessionStatus": "Session Status", "umoInfo": "Session Info",
"sessionInfo": "Session Info", "rulesOverview": "Rules Overview",
"persona": "Persona",
"chatProvider": "Chat Provider",
"sttProvider": "STT Provider",
"ttsProvider": "TTS Provider",
"llmStatus": "LLM Status",
"ttsStatus": "TTS Status",
"knowledgeBase": "Knowledge Base",
"pluginManagement": "Plugin Management",
"actions": "Actions" "actions": "Actions"
} }
}, },
"status": {
"enabled": "Enabled",
"disabled": "Disabled"
},
"persona": { "persona": {
"none": "No Persona" "none": "No Persona"
}, },
"batchOperations": { "addRule": {
"title": "Batch Operations", "title": "Add Custom Rule",
"setPersona": "Batch Set Persona", "description": "Select a session (UMO) to configure custom rules. Custom rules take priority over global settings.",
"setChatProvider": "Batch Set Chat Provider", "selectUmo": "Select Session",
"setSttProvider": "Batch Set STT Provider", "noUmos": "No sessions available"
"setTtsProvider": "Batch Set TTS Provider",
"setLlmStatus": "Batch Set LLM Status",
"setTtsStatus": "Batch Set TTS Status",
"noSttProvider": "No STT Provider Available",
"noTtsProvider": "No TTS Provider Available"
}, },
"pluginManagement": { "ruleEditor": {
"title": "Plugin Management", "title": "Edit Custom Rules",
"noPlugins": "No available plugins", "description": "Configure custom rules for this session. These rules take priority over global settings.",
"noPluginsDesc": "Currently no active plugins", "serviceConfig": {
"loading": "Loading plugin list...", "title": "Service Configuration",
"author": "Author" "sessionEnabled": "Enable Session",
}, "llmEnabled": "Enable LLM",
"nameEditor": { "ttsEnabled": "Enable TTS",
"title": "Edit Session Name", "customName": "Custom Name"
"customName": "Custom Name", },
"placeholder": "Enter custom session name (leave empty to use original name)", "providerConfig": {
"originalName": "Original Name", "title": "Provider Configuration",
"fullSessionId": "Full Session ID", "chatProvider": "Chat Provider",
"hint": "Custom names help you easily identify sessions. The small information icon (!) will show the actual UMO when hovering." "sttProvider": "STT Provider",
}, "ttsProvider": "TTS Provider"
"knowledgeBase": { },
"title": "Knowledge Base Configuration", "personaConfig": {
"configure": "Configure", "title": "Persona Configuration",
"selectKB": "Select Knowledge Bases", "selectPersona": "Select Persona",
"selectMultiple": "You can select multiple knowledge bases", "hint": "Persona settings affect the conversation style and behavior of the LLM"
"noKBAvailable": "No knowledge bases available", }
"noKBDesc": "No knowledge bases have been created yet",
"createKB": "Create Knowledge Base",
"advancedSettings": "Advanced Settings",
"topK": "Result Count",
"topKHint": "Number of results to retrieve from knowledge base",
"enableRerank": "Enable Reranking",
"enableRerankHint": "Use reranking model to improve retrieval quality",
"clearConfig": "Clear Configuration",
"save": "Save",
"cancel": "Cancel",
"loading": "Loading knowledge base configuration...",
"description": "Configure knowledge bases for this session. The session will use configured knowledge bases to enhance conversation context.",
"saveSuccess": "Knowledge base configuration saved successfully",
"saveFailed": "Failed to save knowledge base configuration",
"loadFailed": "Failed to load knowledge base configuration",
"clearSuccess": "Knowledge base configuration cleared",
"clearFailed": "Failed to clear knowledge base configuration",
"clearConfirm": "Are you sure you want to clear the knowledge base configuration for this session?"
},
"list": {
"documents": "documents"
}, },
"deleteConfirm": { "deleteConfirm": {
"message": "Are you sure you want to delete session {sessionName}?", "title": "Confirm Delete",
"warning": "This action will permanently delete all chat history and preference settings for this session (except for data linked via plugins), and this cannot be undone. Continue?" "message": "Are you sure you want to delete all custom rules for this session? Global settings will be used after deletion."
},
"batchDeleteConfirm": {
"title": "Confirm Batch Delete",
"message": "Are you sure you want to delete {count} selected rules? Global settings will be used after deletion."
}, },
"messages": { "messages": {
"refreshSuccess": "Session list refreshed", "refreshSuccess": "Data refreshed",
"personaUpdateSuccess": "Persona updated successfully", "loadError": "Failed to load data",
"personaUpdateError": "Failed to update persona", "saveSuccess": "Saved successfully",
"providerUpdateSuccess": "Provider updated successfully", "saveError": "Failed to save",
"providerUpdateError": "Failed to update provider", "clearSuccess": "Cleared successfully",
"sessionStatusSuccess": "Session {status}", "clearError": "Failed to clear",
"llmStatusSuccess": "LLM {status}", "deleteSuccess": "Deleted successfully",
"ttsStatusSuccess": "TTS {status}", "deleteError": "Failed to delete",
"statusUpdateError": "Failed to update status", "noChanges": "No changes to save",
"loadSessionsError": "Failed to load session list", "batchDeleteSuccess": "Batch delete successful",
"batchUpdateSuccess": "Successfully batch updated {count} settings", "batchDeleteError": "Batch delete failed"
"batchUpdatePartial": "Batch update completed, {success} successful, {error} failed",
"loadPluginsError": "Failed to load plugin list",
"pluginStatusSuccess": "Plugin {name} {status}",
"pluginStatusError": "Failed to update plugin status",
"nameUpdateSuccess": "Session name updated successfully",
"nameUpdateError": "Failed to update session name",
"deleteSuccess": "Session deleted successfully",
"deleteError": "Failed to delete session"
} }
} }

View File

@@ -8,7 +8,7 @@
"config": "配置文件", "config": "配置文件",
"chat": "聊天", "chat": "聊天",
"conversation": "对话数据", "conversation": "对话数据",
"sessionManagement": "会话管理", "sessionManagement": "自定义规则",
"console": "控制台", "console": "控制台",
"alkaid": "Alkaid", "alkaid": "Alkaid",
"knowledgeBase": "知识库", "knowledgeBase": "知识库",

View File

@@ -1,124 +1,96 @@
{ {
"title": "会话管理", "title": "自定义规则",
"subtitle": "管理活跃会话和配置", "subtitle": "为特定会话设置自定义规则,优先级高于全局配置",
"buttons": { "buttons": {
"refresh": "刷新", "refresh": "刷新",
"edit": "编辑", "edit": "编辑",
"apply": "应用批量设置", "editRule": "编辑规则",
"editName": "备注", "deleteAllRules": "删除所有规则",
"addRule": "添加规则",
"save": "保存", "save": "保存",
"cancel": "取消", "cancel": "取消",
"delete": "删除" "delete": "删除",
"clear": "清除",
"next": "下一步",
"editCustomName": "编辑备注",
"batchDelete": "批量删除"
}, },
"sessions": { "customRules": {
"activeSessions": "活跃会话", "title": "自定义规则",
"sessionCount": "个会话", "rulesCount": "条规则",
"noActiveSessions": "暂无活跃会话", "hasRules": "已配置",
"noActiveSessionsDesc": "当有用户与机器人交互时,会话将会显示在这里" "noRules": "暂无自定义规则",
"noRulesDesc": "点击「添加规则」为特定会话配置自定义规则",
"serviceConfig": "服务配置",
"pluginConfig": "插件配置",
"kbConfig": "知识库配置",
"providerConfig": "模型配置",
"configured": "已配置",
"noCustomName": "未设置备注"
},
"quickEditName": {
"title": "编辑备注名"
}, },
"search": { "search": {
"placeholder": "搜索会话...", "placeholder": "搜索会话..."
"platformFilter": "平台筛选"
}, },
"table": { "table": {
"headers": { "headers": {
"sessionStatus": "会话状态", "umoInfo": "消息会话来源",
"sessionInfo": "消息会话来源", "rulesOverview": "规则概览",
"persona": "人格",
"chatProvider": "聊天模型",
"sttProvider": "语音识别模型",
"ttsProvider": "语音合成模型",
"llmStatus": "启用 LLM",
"ttsStatus": "启用 TTS",
"knowledgeBase": "知识库配置",
"pluginManagement": "插件管理",
"actions": "操作" "actions": "操作"
} }
}, },
"status": {
"enabled": "已启用",
"disabled": "已禁用"
},
"persona": { "persona": {
"none": "无人格" "none": "无人格"
}, },
"batchOperations": { "addRule": {
"title": "批量操作", "title": "添加自定义规则",
"setPersona": "批量设置人格", "description": "选择一个消息会话来源 (UMO) 来配置自定义规则。自定义规则的优先级高于该来源所属的配置文件中的全局规则。可以使用 /sid 指令获取该来源的 UMO 信息。",
"setChatProvider": "批量设置 Chat Provider", "selectUmo": "选择会话",
"setSttProvider": "批量设置 STT Provider", "noUmos": "暂无可用会话"
"setTtsProvider": "批量设置 TTS Provider",
"setLlmStatus": "批量设置 LLM 状态",
"setTtsStatus": "批量设置 TTS 状态",
"noSttProvider": "暂无可用 STT Provider",
"noTtsProvider": "暂无可用 TTS Provider"
}, },
"pluginManagement": { "ruleEditor": {
"title": "插件管理", "title": "编辑自定义规则",
"noPlugins": "暂无可用插件", "description": "为此会话配置自定义规则,这些规则将优先于全局配置生效。",
"noPluginsDesc": "目前没有激活的插件", "serviceConfig": {
"loading": "加载插件列表中...", "title": "服务配置",
"author": "作者" "sessionEnabled": "启用该消息会话来源的消息处理",
}, "llmEnabled": "启用 LLM",
"nameEditor": { "ttsEnabled": "启用 TTS",
"title": "编辑会话名称", "customName": "消息会话来源备注名称"
"customName": "自定义名称", },
"placeholder": "输入自定义会话名称(留空则使用原始名称)", "providerConfig": {
"originalName": "原始名称", "title": "模型配置",
"fullSessionId": "完整会话ID", "chatProvider": "聊天模型",
"hint": "自定义名称帮助您轻松识别会话。当设置了自定义名称时会显示一个小感叹号标识鼠标悬停时会显示实际的UMO。" "sttProvider": "语音识别模型",
}, "ttsProvider": "语音合成模型"
"knowledgeBase": { },
"title": "知识库配置", "personaConfig": {
"configure": "配置", "title": "人格配置",
"selectKB": "选择知识库", "selectPersona": "选择人格",
"selectMultiple": "可以选择多个知识库", "hint": "人格配置会影响 LLM 的对话风格和行为"
"noKBAvailable": "暂无可用的知识库", }
"noKBDesc": "目前没有创建任何知识库",
"createKB": "创建知识库",
"advancedSettings": "高级配置",
"topK": "返回结果数量",
"topKHint": "从知识库检索的结果数量",
"enableRerank": "启用重排序",
"enableRerankHint": "使用重排序模型提高检索质量",
"clearConfig": "清除配置",
"save": "保存",
"cancel": "取消",
"loading": "加载知识库配置中...",
"description": "为此会话配置使用的知识库。会话将使用配置的知识库来增强对话上下文。",
"saveSuccess": "知识库配置保存成功",
"saveFailed": "保存知识库配置失败",
"loadFailed": "加载知识库配置失败",
"clearSuccess": "知识库配置已清除",
"clearFailed": "清除知识库配置失败",
"clearConfirm": "确定要清除此会话的知识库配置吗?"
},
"list": {
"documents": "篇文档"
}, },
"deleteConfirm": { "deleteConfirm": {
"message": "确定要删除会话 {sessionName} 吗?", "title": "确认删除",
"warning": "此操作将永久删除本次会话的「全部对话记录」与「偏好设置」(插件对会话的关联数据除外),且无法恢复。确认继续?" "message": "确定要删除此会话的所有自定义规则吗?删除后将恢复使用全局配置。"
},
"batchDeleteConfirm": {
"title": "确认批量删除",
"message": "确定要删除选中的 {count} 条规则吗?删除后将恢复使用全局配置。"
}, },
"messages": { "messages": {
"refreshSuccess": "会话列表已刷新", "refreshSuccess": "数据已刷新",
"personaUpdateSuccess": "人格更新成功", "loadError": "加载数据失败",
"personaUpdateError": "人格更新失败", "saveSuccess": "保存成功",
"providerUpdateSuccess": "Provider 更新成功", "saveError": "保存失败",
"providerUpdateError": "Provider 更新失败", "clearSuccess": "已清除",
"sessionStatusSuccess": "会话 {status}", "clearError": "清除失败",
"llmStatusSuccess": "LLM {status}", "deleteSuccess": "删除成功",
"ttsStatusSuccess": "TTS {status}", "deleteError": "删除失败",
"statusUpdateError": "状态更新失败", "noChanges": "没有需要保存的更改",
"loadSessionsError": "加载会话列表失败", "batchDeleteSuccess": "批量删除成功",
"batchUpdateSuccess": "成功批量更新 {count} 项设置", "batchDeleteError": "批量删除失败"
"batchUpdatePartial": "批量更新完成,{success} 项成功,{error} 项失败",
"loadPluginsError": "加载插件列表失败",
"pluginStatusSuccess": "插件 {name} {status}",
"pluginStatusError": "插件状态更新失败",
"nameUpdateSuccess": "会话名称更新成功",
"nameUpdateError": "会话名称更新失败",
"deleteSuccess": "会话删除成功",
"deleteError": "会话删除失败"
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
import builtins import builtins
from astrbot.api import star from astrbot.api import sp, star
from astrbot.api.event import AstrMessageEvent, MessageEventResult from astrbot.api.event import AstrMessageEvent, MessageEventResult
@@ -17,6 +17,13 @@ class PersonaCommands:
default_persona = await self.context.persona_manager.get_default_persona_v3( default_persona = await self.context.persona_manager.get_default_persona_v3(
umo=umo, umo=umo,
) )
force_applied_persona_id = (
await sp.get_async(
scope="umo", scope_id=umo, key="session_service_config", default={}
)
).get("persona_id")
curr_cid_title = "" curr_cid_title = ""
if cid: if cid:
conv = await self.context.conversation_manager.get_conversation( conv = await self.context.conversation_manager.get_conversation(
@@ -36,6 +43,9 @@ class PersonaCommands:
else: else:
curr_persona_name = conv.persona_id curr_persona_name = conv.persona_id
if force_applied_persona_id:
curr_persona_name = f"{curr_persona_name} (自定义规则)"
curr_cid_title = conv.title if conv.title else "新对话" curr_cid_title = conv.title if conv.title else "新对话"
curr_cid_title += f"({cid[:4]})" curr_cid_title += f"({cid[:4]})"
@@ -113,9 +123,15 @@ class PersonaCommands:
message.unified_msg_origin, message.unified_msg_origin,
ps, ps,
) )
force_warn_msg = ""
if force_applied_persona_id:
force_warn_msg = (
"提醒:由于自定义规则,您现在切换的人格将不会生效。"
)
message.set_result( message.set_result(
MessageEventResult().message( MessageEventResult().message(
"设置成功。如果您正在切换到不同的人格,请注意使用 /reset 来清空上下文,防止原人格对话影响现人格。", f"设置成功。如果您正在切换到不同的人格,请注意使用 /reset 来清空上下文,防止原人格对话影响现人格。{force_warn_msg}",
), ),
) )
else: else:

View File

@@ -3,7 +3,7 @@ import copy
import datetime import datetime
import zoneinfo import zoneinfo
from astrbot.api import logger, star from astrbot.api import logger, sp, star
from astrbot.api.event import AstrMessageEvent from astrbot.api.event import AstrMessageEvent
from astrbot.api.message_components import Image, Reply from astrbot.api.message_components import Image, Reply
from astrbot.api.provider import Provider, ProviderRequest from astrbot.api.provider import Provider, ProviderRequest
@@ -21,16 +21,27 @@ class ProcessLLMRequest:
else: else:
logger.info(f"Timezone set to: {self.timezone}") logger.info(f"Timezone set to: {self.timezone}")
def _ensure_persona(self, req: ProviderRequest, cfg: dict): async def _ensure_persona(self, req: ProviderRequest, cfg: dict, umo: str):
"""确保用户人格已加载""" """确保用户人格已加载"""
if not req.conversation: if not req.conversation:
return return
# persona inject # persona inject
persona_id = req.conversation.persona_id or cfg.get("default_personality")
if not persona_id and persona_id != "[%None]": # [%None] 为用户取消人格 # custom rule is preferred
default_persona = self.ctx.persona_manager.selected_default_persona_v3 persona_id = (
if default_persona: await sp.get_async(
persona_id = default_persona["name"] scope="umo", scope_id=umo, key="session_service_config", default={}
)
).get("persona_id")
if not persona_id:
persona_id = req.conversation.persona_id or cfg.get("default_personality")
if not persona_id and persona_id != "[%None]": # [%None] 为用户取消人格
default_persona = self.ctx.persona_manager.selected_default_persona_v3
if default_persona:
persona_id = default_persona["name"]
persona = next( persona = next(
builtins.filter( builtins.filter(
lambda persona: persona["name"] == persona_id, lambda persona: persona["name"] == persona_id,
@@ -152,7 +163,7 @@ class ProcessLLMRequest:
img_cap_prov_id: str = cfg.get("default_image_caption_provider_id") or "" img_cap_prov_id: str = cfg.get("default_image_caption_provider_id") or ""
if req.conversation: if req.conversation:
# inject persona for this request # inject persona for this request
self._ensure_persona(req, cfg) await self._ensure_persona(req, cfg, event.unified_msg_origin)
# image caption # image caption
if img_cap_prov_id and req.image_urls: if img_cap_prov_id and req.image_urls: