feat: 支持删除指定会话以及部分会话管理优化 (#2895)
* feat: add toast notification system with snackbar component * feat: add session deletion functionality * feat: support batch operations for updating session persona, provider, LLM, and TTS statuses fix: #2263 * feat: 修复对话状态关闭,删除对话管理库会导致对话无法恢复 fixes: #2309
This commit is contained in:
@@ -87,17 +87,25 @@ class ConversationManager:
|
||||
unified_msg_origin (str): 统一的消息来源字符串。格式为 platform_name:message_type:session_id
|
||||
conversation_id (str): 对话 ID, 是 uuid 格式的字符串
|
||||
"""
|
||||
f = False
|
||||
if not conversation_id:
|
||||
conversation_id = self.session_conversations.get(unified_msg_origin)
|
||||
if conversation_id:
|
||||
f = True
|
||||
if conversation_id:
|
||||
await self.db.delete_conversation(cid=conversation_id)
|
||||
if f:
|
||||
curr_cid = await self.get_curr_conversation_id(unified_msg_origin)
|
||||
if curr_cid == conversation_id:
|
||||
self.session_conversations.pop(unified_msg_origin, None)
|
||||
await sp.session_remove(unified_msg_origin, "sel_conv_id")
|
||||
|
||||
async def delete_conversations_by_user_id(self, unified_msg_origin: str):
|
||||
"""删除会话的所有对话
|
||||
|
||||
Args:
|
||||
unified_msg_origin (str): 统一的消息来源字符串。格式为 platform_name:message_type:session_id
|
||||
"""
|
||||
await self.db.delete_conversations_by_user_id(user_id=unified_msg_origin)
|
||||
self.session_conversations.pop(unified_msg_origin, None)
|
||||
await sp.session_remove(unified_msg_origin, "sel_conv_id")
|
||||
|
||||
async def get_curr_conversation_id(self, unified_msg_origin: str) -> str | None:
|
||||
"""获取会话当前的对话 ID
|
||||
|
||||
|
||||
@@ -154,6 +154,11 @@ class BaseDatabase(abc.ABC):
|
||||
"""Delete a conversation by its ID."""
|
||||
...
|
||||
|
||||
@abc.abstractmethod
|
||||
async def delete_conversations_by_user_id(self, user_id: str) -> None:
|
||||
"""Delete all conversations for a specific user."""
|
||||
...
|
||||
|
||||
@abc.abstractmethod
|
||||
async def insert_platform_message_history(
|
||||
self,
|
||||
|
||||
@@ -249,6 +249,14 @@ class SQLiteDatabase(BaseDatabase):
|
||||
delete(ConversationV2).where(ConversationV2.conversation_id == cid)
|
||||
)
|
||||
|
||||
async def delete_conversations_by_user_id(self, user_id: str) -> None:
|
||||
async with self.get_db() as session:
|
||||
session: AsyncSession
|
||||
async with session.begin():
|
||||
await session.execute(
|
||||
delete(ConversationV2).where(ConversationV2.user_id == user_id)
|
||||
)
|
||||
|
||||
async def insert_platform_message_history(
|
||||
self,
|
||||
platform_id,
|
||||
|
||||
@@ -11,7 +11,8 @@ class SessionStatusCheckStage(Stage):
|
||||
"""检查会话是否整体启用"""
|
||||
|
||||
async def initialize(self, ctx: PipelineContext) -> None:
|
||||
pass
|
||||
self.ctx = ctx
|
||||
self.conv_mgr = ctx.plugin_manager.context.conversation_manager
|
||||
|
||||
async def process(
|
||||
self, event: AstrMessageEvent
|
||||
@@ -19,4 +20,14 @@ class SessionStatusCheckStage(Stage):
|
||||
# 检查会话是否整体启用
|
||||
if not SessionServiceManager.is_session_enabled(event.unified_msg_origin):
|
||||
logger.debug(f"会话 {event.unified_msg_origin} 已被关闭,已终止事件传播。")
|
||||
|
||||
# workaround for #2309
|
||||
conv_id = await self.conv_mgr.get_curr_conversation_id(
|
||||
event.unified_msg_origin
|
||||
)
|
||||
if not conv_id:
|
||||
await self.conv_mgr.new_conversation(
|
||||
event.unified_msg_origin, platform_id=event.get_platform_id()
|
||||
)
|
||||
|
||||
event.stop_event()
|
||||
|
||||
@@ -52,10 +52,6 @@ class SessionServiceManager:
|
||||
"session_service_config", session_config, scope="umo", scope_id=session_id
|
||||
)
|
||||
|
||||
logger.info(
|
||||
f"会话 {session_id} 的LLM状态已更新为: {'启用' if enabled else '禁用'}"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def should_process_llm_request(event: AstrMessageEvent) -> bool:
|
||||
"""检查是否应该处理LLM请求
|
||||
|
||||
@@ -30,6 +30,7 @@ class SessionManagementRoute(Route):
|
||||
"/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.core_lifecycle = core_lifecycle
|
||||
@@ -180,60 +181,132 @@ class SessionManagementRoute(Route):
|
||||
logger.error(error_msg)
|
||||
return Response().error(f"获取会话列表失败: {str(e)}").__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} 失败: {str(e)}")
|
||||
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__
|
||||
)
|
||||
else:
|
||||
return (
|
||||
Response()
|
||||
.ok(
|
||||
{
|
||||
"message": f"成功批量{operation_name} {success_count} 个会话",
|
||||
"success_count": success_count,
|
||||
}
|
||||
)
|
||||
.__dict__
|
||||
)
|
||||
|
||||
async def update_session_persona(self):
|
||||
"""更新指定会话的 persona"""
|
||||
"""更新指定会话的 persona,支持批量操作"""
|
||||
try:
|
||||
data = await request.get_json()
|
||||
session_id = data.get("session_id")
|
||||
is_batch = data.get("is_batch", False)
|
||||
persona_name = data.get("persona_name")
|
||||
|
||||
if not session_id:
|
||||
return Response().error("缺少必要参数: session_id").__dict__
|
||||
|
||||
if persona_name is None:
|
||||
return Response().error("缺少必要参数: persona_name").__dict__
|
||||
|
||||
# 获取会话当前的对话 ID
|
||||
conversation_manager = self.core_lifecycle.star_context.conversation_manager
|
||||
conversation_id = await conversation_manager.get_curr_conversation_id(
|
||||
session_id
|
||||
)
|
||||
if is_batch:
|
||||
session_ids = data.get("session_ids", [])
|
||||
if not session_ids:
|
||||
return Response().error("缺少必要参数: session_ids").__dict__
|
||||
|
||||
if not conversation_id:
|
||||
# 如果没有对话,创建一个新的对话
|
||||
conversation_id = await conversation_manager.new_conversation(
|
||||
session_id
|
||||
return await self._handle_batch_operation(
|
||||
session_ids,
|
||||
self._update_single_session_persona,
|
||||
"更新人格",
|
||||
persona_name=persona_name,
|
||||
)
|
||||
else:
|
||||
session_id = data.get("session_id")
|
||||
if not session_id:
|
||||
return Response().error("缺少必要参数: session_id").__dict__
|
||||
|
||||
# 更新 persona
|
||||
await conversation_manager.update_conversation_persona_id(
|
||||
session_id, persona_name
|
||||
)
|
||||
|
||||
return (
|
||||
Response()
|
||||
.ok({"message": f"成功更新会话 {session_id} 的人格为 {persona_name}"})
|
||||
.__dict__
|
||||
)
|
||||
await self._update_single_session_persona(session_id, persona_name)
|
||||
return (
|
||||
Response()
|
||||
.ok(
|
||||
{
|
||||
"message": f"成功更新会话 {session_id} 的人格为 {persona_name}"
|
||||
}
|
||||
)
|
||||
.__dict__
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"更新会话人格失败: {str(e)}\n{traceback.format_exc()}"
|
||||
logger.error(error_msg)
|
||||
return Response().error(f"更新会话人格失败: {str(e)}").__dict__
|
||||
|
||||
async def _update_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_provider(self):
|
||||
"""更新指定会话的 provider"""
|
||||
"""更新指定会话的 provider,支持批量操作"""
|
||||
try:
|
||||
data = await request.get_json()
|
||||
session_id = data.get("session_id")
|
||||
is_batch = data.get("is_batch", False)
|
||||
provider_id = data.get("provider_id")
|
||||
# "chat_completion", "speech_to_text", "text_to_speech"
|
||||
provider_type = data.get("provider_type")
|
||||
|
||||
if not session_id or not provider_id or not provider_type:
|
||||
if not provider_id or not provider_type:
|
||||
return (
|
||||
Response()
|
||||
.error("缺少必要参数: session_id, provider_id, provider_type")
|
||||
.error("缺少必要参数: provider_id, provider_type")
|
||||
.__dict__
|
||||
)
|
||||
|
||||
@@ -251,23 +324,35 @@ class SessionManagementRoute(Route):
|
||||
.__dict__
|
||||
)
|
||||
|
||||
# 设置 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,
|
||||
)
|
||||
if is_batch:
|
||||
session_ids = data.get("session_ids", [])
|
||||
if not session_ids:
|
||||
return Response().error("缺少必要参数: session_ids").__dict__
|
||||
|
||||
return (
|
||||
Response()
|
||||
.ok(
|
||||
{
|
||||
"message": f"成功更新会话 {session_id} 的 {provider_type} 提供商为 {provider_id}"
|
||||
}
|
||||
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,
|
||||
)
|
||||
else:
|
||||
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__
|
||||
)
|
||||
.__dict__
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"更新会话提供商失败: {str(e)}\n{traceback.format_exc()}"
|
||||
@@ -376,66 +461,98 @@ class SessionManagementRoute(Route):
|
||||
logger.error(error_msg)
|
||||
return Response().error(f"更新会话插件状态失败: {str(e)}").__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 update_session_llm(self):
|
||||
"""更新指定会话的LLM启停状态"""
|
||||
"""更新指定会话的LLM启停状态,支持批量操作"""
|
||||
try:
|
||||
data = await request.get_json()
|
||||
session_id = data.get("session_id")
|
||||
is_batch = data.get("is_batch", False)
|
||||
enabled = data.get("enabled")
|
||||
|
||||
if not session_id:
|
||||
return Response().error("缺少必要参数: session_id").__dict__
|
||||
|
||||
if enabled is None:
|
||||
return Response().error("缺少必要参数: enabled").__dict__
|
||||
|
||||
# 使用 SessionServiceManager 更新LLM状态
|
||||
SessionServiceManager.set_llm_status_for_session(session_id, enabled)
|
||||
if is_batch:
|
||||
session_ids = data.get("session_ids", [])
|
||||
if not session_ids:
|
||||
return Response().error("缺少必要参数: session_ids").__dict__
|
||||
|
||||
return (
|
||||
Response()
|
||||
.ok(
|
||||
{
|
||||
"message": f"LLM已{'启用' if enabled else '禁用'}",
|
||||
"session_id": session_id,
|
||||
"llm_enabled": enabled,
|
||||
}
|
||||
result = await self._handle_batch_operation(
|
||||
session_ids,
|
||||
self._update_single_session_llm,
|
||||
f"{'启用' if enabled else '禁用'}LLM",
|
||||
enabled=enabled,
|
||||
)
|
||||
return result
|
||||
else:
|
||||
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__
|
||||
)
|
||||
.__dict__
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"更新会话LLM状态失败: {str(e)}\n{traceback.format_exc()}"
|
||||
logger.error(error_msg)
|
||||
return Response().error(f"更新会话LLM状态失败: {str(e)}").__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启停状态"""
|
||||
"""更新指定会话的TTS启停状态,支持批量操作"""
|
||||
try:
|
||||
data = await request.get_json()
|
||||
session_id = data.get("session_id")
|
||||
is_batch = data.get("is_batch", False)
|
||||
enabled = data.get("enabled")
|
||||
|
||||
if not session_id:
|
||||
return Response().error("缺少必要参数: session_id").__dict__
|
||||
|
||||
if enabled is None:
|
||||
return Response().error("缺少必要参数: enabled").__dict__
|
||||
|
||||
# 使用 SessionServiceManager 更新TTS状态
|
||||
SessionServiceManager.set_tts_status_for_session(session_id, enabled)
|
||||
if is_batch:
|
||||
session_ids = data.get("session_ids", [])
|
||||
if not session_ids:
|
||||
return Response().error("缺少必要参数: session_ids").__dict__
|
||||
|
||||
return (
|
||||
Response()
|
||||
.ok(
|
||||
{
|
||||
"message": f"TTS已{'启用' if enabled else '禁用'}",
|
||||
"session_id": session_id,
|
||||
"tts_enabled": enabled,
|
||||
}
|
||||
result = await self._handle_batch_operation(
|
||||
session_ids,
|
||||
self._update_single_session_tts,
|
||||
f"{'启用' if enabled else '禁用'}TTS",
|
||||
enabled=enabled,
|
||||
)
|
||||
return result
|
||||
else:
|
||||
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__
|
||||
)
|
||||
.__dict__
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"更新会话TTS状态失败: {str(e)}\n{traceback.format_exc()}"
|
||||
@@ -507,3 +624,43 @@ class SessionManagementRoute(Route):
|
||||
error_msg = f"更新会话整体状态失败: {str(e)}\n{traceback.format_exc()}"
|
||||
logger.error(error_msg)
|
||||
return Response().error(f"更新会话整体状态失败: {str(e)}").__dict__
|
||||
|
||||
async def 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} 的对话失败: {str(e)}")
|
||||
|
||||
# 2. 清除会话的偏好设置数据(清空该会话的所有配置)
|
||||
try:
|
||||
await sp.clear_async("umo", session_id)
|
||||
except Exception as e:
|
||||
logger.warning(f"清除会话 {session_id} 的偏好设置失败: {str(e)}")
|
||||
|
||||
return (
|
||||
Response()
|
||||
.ok(
|
||||
{
|
||||
"message": f"会话 {session_id} 及其相关所有对话数据已成功删除",
|
||||
"session_id": session_id,
|
||||
}
|
||||
)
|
||||
.__dict__
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"删除会话失败: {str(e)}\n{traceback.format_exc()}"
|
||||
logger.error(error_msg)
|
||||
return Response().error(f"删除会话失败: {str(e)}").__dict__
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
"apply": "Apply Batch Settings",
|
||||
"editName": "Edit Session Name",
|
||||
"save": "Save",
|
||||
"cancel": "Cancel"
|
||||
"cancel": "Cancel",
|
||||
"delete": "Delete"
|
||||
},
|
||||
"sessions": {
|
||||
"activeSessions": "Active Sessions",
|
||||
@@ -29,7 +30,8 @@
|
||||
"ttsProvider": "TTS Provider",
|
||||
"llmStatus": "LLM Status",
|
||||
"ttsStatus": "TTS Status",
|
||||
"pluginManagement": "Plugin Management"
|
||||
"pluginManagement": "Plugin Management",
|
||||
"actions": "Actions"
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
@@ -65,6 +67,10 @@
|
||||
"fullSessionId": "Full Session ID",
|
||||
"hint": "Custom names help you easily identify sessions. The small information icon (!) will show the actual UMO when hovering."
|
||||
},
|
||||
"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?"
|
||||
},
|
||||
"messages": {
|
||||
"refreshSuccess": "Session list refreshed",
|
||||
"personaUpdateSuccess": "Persona updated successfully",
|
||||
@@ -82,6 +88,8 @@
|
||||
"pluginStatusSuccess": "Plugin {name} {status}",
|
||||
"pluginStatusError": "Failed to update plugin status",
|
||||
"nameUpdateSuccess": "Session name updated successfully",
|
||||
"nameUpdateError": "Failed to update session name"
|
||||
"nameUpdateError": "Failed to update session name",
|
||||
"deleteSuccess": "Session deleted successfully",
|
||||
"deleteError": "Failed to delete session"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
"apply": "应用批量设置",
|
||||
"editName": "备注",
|
||||
"save": "保存",
|
||||
"cancel": "取消"
|
||||
"cancel": "取消",
|
||||
"delete": "删除"
|
||||
},
|
||||
"sessions": {
|
||||
"activeSessions": "活跃会话",
|
||||
@@ -29,7 +30,8 @@
|
||||
"ttsProvider": "语音合成模型",
|
||||
"llmStatus": "启用 LLM",
|
||||
"ttsStatus": "启用 TTS",
|
||||
"pluginManagement": "插件管理"
|
||||
"pluginManagement": "插件管理",
|
||||
"actions": "操作"
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
@@ -65,6 +67,10 @@
|
||||
"fullSessionId": "完整会话ID",
|
||||
"hint": "自定义名称帮助您轻松识别会话。当设置了自定义名称时,会显示一个小感叹号标识(!),鼠标悬停时会显示实际的UMO。"
|
||||
},
|
||||
"deleteConfirm": {
|
||||
"message": "确定要删除会话 {sessionName} 吗?",
|
||||
"warning": "此操作将永久删除本次会话的「全部对话记录」与「偏好设置」(插件对会话的关联数据除外),且无法恢复。确认继续?"
|
||||
},
|
||||
"messages": {
|
||||
"refreshSuccess": "会话列表已刷新",
|
||||
"personaUpdateSuccess": "人格更新成功",
|
||||
@@ -82,6 +88,8 @@
|
||||
"pluginStatusSuccess": "插件 {name} {status}",
|
||||
"pluginStatusError": "插件状态更新失败",
|
||||
"nameUpdateSuccess": "会话名称更新成功",
|
||||
"nameUpdateError": "会话名称更新失败"
|
||||
"nameUpdateError": "会话名称更新失败",
|
||||
"deleteSuccess": "会话删除成功",
|
||||
"deleteError": "会话删除失败"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -601,19 +601,15 @@ export default {
|
||||
}
|
||||
|
||||
if (this.search) {
|
||||
params.search = this.search;
|
||||
params.search = this.search.trim();
|
||||
}
|
||||
|
||||
// 添加排除条件
|
||||
params.exclude_ids = 'astrbot';
|
||||
params.exclude_platforms = 'webchat';
|
||||
|
||||
console.log(`正在请求对话列表: /api/conversation/list 参数:`, params);
|
||||
|
||||
const response = await axios.get('/api/conversation/list', { params });
|
||||
|
||||
console.log('收到对话列表响应:', response.data);
|
||||
|
||||
this.lastAppliedFilters = { ...this.currentFilters }; // 记录已应用的筛选条件
|
||||
|
||||
if (response.data.status === "ok") {
|
||||
|
||||
@@ -141,6 +141,17 @@
|
||||
</v-btn>
|
||||
</template>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<template v-slot:item.actions="{ item }">
|
||||
<v-btn size="x-small" variant="tonal" color="error" @click="deleteSession(item)"
|
||||
:loading="item.deleting" icon>
|
||||
<v-icon>mdi-delete</v-icon>
|
||||
<v-tooltip activator="parent" location="top">
|
||||
{{ tm('buttons.delete') }}
|
||||
</v-tooltip>
|
||||
</v-btn>
|
||||
</template>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<template v-slot:no-data>
|
||||
<div class="text-center py-8">
|
||||
@@ -409,6 +420,7 @@ export default {
|
||||
{ title: this.tm('table.headers.llmStatus'), key: 'llm_enabled', sortable: false, minWidth: '120px' },
|
||||
{ title: this.tm('table.headers.ttsStatus'), key: 'tts_enabled', sortable: false, minWidth: '120px' },
|
||||
{ title: this.tm('table.headers.pluginManagement'), key: 'plugins', sortable: false, minWidth: '120px' },
|
||||
{ title: this.tm('table.headers.actions'), key: 'actions', sortable: false, minWidth: '100px' },
|
||||
]
|
||||
},
|
||||
|
||||
@@ -418,12 +430,13 @@ export default {
|
||||
|
||||
// 搜索筛选
|
||||
if (this.searchQuery) {
|
||||
const query = this.searchQuery.toLowerCase();
|
||||
const query = this.searchQuery.toLowerCase().trim();
|
||||
filtered = filtered.filter(session =>
|
||||
session.session_name.toLowerCase().includes(query) ||
|
||||
session.platform.toLowerCase().includes(query) ||
|
||||
session.persona_name?.toLowerCase().includes(query) ||
|
||||
session.chat_provider_name?.toLowerCase().includes(query)
|
||||
session.chat_provider_name?.toLowerCase().includes(query) ||
|
||||
session.session_id.toLowerCase().includes(query)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -487,7 +500,8 @@ export default {
|
||||
this.sessions = data.sessions.map(session => ({
|
||||
...session,
|
||||
updating: false, // 添加更新状态标志
|
||||
loadingPlugins: false // 添加插件加载状态标志
|
||||
loadingPlugins: false, // 添加插件加载状态标志
|
||||
deleting: false // 添加删除状态标志
|
||||
}));
|
||||
this.availablePersonas = data.available_personas;
|
||||
this.availableChatProviders = data.available_chat_providers;
|
||||
@@ -508,60 +522,131 @@ export default {
|
||||
},
|
||||
|
||||
async updatePersona(session, personaName) {
|
||||
session.updating = true;
|
||||
try {
|
||||
const response = await axios.post('/api/session/update_persona', {
|
||||
session_id: session.session_id,
|
||||
persona_name: personaName
|
||||
});
|
||||
|
||||
if (response.data.status === 'ok') {
|
||||
session.persona_id = personaName;
|
||||
session.persona_name = personaName === '[%None]' ? this.tm('persona.none') :
|
||||
return this._updateSession('persona', session, { persona_name: personaName }, (s, success) => {
|
||||
if (success) {
|
||||
s.persona_id = personaName;
|
||||
s.persona_name = personaName === '[%None]' ? this.tm('persona.none') :
|
||||
this.availablePersonas.find(p => p.name === personaName)?.name || personaName;
|
||||
this.showSuccess(this.tm('messages.personaUpdateSuccess'));
|
||||
} else {
|
||||
this.showError(response.data.message || this.tm('messages.personaUpdateError'));
|
||||
}
|
||||
} catch (error) {
|
||||
this.showError(error.response?.data?.message || this.tm('messages.personaUpdateError'));
|
||||
}
|
||||
session.updating = false;
|
||||
});
|
||||
},
|
||||
|
||||
async updateProvider(session, providerId, providerType) {
|
||||
session.updating = true;
|
||||
try {
|
||||
const response = await axios.post('/api/session/update_provider', {
|
||||
session_id: session.session_id,
|
||||
provider_id: providerId,
|
||||
provider_type: providerType
|
||||
});
|
||||
|
||||
if (response.data.status === 'ok') {
|
||||
// 更新本地数据
|
||||
return this._updateSession('provider', session, {
|
||||
provider_id: providerId,
|
||||
provider_type: providerType
|
||||
}, (s, success) => {
|
||||
if (success) {
|
||||
if (providerType === 'chat_completion') {
|
||||
session.chat_provider_id = providerId;
|
||||
s.chat_provider_id = providerId;
|
||||
const provider = this.availableChatProviders.find(p => p.id === providerId);
|
||||
session.chat_provider_name = provider?.name || providerId;
|
||||
s.chat_provider_name = provider?.name || providerId;
|
||||
} else if (providerType === 'speech_to_text') {
|
||||
session.stt_provider_id = providerId;
|
||||
s.stt_provider_id = providerId;
|
||||
const provider = this.availableSttProviders.find(p => p.id === providerId);
|
||||
session.stt_provider_name = provider?.name || providerId;
|
||||
s.stt_provider_name = provider?.name || providerId;
|
||||
} else if (providerType === 'text_to_speech') {
|
||||
session.tts_provider_id = providerId;
|
||||
s.tts_provider_id = providerId;
|
||||
const provider = this.availableTtsProviders.find(p => p.id === providerId);
|
||||
session.tts_provider_name = provider?.name || providerId;
|
||||
s.tts_provider_name = provider?.name || providerId;
|
||||
}
|
||||
this.showSuccess(this.tm('messages.providerUpdateSuccess'));
|
||||
} else {
|
||||
this.showError(response.data.message || this.tm('messages.providerUpdateError'));
|
||||
}
|
||||
} catch (error) {
|
||||
this.showError(error.response?.data?.message || this.tm('messages.providerUpdateError'));
|
||||
} session.updating = false;
|
||||
});
|
||||
},
|
||||
|
||||
async updateLLM(session, enabled) {
|
||||
return this._updateSession('llm', session, { enabled }, (s, success) => {
|
||||
if (success) s.llm_enabled = enabled;
|
||||
});
|
||||
},
|
||||
|
||||
async updateTTS(session, enabled) {
|
||||
return this._updateSession('tts', session, { enabled }, (s, success) => {
|
||||
if (success) s.tts_enabled = enabled;
|
||||
});
|
||||
},
|
||||
|
||||
// 通用的更新会话方法,支持单个和批量操作
|
||||
async _updateSession(type, sessionOrSessions, params, updateLocalData) {
|
||||
const isBatch = Array.isArray(sessionOrSessions);
|
||||
|
||||
if (!isBatch) {
|
||||
// 单个操作
|
||||
const session = sessionOrSessions;
|
||||
session.updating = true;
|
||||
|
||||
try {
|
||||
const payload = {
|
||||
is_batch: false,
|
||||
session_id: session.session_id,
|
||||
...params
|
||||
};
|
||||
|
||||
const response = await axios.post(`/api/session/update_${type}`, payload);
|
||||
|
||||
if (response.data.status === 'ok') {
|
||||
updateLocalData(session, true);
|
||||
this.showSuccess(this.tm(`messages.${type}UpdateSuccess`));
|
||||
return { success: true };
|
||||
} else {
|
||||
this.showError(response.data.message || this.tm(`messages.${type}UpdateError`));
|
||||
return { success: false, error: response.data.message };
|
||||
}
|
||||
} catch (error) {
|
||||
this.showError(error.response?.data?.message || this.tm(`messages.${type}UpdateError`));
|
||||
return { success: false, error: error.message };
|
||||
} finally {
|
||||
session.updating = false;
|
||||
}
|
||||
} else {
|
||||
// 批量操作
|
||||
const sessions = sessionOrSessions;
|
||||
const sessionIds = sessions.map(s => s.session_id);
|
||||
|
||||
try {
|
||||
const payload = {
|
||||
is_batch: true,
|
||||
session_ids: sessionIds,
|
||||
...params
|
||||
};
|
||||
|
||||
const response = await axios.post(`/api/session/update_${type}`, payload);
|
||||
|
||||
if (response.data.status === 'ok') {
|
||||
const data = response.data.data;
|
||||
|
||||
// 更新成功的会话的本地数据
|
||||
sessions.forEach(session => {
|
||||
const wasSuccessful = !data.error_sessions || !data.error_sessions.includes(session.session_id);
|
||||
updateLocalData(session, wasSuccessful);
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
successCount: data.success_count || 0,
|
||||
errorCount: data.error_count || 0,
|
||||
errorSessions: data.error_sessions || []
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
success: false,
|
||||
error: response.data.message,
|
||||
errorCount: sessionIds.length,
|
||||
successCount: 0
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.response?.data?.message || error.message,
|
||||
errorCount: sessionIds.length,
|
||||
successCount: 0
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 单独的会话状态更新方法(不支持批量操作)
|
||||
async updateSessionStatus(session, enabled) {
|
||||
session.updating = true;
|
||||
try {
|
||||
@@ -572,47 +657,9 @@ export default {
|
||||
|
||||
if (response.data.status === 'ok') {
|
||||
session.session_enabled = enabled;
|
||||
this.showSuccess(this.tm('messages.sessionStatusSuccess', { status: enabled ? this.tm('status.enabled') : this.tm('status.disabled') }));
|
||||
} else {
|
||||
this.showError(response.data.message || this.tm('messages.statusUpdateError'));
|
||||
}
|
||||
} catch (error) {
|
||||
this.showError(error.response?.data?.message || this.tm('messages.statusUpdateError'));
|
||||
}
|
||||
session.updating = false;
|
||||
},
|
||||
|
||||
async updateLLM(session, enabled) {
|
||||
session.updating = true;
|
||||
try {
|
||||
const response = await axios.post('/api/session/update_llm', {
|
||||
session_id: session.session_id,
|
||||
enabled: enabled
|
||||
});
|
||||
|
||||
if (response.data.status === 'ok') {
|
||||
session.llm_enabled = enabled;
|
||||
this.showSuccess(this.tm('messages.llmStatusSuccess', { status: enabled ? this.tm('status.enabled') : this.tm('status.disabled') }));
|
||||
} else {
|
||||
this.showError(response.data.message || this.tm('messages.statusUpdateError'));
|
||||
}
|
||||
} catch (error) {
|
||||
this.showError(error.response?.data?.message || this.tm('messages.statusUpdateError'));
|
||||
}
|
||||
session.updating = false;
|
||||
},
|
||||
|
||||
async updateTTS(session, enabled) {
|
||||
session.updating = true;
|
||||
try {
|
||||
const response = await axios.post('/api/session/update_tts', {
|
||||
session_id: session.session_id,
|
||||
enabled: enabled
|
||||
});
|
||||
|
||||
if (response.data.status === 'ok') {
|
||||
session.tts_enabled = enabled;
|
||||
this.showSuccess(this.tm('messages.ttsStatusSuccess', { status: enabled ? this.tm('status.enabled') : this.tm('status.disabled') }));
|
||||
this.showSuccess(this.tm('messages.sessionStatusSuccess', {
|
||||
status: enabled ? this.tm('status.enabled') : this.tm('status.disabled')
|
||||
}));
|
||||
} else {
|
||||
this.showError(response.data.message || this.tm('messages.statusUpdateError'));
|
||||
}
|
||||
@@ -628,60 +675,120 @@ export default {
|
||||
}
|
||||
|
||||
this.batchUpdating = true;
|
||||
let successCount = 0;
|
||||
let errorCount = 0;
|
||||
let totalSuccessCount = 0;
|
||||
let totalErrorCount = 0;
|
||||
let allErrorSessions = [];
|
||||
|
||||
// 使用过滤后的会话数据进行批量操作
|
||||
for (const session of this.filteredSessions) {
|
||||
try {
|
||||
// 批量更新人格
|
||||
if (this.batchPersona) {
|
||||
await this.updatePersona(session, this.batchPersona);
|
||||
successCount++;
|
||||
}
|
||||
const sessions = this.filteredSessions;
|
||||
|
||||
// 批量更新 Chat Provider
|
||||
if (this.batchChatProvider) {
|
||||
await this.updateProvider(session, this.batchChatProvider, 'chat_completion');
|
||||
successCount++;
|
||||
}
|
||||
try {
|
||||
// 定义批量操作任务
|
||||
const batchTasks = [];
|
||||
|
||||
// 批量更新 STT Provider
|
||||
if (this.batchSttProvider) {
|
||||
await this.updateProvider(session, this.batchSttProvider, 'speech_to_text');
|
||||
successCount++;
|
||||
}
|
||||
|
||||
// 批量更新 TTS Provider
|
||||
if (this.batchTtsProvider) {
|
||||
await this.updateProvider(session, this.batchTtsProvider, 'text_to_speech');
|
||||
successCount++;
|
||||
}
|
||||
|
||||
// 批量更新 LLM 状态
|
||||
if (this.batchLlmStatus !== null) {
|
||||
await this.updateLLM(session, this.batchLlmStatus);
|
||||
successCount++;
|
||||
}
|
||||
|
||||
// 批量更新 TTS 状态
|
||||
if (this.batchTtsStatus !== null) {
|
||||
await this.updateTTS(session, this.batchTtsStatus);
|
||||
successCount++;
|
||||
}
|
||||
} catch (error) {
|
||||
errorCount++;
|
||||
if (this.batchPersona) {
|
||||
batchTasks.push({
|
||||
type: 'persona',
|
||||
params: { persona_name: this.batchPersona }
|
||||
});
|
||||
}
|
||||
|
||||
if (this.batchChatProvider) {
|
||||
batchTasks.push({
|
||||
type: 'provider',
|
||||
params: { provider_id: this.batchChatProvider, provider_type: 'chat_completion' }
|
||||
});
|
||||
}
|
||||
|
||||
if (this.batchSttProvider) {
|
||||
batchTasks.push({
|
||||
type: 'provider',
|
||||
params: { provider_id: this.batchSttProvider, provider_type: 'speech_to_text' }
|
||||
});
|
||||
}
|
||||
|
||||
if (this.batchTtsProvider) {
|
||||
batchTasks.push({
|
||||
type: 'provider',
|
||||
params: { provider_id: this.batchTtsProvider, provider_type: 'text_to_speech' }
|
||||
});
|
||||
}
|
||||
|
||||
if (this.batchLlmStatus !== null) {
|
||||
batchTasks.push({
|
||||
type: 'llm',
|
||||
params: { enabled: this.batchLlmStatus }
|
||||
});
|
||||
}
|
||||
|
||||
if (this.batchTtsStatus !== null) {
|
||||
batchTasks.push({
|
||||
type: 'tts',
|
||||
params: { enabled: this.batchTtsStatus }
|
||||
});
|
||||
}
|
||||
|
||||
// 执行所有批量任务
|
||||
for (const task of batchTasks) {
|
||||
let updateLocalData;
|
||||
|
||||
// 定义本地数据更新逻辑
|
||||
switch (task.type) {
|
||||
case 'persona':
|
||||
updateLocalData = (s, success) => {
|
||||
if (success) s.persona_id = task.params.persona_name;
|
||||
};
|
||||
break;
|
||||
case 'provider':
|
||||
updateLocalData = (s, success) => {
|
||||
if (!success) return;
|
||||
const { provider_id, provider_type } = task.params;
|
||||
if (provider_type === 'chat_completion') {
|
||||
s.chat_provider_id = provider_id;
|
||||
} else if (provider_type === 'speech_to_text') {
|
||||
s.stt_provider_id = provider_id;
|
||||
} else if (provider_type === 'text_to_speech') {
|
||||
s.tts_provider_id = provider_id;
|
||||
}
|
||||
};
|
||||
break;
|
||||
case 'llm':
|
||||
updateLocalData = (s, success) => {
|
||||
if (success) s.llm_enabled = task.params.enabled;
|
||||
};
|
||||
break;
|
||||
case 'tts':
|
||||
updateLocalData = (s, success) => {
|
||||
if (success) s.tts_enabled = task.params.enabled;
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
const result = await this._updateSession(task.type, sessions, task.params, updateLocalData);
|
||||
|
||||
totalSuccessCount += result.successCount || 0;
|
||||
totalErrorCount += result.errorCount || 0;
|
||||
if (result.errorSessions) {
|
||||
allErrorSessions.push(...result.errorSessions);
|
||||
}
|
||||
}
|
||||
|
||||
// 显示最终结果
|
||||
if (totalErrorCount === 0) {
|
||||
this.showSuccess(this.tm('messages.batchUpdateSuccess', { count: totalSuccessCount }));
|
||||
} else {
|
||||
const uniqueErrorSessions = [...new Set(allErrorSessions)];
|
||||
this.showError(this.tm('messages.batchUpdatePartial', {
|
||||
success: totalSuccessCount,
|
||||
error: uniqueErrorSessions.length
|
||||
}));
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
this.showError(this.tm('messages.batchUpdateError'));
|
||||
}
|
||||
|
||||
this.batchUpdating = false;
|
||||
|
||||
if (errorCount === 0) {
|
||||
this.showSuccess(this.tm('messages.batchUpdateSuccess', { count: successCount }));
|
||||
} else {
|
||||
this.showError(this.tm('messages.batchUpdatePartial', { success: successCount, error: errorCount }));
|
||||
}
|
||||
|
||||
// 清空批量设置
|
||||
this.batchPersona = null;
|
||||
this.batchChatProvider = null;
|
||||
@@ -797,6 +904,38 @@ export default {
|
||||
this.snackbarColor = 'error';
|
||||
this.snackbar = true;
|
||||
},
|
||||
|
||||
async deleteSession(session) {
|
||||
const confirmMessage = this.tm('deleteConfirm.message', {
|
||||
sessionName: session.session_name || session.session_id
|
||||
}) + '\n\n' + this.tm('deleteConfirm.warning');
|
||||
|
||||
if (!confirm(confirmMessage)) {
|
||||
return;
|
||||
}
|
||||
|
||||
session.deleting = true;
|
||||
try {
|
||||
const response = await axios.post('/api/session/delete', {
|
||||
session_id: session.session_id
|
||||
});
|
||||
|
||||
if (response.data.status === 'ok') {
|
||||
this.showSuccess(response.data.data.message || this.tm('messages.deleteSuccess'));
|
||||
// 从列表中移除已删除的会话
|
||||
const index = this.sessions.findIndex(s => s.session_id === session.session_id);
|
||||
if (index > -1) {
|
||||
this.sessions.splice(index, 1);
|
||||
}
|
||||
} else {
|
||||
this.showError(response.data.message || this.tm('messages.deleteError'));
|
||||
}
|
||||
} catch (error) {
|
||||
this.showError(error.response?.data?.message || this.tm('messages.deleteError'));
|
||||
}
|
||||
|
||||
session.deleting = false;
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user