Compare commits
7 Commits
refactor/s
...
fix/3780
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
150a741e92 | ||
|
|
88c1d77f0b | ||
|
|
758ce40cc1 | ||
|
|
3e7bb80492 | ||
|
|
75e95aa9ca | ||
|
|
a389842e25 | ||
|
|
0f6a3c3f5a |
@@ -345,9 +345,6 @@ class MCPClient:
|
||||
|
||||
async def cleanup(self):
|
||||
"""Clean up resources including old exit stacks from reconnections"""
|
||||
# Set running_event first to unblock any waiting tasks
|
||||
self.running_event.set()
|
||||
|
||||
# Close current exit stack
|
||||
try:
|
||||
await self.exit_stack.aclose()
|
||||
@@ -359,6 +356,9 @@ class MCPClient:
|
||||
# Just clear the list to release references
|
||||
self._old_exit_stacks.clear()
|
||||
|
||||
# Set running_event first to unblock any waiting tasks
|
||||
self.running_event.set()
|
||||
|
||||
|
||||
class MCPTool(FunctionTool, Generic[TContext]):
|
||||
"""A function tool that calls an MCP service."""
|
||||
|
||||
@@ -1,16 +1,24 @@
|
||||
import traceback
|
||||
|
||||
from quart import request
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlmodel import col, select
|
||||
|
||||
from astrbot.core import logger, sp
|
||||
from astrbot.core.core_lifecycle import AstrBotCoreLifecycle
|
||||
from astrbot.core.db import BaseDatabase
|
||||
from astrbot.core.db.po import ConversationV2, Preference
|
||||
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
|
||||
|
||||
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):
|
||||
def __init__(
|
||||
@@ -22,667 +30,325 @@ class SessionManagementRoute(Route):
|
||||
super().__init__(context)
|
||||
self.db_helper = db_helper
|
||||
self.routes = {
|
||||
"/session/list": ("GET", self.list_sessions),
|
||||
"/session/update_persona": ("POST", self.update_session_persona),
|
||||
"/session/update_provider": ("POST", self.update_session_provider),
|
||||
"/session/plugins": ("GET", self.get_session_plugins),
|
||||
"/session/update_plugin": ("POST", self.update_session_plugin),
|
||||
"/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),
|
||||
"/session/list-rule": ("GET", self.list_session_rule),
|
||||
"/session/update-rule": ("POST", self.update_session_rule),
|
||||
"/session/delete-rule": ("POST", self.delete_session_rule),
|
||||
"/session/batch-delete-rule": ("POST", self.batch_delete_session_rule),
|
||||
"/session/active-umos": ("GET", self.list_umos),
|
||||
}
|
||||
self.conv_mgr = core_lifecycle.conversation_manager
|
||||
self.core_lifecycle = core_lifecycle
|
||||
self.register_routes()
|
||||
|
||||
async def list_sessions(self):
|
||||
"""获取所有会话的列表,包括 persona 和 provider 信息"""
|
||||
try:
|
||||
page = int(request.args.get("page", 1))
|
||||
page_size = int(request.args.get("page_size", 20))
|
||||
search_query = request.args.get("search", "")
|
||||
platform = request.args.get("platform", "")
|
||||
async def _get_umo_rules(
|
||||
self, page: int = 1, page_size: int = 10, search: str = ""
|
||||
) -> tuple[dict, int]:
|
||||
"""获取所有带有自定义规则的 umo 及其规则内容(支持分页和搜索)。
|
||||
|
||||
# 获取活跃的会话数据(处于对话内的会话)
|
||||
sessions_data, total = await self.db_helper.get_session_conversations(
|
||||
page,
|
||||
page_size,
|
||||
search_query,
|
||||
platform,
|
||||
如果某个 umo 在 preference 中有以下字段,则表示有自定义规则:
|
||||
|
||||
1. session_service_config (包含了 是否启用这个umo, 这个umo是否启用 llm, 这个umo是否启用tts, umo自定义名称。)
|
||||
2. session_plugin_config (包含了 这个 umo 的 plugin set)
|
||||
3. provider_perf_{ProviderType.value} (包含了这个 umo 所选择使用的 provider 信息)
|
||||
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
|
||||
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 = [
|
||||
{"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 = []
|
||||
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(
|
||||
available_chat_providers = [
|
||||
{
|
||||
"message": f"成功批量{operation_name} {success_count} 个会话",
|
||||
"success_count": success_count,
|
||||
},
|
||||
)
|
||||
.__dict__
|
||||
)
|
||||
"id": p.meta().id,
|
||||
"name": p.meta().id,
|
||||
"model": p.meta().model,
|
||||
}
|
||||
for p in provider_manager.provider_insts
|
||||
]
|
||||
|
||||
async def update_session_persona(self):
|
||||
"""更新指定会话的 persona,支持批量操作"""
|
||||
try:
|
||||
data = await request.get_json()
|
||||
is_batch = data.get("is_batch", False)
|
||||
persona_name = data.get("persona_name")
|
||||
available_stt_providers = [
|
||||
{
|
||||
"id": p.meta().id,
|
||||
"name": p.meta().id,
|
||||
"model": p.meta().model,
|
||||
}
|
||||
for p in provider_manager.stt_provider_insts
|
||||
]
|
||||
|
||||
if persona_name is None:
|
||||
return Response().error("缺少必要参数: persona_name").__dict__
|
||||
available_tts_providers = [
|
||||
{
|
||||
"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 (
|
||||
Response()
|
||||
.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__
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"更新会话人格失败: {e!s}\n{traceback.format_exc()}"
|
||||
logger.error(error_msg)
|
||||
return Response().error(f"更新会话人格失败: {e!s}").__dict__
|
||||
logger.error(f"获取规则列表失败: {e!s}")
|
||||
return Response().error(f"获取规则列表失败: {e!s}").__dict__
|
||||
|
||||
async def _update_single_session_provider(
|
||||
self,
|
||||
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_rule(self):
|
||||
"""更新某个 umo 的自定义规则
|
||||
|
||||
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:
|
||||
data = await request.get_json()
|
||||
is_batch = data.get("is_batch", False)
|
||||
provider_id = data.get("provider_id")
|
||||
provider_type = data.get("provider_type")
|
||||
umo = data.get("umo")
|
||||
rule_key = data.get("rule_key")
|
||||
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 (
|
||||
Response()
|
||||
.error("缺少必要参数: provider_id, provider_type")
|
||||
.ok({"message": f"规则 {rule_key} 已删除", "umo": umo})
|
||||
.__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 字符串为枚举
|
||||
if provider_type == "chat_completion":
|
||||
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":
|
||||
provider_type_enum = ProviderType.TEXT_TO_SPEECH
|
||||
async def batch_delete_session_rule(self):
|
||||
"""批量删除多个 umo 的自定义规则
|
||||
|
||||
请求体:
|
||||
{
|
||||
"umos": ["平台:消息类型:会话ID", ...] // umo 列表
|
||||
}
|
||||
"""
|
||||
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:
|
||||
return (
|
||||
Response()
|
||||
.error(f"不支持的 provider_type: {provider_type}")
|
||||
.__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(
|
||||
.ok(
|
||||
{
|
||||
"name": plugin_name,
|
||||
"author": plugin.author,
|
||||
"desc": plugin.desc,
|
||||
"enabled": plugin_enabled,
|
||||
},
|
||||
"message": f"已删除 {deleted_count} 条规则",
|
||||
"deleted_count": deleted_count,
|
||||
}
|
||||
)
|
||||
|
||||
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__
|
||||
)
|
||||
|
||||
# 使用 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"更新会话插件状态失败: {e!s}\n{traceback.format_exc()}"
|
||||
logger.error(error_msg)
|
||||
return Response().error(f"更新会话插件状态失败: {e!s}").__dict__
|
||||
logger.error(f"批量删除会话规则失败: {e!s}")
|
||||
return Response().error(f"批量删除会话规则失败: {e!s}").__dict__
|
||||
|
||||
async def _update_single_session_llm(self, session_id: str, enabled: bool):
|
||||
"""更新单个会话的LLM状态的内部方法"""
|
||||
SessionServiceManager.set_llm_status_for_session(session_id, enabled)
|
||||
async def list_umos(self):
|
||||
"""列出所有有对话记录的 umo,从 Conversations 表中找
|
||||
|
||||
async def update_session_llm(self):
|
||||
"""更新指定会话的LLM启停状态,支持批量操作"""
|
||||
仅返回 umo 字符串列表,用于用户在创建规则时选择 umo
|
||||
"""
|
||||
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_llm,
|
||||
f"{'启用' if enabled else '禁用'}LLM",
|
||||
enabled=enabled,
|
||||
# 从 Conversation 表获取所有 distinct user_id (即 umo)
|
||||
async with self.db_helper.get_db() as session:
|
||||
session: AsyncSession
|
||||
result = await session.execute(
|
||||
select(ConversationV2.user_id)
|
||||
.distinct()
|
||||
.order_by(ConversationV2.user_id)
|
||||
)
|
||||
return result
|
||||
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__
|
||||
)
|
||||
umos = [row[0] for row in result.fetchall()]
|
||||
|
||||
return Response().ok({"umos": umos}).__dict__
|
||||
except Exception as e:
|
||||
error_msg = f"更新会话LLM状态失败: {e!s}\n{traceback.format_exc()}"
|
||||
logger.error(error_msg)
|
||||
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__
|
||||
logger.error(f"获取 UMO 列表失败: {e!s}")
|
||||
return Response().error(f"获取 UMO 列表失败: {e!s}").__dict__
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"chat": "Chat",
|
||||
"extension": "Extensions",
|
||||
"conversation": "Conversations",
|
||||
"sessionManagement": "Session Management",
|
||||
"sessionManagement": "Custom Rules",
|
||||
"console": "Console",
|
||||
"alkaid": "Alkaid Lab",
|
||||
"knowledgeBase": "Knowledge Base",
|
||||
|
||||
@@ -1,124 +1,99 @@
|
||||
{
|
||||
"title": "Session Management",
|
||||
"subtitle": "Manage active sessions and configurations",
|
||||
"title": "Custom Rules",
|
||||
"subtitle": "Set custom rules for specific sessions, which take priority over global settings",
|
||||
"buttons": {
|
||||
"refresh": "Refresh",
|
||||
"edit": "Edit",
|
||||
"apply": "Apply Batch Settings",
|
||||
"editName": "Edit Session Name",
|
||||
"editRule": "Edit Rules",
|
||||
"deleteAllRules": "Delete All Rules",
|
||||
"addRule": "Add Rule",
|
||||
"save": "Save",
|
||||
"cancel": "Cancel",
|
||||
"delete": "Delete"
|
||||
"delete": "Delete",
|
||||
"clear": "Clear",
|
||||
"next": "Next",
|
||||
"editCustomName": "Edit Note",
|
||||
"batchDelete": "Batch Delete"
|
||||
},
|
||||
"sessions": {
|
||||
"activeSessions": "Active Sessions",
|
||||
"sessionCount": "sessions",
|
||||
"noActiveSessions": "No active sessions",
|
||||
"noActiveSessionsDesc": "Sessions will appear here when users interact with the bot"
|
||||
"customRules": {
|
||||
"title": "Custom Rules",
|
||||
"rulesCount": "rules",
|
||||
"hasRules": "Configured",
|
||||
"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": {
|
||||
"placeholder": "Search sessions...",
|
||||
"platformFilter": "Platform Filter"
|
||||
"placeholder": "Search sessions..."
|
||||
},
|
||||
"table": {
|
||||
"headers": {
|
||||
"sessionStatus": "Session Status",
|
||||
"sessionInfo": "Session Info",
|
||||
"persona": "Persona",
|
||||
"chatProvider": "Chat Provider",
|
||||
"sttProvider": "STT Provider",
|
||||
"ttsProvider": "TTS Provider",
|
||||
"llmStatus": "LLM Status",
|
||||
"ttsStatus": "TTS Status",
|
||||
"knowledgeBase": "Knowledge Base",
|
||||
"pluginManagement": "Plugin Management",
|
||||
"umoInfo": "Unified Message Origin",
|
||||
"rulesOverview": "Rules Overview",
|
||||
"actions": "Actions"
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"enabled": "Enabled",
|
||||
"disabled": "Disabled"
|
||||
},
|
||||
"persona": {
|
||||
"none": "No Persona"
|
||||
"none": "Follow Config"
|
||||
},
|
||||
"batchOperations": {
|
||||
"title": "Batch Operations",
|
||||
"setPersona": "Batch Set Persona",
|
||||
"setChatProvider": "Batch Set Chat Provider",
|
||||
"setSttProvider": "Batch Set STT Provider",
|
||||
"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"
|
||||
"provider": {
|
||||
"followConfig": "Follow Config"
|
||||
},
|
||||
"pluginManagement": {
|
||||
"title": "Plugin Management",
|
||||
"noPlugins": "No available plugins",
|
||||
"noPluginsDesc": "Currently no active plugins",
|
||||
"loading": "Loading plugin list...",
|
||||
"author": "Author"
|
||||
"addRule": {
|
||||
"title": "Add Custom Rule",
|
||||
"description": "Select a session (UMO) to configure custom rules. Custom rules take priority over global settings.",
|
||||
"selectUmo": "Select Session",
|
||||
"noUmos": "No sessions available"
|
||||
},
|
||||
"nameEditor": {
|
||||
"title": "Edit Session Name",
|
||||
"customName": "Custom Name",
|
||||
"placeholder": "Enter custom session name (leave empty to use original name)",
|
||||
"originalName": "Original Name",
|
||||
"fullSessionId": "Full Session ID",
|
||||
"hint": "Custom names help you easily identify sessions. The small information icon (!) will show the actual UMO when hovering."
|
||||
},
|
||||
"knowledgeBase": {
|
||||
"title": "Knowledge Base Configuration",
|
||||
"configure": "Configure",
|
||||
"selectKB": "Select Knowledge Bases",
|
||||
"selectMultiple": "You can select multiple knowledge bases",
|
||||
"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"
|
||||
"ruleEditor": {
|
||||
"title": "Edit Custom Rules",
|
||||
"description": "Configure custom rules for this session. These rules take priority over global settings.",
|
||||
"serviceConfig": {
|
||||
"title": "Service Configuration",
|
||||
"sessionEnabled": "Enable Session",
|
||||
"llmEnabled": "Enable LLM",
|
||||
"ttsEnabled": "Enable TTS",
|
||||
"customName": "Custom Name"
|
||||
},
|
||||
"providerConfig": {
|
||||
"title": "Provider Configuration",
|
||||
"chatProvider": "Chat Provider",
|
||||
"sttProvider": "STT Provider",
|
||||
"ttsProvider": "TTS Provider"
|
||||
},
|
||||
"personaConfig": {
|
||||
"title": "Persona Configuration",
|
||||
"selectPersona": "Select Persona",
|
||||
"hint": "Persona settings affect the conversation style and behavior of the LLM"
|
||||
}
|
||||
},
|
||||
"deleteConfirm": {
|
||||
"message": "Are you sure you want to delete session {sessionName}?",
|
||||
"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?"
|
||||
"title": "Confirm Delete",
|
||||
"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": {
|
||||
"refreshSuccess": "Session list refreshed",
|
||||
"personaUpdateSuccess": "Persona updated successfully",
|
||||
"personaUpdateError": "Failed to update persona",
|
||||
"providerUpdateSuccess": "Provider updated successfully",
|
||||
"providerUpdateError": "Failed to update provider",
|
||||
"sessionStatusSuccess": "Session {status}",
|
||||
"llmStatusSuccess": "LLM {status}",
|
||||
"ttsStatusSuccess": "TTS {status}",
|
||||
"statusUpdateError": "Failed to update status",
|
||||
"loadSessionsError": "Failed to load session list",
|
||||
"batchUpdateSuccess": "Successfully batch updated {count} settings",
|
||||
"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"
|
||||
"refreshSuccess": "Data refreshed",
|
||||
"loadError": "Failed to load data",
|
||||
"saveSuccess": "Saved successfully",
|
||||
"saveError": "Failed to save",
|
||||
"clearSuccess": "Cleared successfully",
|
||||
"clearError": "Failed to clear",
|
||||
"deleteSuccess": "Deleted successfully",
|
||||
"deleteError": "Failed to delete",
|
||||
"noChanges": "No changes to save",
|
||||
"batchDeleteSuccess": "Batch delete successful",
|
||||
"batchDeleteError": "Batch delete failed"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"config": "配置文件",
|
||||
"chat": "聊天",
|
||||
"conversation": "对话数据",
|
||||
"sessionManagement": "会话管理",
|
||||
"sessionManagement": "自定义规则",
|
||||
"console": "控制台",
|
||||
"alkaid": "Alkaid",
|
||||
"knowledgeBase": "知识库",
|
||||
|
||||
@@ -1,124 +1,99 @@
|
||||
{
|
||||
"title": "会话管理",
|
||||
"subtitle": "管理活跃会话和配置",
|
||||
"title": "自定义规则",
|
||||
"subtitle": "为特定会话设置自定义规则,优先级高于全局配置",
|
||||
"buttons": {
|
||||
"refresh": "刷新",
|
||||
"edit": "编辑",
|
||||
"apply": "应用批量设置",
|
||||
"editName": "备注",
|
||||
"editRule": "编辑规则",
|
||||
"deleteAllRules": "删除所有规则",
|
||||
"addRule": "添加规则",
|
||||
"save": "保存",
|
||||
"cancel": "取消",
|
||||
"delete": "删除"
|
||||
"delete": "删除",
|
||||
"clear": "清除",
|
||||
"next": "下一步",
|
||||
"editCustomName": "编辑备注",
|
||||
"batchDelete": "批量删除"
|
||||
},
|
||||
"sessions": {
|
||||
"activeSessions": "活跃会话",
|
||||
"sessionCount": "个会话",
|
||||
"noActiveSessions": "暂无活跃会话",
|
||||
"noActiveSessionsDesc": "当有用户与机器人交互时,会话将会显示在这里"
|
||||
"customRules": {
|
||||
"title": "自定义规则",
|
||||
"rulesCount": "条规则",
|
||||
"hasRules": "已配置",
|
||||
"noRules": "暂无自定义规则",
|
||||
"noRulesDesc": "点击「添加规则」为特定会话配置自定义规则",
|
||||
"serviceConfig": "服务配置",
|
||||
"pluginConfig": "插件配置",
|
||||
"kbConfig": "知识库配置",
|
||||
"providerConfig": "模型配置",
|
||||
"configured": "已配置",
|
||||
"noCustomName": "未设置备注"
|
||||
},
|
||||
"quickEditName": {
|
||||
"title": "编辑备注名"
|
||||
},
|
||||
"search": {
|
||||
"placeholder": "搜索会话...",
|
||||
"platformFilter": "平台筛选"
|
||||
"placeholder": "搜索会话..."
|
||||
},
|
||||
"table": {
|
||||
"headers": {
|
||||
"sessionStatus": "会话状态",
|
||||
"sessionInfo": "消息会话来源",
|
||||
"persona": "人格",
|
||||
"chatProvider": "聊天模型",
|
||||
"sttProvider": "语音识别模型",
|
||||
"ttsProvider": "语音合成模型",
|
||||
"llmStatus": "启用 LLM",
|
||||
"ttsStatus": "启用 TTS",
|
||||
"knowledgeBase": "知识库配置",
|
||||
"pluginManagement": "插件管理",
|
||||
"umoInfo": "消息会话来源",
|
||||
"rulesOverview": "规则概览",
|
||||
"actions": "操作"
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"enabled": "已启用",
|
||||
"disabled": "已禁用"
|
||||
},
|
||||
"persona": {
|
||||
"none": "无人格"
|
||||
"none": "跟随配置文件"
|
||||
},
|
||||
"batchOperations": {
|
||||
"title": "批量操作",
|
||||
"setPersona": "批量设置人格",
|
||||
"setChatProvider": "批量设置 Chat Provider",
|
||||
"setSttProvider": "批量设置 STT Provider",
|
||||
"setTtsProvider": "批量设置 TTS Provider",
|
||||
"setLlmStatus": "批量设置 LLM 状态",
|
||||
"setTtsStatus": "批量设置 TTS 状态",
|
||||
"noSttProvider": "暂无可用 STT Provider",
|
||||
"noTtsProvider": "暂无可用 TTS Provider"
|
||||
"provider": {
|
||||
"followConfig": "跟随配置文件"
|
||||
},
|
||||
"pluginManagement": {
|
||||
"title": "插件管理",
|
||||
"noPlugins": "暂无可用插件",
|
||||
"noPluginsDesc": "目前没有激活的插件",
|
||||
"loading": "加载插件列表中...",
|
||||
"author": "作者"
|
||||
"addRule": {
|
||||
"title": "添加自定义规则",
|
||||
"description": "选择一个消息会话来源 (UMO) 来配置自定义规则。自定义规则的优先级高于该来源所属的配置文件中的全局规则。可以使用 /sid 指令获取该来源的 UMO 信息。",
|
||||
"selectUmo": "选择会话",
|
||||
"noUmos": "暂无可用会话"
|
||||
},
|
||||
"nameEditor": {
|
||||
"title": "编辑会话名称",
|
||||
"customName": "自定义名称",
|
||||
"placeholder": "输入自定义会话名称(留空则使用原始名称)",
|
||||
"originalName": "原始名称",
|
||||
"fullSessionId": "完整会话ID",
|
||||
"hint": "自定义名称帮助您轻松识别会话。当设置了自定义名称时,会显示一个小感叹号标识(!),鼠标悬停时会显示实际的UMO。"
|
||||
},
|
||||
"knowledgeBase": {
|
||||
"title": "知识库配置",
|
||||
"configure": "配置",
|
||||
"selectKB": "选择知识库",
|
||||
"selectMultiple": "可以选择多个知识库",
|
||||
"noKBAvailable": "暂无可用的知识库",
|
||||
"noKBDesc": "目前没有创建任何知识库",
|
||||
"createKB": "创建知识库",
|
||||
"advancedSettings": "高级配置",
|
||||
"topK": "返回结果数量",
|
||||
"topKHint": "从知识库检索的结果数量",
|
||||
"enableRerank": "启用重排序",
|
||||
"enableRerankHint": "使用重排序模型提高检索质量",
|
||||
"clearConfig": "清除配置",
|
||||
"save": "保存",
|
||||
"cancel": "取消",
|
||||
"loading": "加载知识库配置中...",
|
||||
"description": "为此会话配置使用的知识库。会话将使用配置的知识库来增强对话上下文。",
|
||||
"saveSuccess": "知识库配置保存成功",
|
||||
"saveFailed": "保存知识库配置失败",
|
||||
"loadFailed": "加载知识库配置失败",
|
||||
"clearSuccess": "知识库配置已清除",
|
||||
"clearFailed": "清除知识库配置失败",
|
||||
"clearConfirm": "确定要清除此会话的知识库配置吗?"
|
||||
},
|
||||
"list": {
|
||||
"documents": "篇文档"
|
||||
"ruleEditor": {
|
||||
"title": "编辑自定义规则",
|
||||
"description": "为此会话配置自定义规则,这些规则将优先于全局配置生效。",
|
||||
"serviceConfig": {
|
||||
"title": "服务配置",
|
||||
"sessionEnabled": "启用该消息会话来源的消息处理",
|
||||
"llmEnabled": "启用 LLM",
|
||||
"ttsEnabled": "启用 TTS",
|
||||
"customName": "消息会话来源备注名称"
|
||||
},
|
||||
"providerConfig": {
|
||||
"title": "模型配置",
|
||||
"chatProvider": "聊天模型",
|
||||
"sttProvider": "语音识别模型",
|
||||
"ttsProvider": "语音合成模型"
|
||||
},
|
||||
"personaConfig": {
|
||||
"title": "人格配置",
|
||||
"selectPersona": "选择人格",
|
||||
"hint": "应用人格配置后,将会强制该来源的所有对话使用该人格。"
|
||||
}
|
||||
},
|
||||
"deleteConfirm": {
|
||||
"message": "确定要删除会话 {sessionName} 吗?",
|
||||
"warning": "此操作将永久删除本次会话的「全部对话记录」与「偏好设置」(插件对会话的关联数据除外),且无法恢复。确认继续?"
|
||||
"title": "确认删除",
|
||||
"message": "确定要删除此会话的所有自定义规则吗?删除后将恢复使用全局配置。"
|
||||
},
|
||||
"batchDeleteConfirm": {
|
||||
"title": "确认批量删除",
|
||||
"message": "确定要删除选中的 {count} 条规则吗?删除后将恢复使用全局配置。"
|
||||
},
|
||||
"messages": {
|
||||
"refreshSuccess": "会话列表已刷新",
|
||||
"personaUpdateSuccess": "人格更新成功",
|
||||
"personaUpdateError": "人格更新失败",
|
||||
"providerUpdateSuccess": "Provider 更新成功",
|
||||
"providerUpdateError": "Provider 更新失败",
|
||||
"sessionStatusSuccess": "会话 {status}",
|
||||
"llmStatusSuccess": "LLM {status}",
|
||||
"ttsStatusSuccess": "TTS {status}",
|
||||
"statusUpdateError": "状态更新失败",
|
||||
"loadSessionsError": "加载会话列表失败",
|
||||
"batchUpdateSuccess": "成功批量更新 {count} 项设置",
|
||||
"batchUpdatePartial": "批量更新完成,{success} 项成功,{error} 项失败",
|
||||
"loadPluginsError": "加载插件列表失败",
|
||||
"pluginStatusSuccess": "插件 {name} {status}",
|
||||
"pluginStatusError": "插件状态更新失败",
|
||||
"nameUpdateSuccess": "会话名称更新成功",
|
||||
"nameUpdateError": "会话名称更新失败",
|
||||
"deleteSuccess": "会话删除成功",
|
||||
"deleteError": "会话删除失败"
|
||||
"refreshSuccess": "数据已刷新",
|
||||
"loadError": "加载数据失败",
|
||||
"saveSuccess": "保存成功",
|
||||
"saveError": "保存失败",
|
||||
"clearSuccess": "已清除",
|
||||
"clearError": "清除失败",
|
||||
"deleteSuccess": "删除成功",
|
||||
"deleteError": "删除失败",
|
||||
"noChanges": "没有需要保存的更改",
|
||||
"batchDeleteSuccess": "批量删除成功",
|
||||
"batchDeleteError": "批量删除失败"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ const sidebarItem: menu[] = [
|
||||
},
|
||||
{
|
||||
title: 'core.navigation.sessionManagement',
|
||||
icon: 'mdi-account-group',
|
||||
icon: 'mdi-pencil-ruler',
|
||||
to: '/session-management'
|
||||
},
|
||||
{
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
import builtins
|
||||
|
||||
from astrbot.api import star
|
||||
from astrbot.api import sp, star
|
||||
from astrbot.api.event import AstrMessageEvent, MessageEventResult
|
||||
|
||||
|
||||
@@ -17,6 +17,13 @@ class PersonaCommands:
|
||||
default_persona = await self.context.persona_manager.get_default_persona_v3(
|
||||
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 = "无"
|
||||
if cid:
|
||||
conv = await self.context.conversation_manager.get_conversation(
|
||||
@@ -36,6 +43,9 @@ class PersonaCommands:
|
||||
else:
|
||||
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 += f"({cid[:4]})"
|
||||
|
||||
@@ -113,9 +123,15 @@ class PersonaCommands:
|
||||
message.unified_msg_origin,
|
||||
ps,
|
||||
)
|
||||
force_warn_msg = ""
|
||||
if force_applied_persona_id:
|
||||
force_warn_msg = (
|
||||
"提醒:由于自定义规则,您现在切换的人格将不会生效。"
|
||||
)
|
||||
|
||||
message.set_result(
|
||||
MessageEventResult().message(
|
||||
"设置成功。如果您正在切换到不同的人格,请注意使用 /reset 来清空上下文,防止原人格对话影响现人格。",
|
||||
f"设置成功。如果您正在切换到不同的人格,请注意使用 /reset 来清空上下文,防止原人格对话影响现人格。{force_warn_msg}",
|
||||
),
|
||||
)
|
||||
else:
|
||||
|
||||
@@ -6,7 +6,7 @@ from collections import defaultdict
|
||||
from astrbot import logger
|
||||
from astrbot.api import star
|
||||
from astrbot.api.event import AstrMessageEvent
|
||||
from astrbot.api.message_components import Image, Plain
|
||||
from astrbot.api.message_components import At, Image, Plain
|
||||
from astrbot.api.platform import MessageType
|
||||
from astrbot.api.provider import Provider, ProviderRequest
|
||||
from astrbot.core.astrbot_config_mgr import AstrBotConfigManager
|
||||
@@ -142,6 +142,8 @@ class LongTermMemory:
|
||||
logger.error(f"获取图片描述失败: {e}")
|
||||
else:
|
||||
parts.append(" [Image]")
|
||||
elif isinstance(comp, At):
|
||||
parts.append(f" [At: {comp.name}]")
|
||||
|
||||
final_message = "".join(parts)
|
||||
logger.debug(f"ltm | {event.unified_msg_origin} | {final_message}")
|
||||
|
||||
@@ -3,7 +3,7 @@ import copy
|
||||
import datetime
|
||||
import zoneinfo
|
||||
|
||||
from astrbot.api import logger, star
|
||||
from astrbot.api import logger, sp, star
|
||||
from astrbot.api.event import AstrMessageEvent
|
||||
from astrbot.api.message_components import Image, Reply
|
||||
from astrbot.api.provider import Provider, ProviderRequest
|
||||
@@ -21,16 +21,26 @@ class ProcessLLMRequest:
|
||||
else:
|
||||
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:
|
||||
return
|
||||
# persona inject
|
||||
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"]
|
||||
|
||||
# custom rule is preferred
|
||||
persona_id = (
|
||||
await sp.get_async(
|
||||
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(
|
||||
builtins.filter(
|
||||
lambda persona: persona["name"] == persona_id,
|
||||
@@ -152,7 +162,7 @@ class ProcessLLMRequest:
|
||||
img_cap_prov_id: str = cfg.get("default_image_caption_provider_id") or ""
|
||||
if req.conversation:
|
||||
# inject persona for this request
|
||||
self._ensure_persona(req, cfg)
|
||||
await self._ensure_persona(req, cfg, event.unified_msg_origin)
|
||||
|
||||
# image caption
|
||||
if img_cap_prov_id and req.image_urls:
|
||||
|
||||
@@ -14,7 +14,7 @@ from astrbot.core.utils.session_waiter import (
|
||||
)
|
||||
|
||||
|
||||
class Waiter(Star):
|
||||
class Main(Star):
|
||||
"""会话控制"""
|
||||
|
||||
def __init__(self, context: Context):
|
||||
|
||||
@@ -21,7 +21,17 @@ async def core_lifecycle_td(tmp_path_factory):
|
||||
log_broker = LogBroker()
|
||||
core_lifecycle = AstrBotCoreLifecycle(log_broker, db)
|
||||
await core_lifecycle.initialize()
|
||||
return core_lifecycle
|
||||
try:
|
||||
yield core_lifecycle
|
||||
finally:
|
||||
# 优先停止核心生命周期以释放资源(包括关闭 MCP 等后台任务)
|
||||
try:
|
||||
_stop_res = core_lifecycle.stop()
|
||||
if asyncio.iscoroutine(_stop_res):
|
||||
await _stop_res
|
||||
except Exception:
|
||||
# 停止过程中如有异常,不影响后续清理
|
||||
pass
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
|
||||
@@ -39,6 +39,7 @@ def plugin_manager_pm(tmp_path):
|
||||
message_history_manager = MagicMock()
|
||||
persona_manager = MagicMock()
|
||||
astrbot_config_mgr = MagicMock()
|
||||
knowledge_base_manager = MagicMock()
|
||||
|
||||
star_context = Context(
|
||||
event_queue,
|
||||
@@ -50,6 +51,7 @@ def plugin_manager_pm(tmp_path):
|
||||
message_history_manager,
|
||||
persona_manager,
|
||||
astrbot_config_mgr,
|
||||
knowledge_base_manager=knowledge_base_manager,
|
||||
)
|
||||
|
||||
# Create the PluginManager instance
|
||||
|
||||
Reference in New Issue
Block a user