Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9ab11f3f7f |
@@ -35,14 +35,6 @@ class SessionManagementRoute(Route):
|
||||
"/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),
|
||||
"/session/list-all-with-status": ("GET", self.list_all_umos_with_status),
|
||||
"/session/batch-update-service": ("POST", self.batch_update_service),
|
||||
"/session/batch-update-provider": ("POST", self.batch_update_provider),
|
||||
# 分组管理 API
|
||||
"/session/groups": ("GET", self.list_groups),
|
||||
"/session/group/create": ("POST", self.create_group),
|
||||
"/session/group/update": ("POST", self.update_group),
|
||||
"/session/group/delete": ("POST", self.delete_group),
|
||||
}
|
||||
self.conv_mgr = core_lifecycle.conversation_manager
|
||||
self.core_lifecycle = core_lifecycle
|
||||
@@ -399,540 +391,3 @@ class SessionManagementRoute(Route):
|
||||
except Exception as e:
|
||||
logger.error(f"获取 UMO 列表失败: {e!s}")
|
||||
return Response().error(f"获取 UMO 列表失败: {e!s}").__dict__
|
||||
|
||||
async def list_all_umos_with_status(self):
|
||||
"""获取所有有对话记录的 UMO 及其服务状态(支持分页、搜索、筛选)
|
||||
|
||||
Query 参数:
|
||||
page: 页码,默认为 1
|
||||
page_size: 每页数量,默认为 20
|
||||
search: 搜索关键词
|
||||
message_type: 筛选消息类型 (group/private/all)
|
||||
platform: 筛选平台
|
||||
"""
|
||||
try:
|
||||
page = request.args.get("page", 1, type=int)
|
||||
page_size = request.args.get("page_size", 20, type=int)
|
||||
search = request.args.get("search", "", type=str).strip()
|
||||
message_type = request.args.get("message_type", "all", type=str)
|
||||
platform = request.args.get("platform", "", type=str)
|
||||
|
||||
if page < 1:
|
||||
page = 1
|
||||
if page_size < 1:
|
||||
page_size = 20
|
||||
if page_size > 100:
|
||||
page_size = 100
|
||||
|
||||
# 从 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)
|
||||
)
|
||||
all_umos = [row[0] for row in result.fetchall()]
|
||||
|
||||
# 获取所有 umo 的规则配置
|
||||
umo_rules, _ = await self._get_umo_rules(page=1, page_size=99999, search="")
|
||||
|
||||
# 构建带状态的 umo 列表
|
||||
umos_with_status = []
|
||||
for umo in all_umos:
|
||||
parts = umo.split(":")
|
||||
umo_platform = parts[0] if len(parts) >= 1 else "unknown"
|
||||
umo_message_type = parts[1] if len(parts) >= 2 else "unknown"
|
||||
umo_session_id = parts[2] if len(parts) >= 3 else umo
|
||||
|
||||
# 筛选消息类型
|
||||
if message_type != "all":
|
||||
if message_type == "group" and umo_message_type not in [
|
||||
"group",
|
||||
"GroupMessage",
|
||||
]:
|
||||
continue
|
||||
if message_type == "private" and umo_message_type not in [
|
||||
"private",
|
||||
"FriendMessage",
|
||||
"friend",
|
||||
]:
|
||||
continue
|
||||
|
||||
# 筛选平台
|
||||
if platform and umo_platform != platform:
|
||||
continue
|
||||
|
||||
# 获取服务配置
|
||||
rules = umo_rules.get(umo, {})
|
||||
svc_config = rules.get("session_service_config", {})
|
||||
|
||||
custom_name = svc_config.get("custom_name", "") if svc_config else ""
|
||||
session_enabled = (
|
||||
svc_config.get("session_enabled", True) if svc_config else True
|
||||
)
|
||||
llm_enabled = (
|
||||
svc_config.get("llm_enabled", True) if svc_config else True
|
||||
)
|
||||
tts_enabled = (
|
||||
svc_config.get("tts_enabled", True) if svc_config else True
|
||||
)
|
||||
|
||||
# 搜索过滤
|
||||
if search:
|
||||
search_lower = search.lower()
|
||||
if (
|
||||
search_lower not in umo.lower()
|
||||
and search_lower not in custom_name.lower()
|
||||
):
|
||||
continue
|
||||
|
||||
# 获取 provider 配置
|
||||
chat_provider_key = (
|
||||
f"provider_perf_{ProviderType.CHAT_COMPLETION.value}"
|
||||
)
|
||||
tts_provider_key = f"provider_perf_{ProviderType.TEXT_TO_SPEECH.value}"
|
||||
stt_provider_key = f"provider_perf_{ProviderType.SPEECH_TO_TEXT.value}"
|
||||
|
||||
umos_with_status.append(
|
||||
{
|
||||
"umo": umo,
|
||||
"platform": umo_platform,
|
||||
"message_type": umo_message_type,
|
||||
"session_id": umo_session_id,
|
||||
"custom_name": custom_name,
|
||||
"session_enabled": session_enabled,
|
||||
"llm_enabled": llm_enabled,
|
||||
"tts_enabled": tts_enabled,
|
||||
"has_rules": umo in umo_rules,
|
||||
"chat_provider": rules.get(chat_provider_key),
|
||||
"tts_provider": rules.get(tts_provider_key),
|
||||
"stt_provider": rules.get(stt_provider_key),
|
||||
}
|
||||
)
|
||||
|
||||
# 分页
|
||||
total = len(umos_with_status)
|
||||
start_idx = (page - 1) * page_size
|
||||
end_idx = start_idx + page_size
|
||||
paginated = umos_with_status[start_idx:end_idx]
|
||||
|
||||
# 获取可用的平台列表
|
||||
platforms = list({u["platform"] for u in umos_with_status})
|
||||
|
||||
# 获取可用的 providers
|
||||
provider_manager = self.core_lifecycle.provider_manager
|
||||
available_chat_providers = [
|
||||
{"id": p.meta().id, "name": p.meta().id, "model": p.meta().model}
|
||||
for p in provider_manager.provider_insts
|
||||
]
|
||||
available_tts_providers = [
|
||||
{"id": p.meta().id, "name": p.meta().id, "model": p.meta().model}
|
||||
for p in provider_manager.tts_provider_insts
|
||||
]
|
||||
available_stt_providers = [
|
||||
{"id": p.meta().id, "name": p.meta().id, "model": p.meta().model}
|
||||
for p in provider_manager.stt_provider_insts
|
||||
]
|
||||
|
||||
return (
|
||||
Response()
|
||||
.ok(
|
||||
{
|
||||
"sessions": paginated,
|
||||
"total": total,
|
||||
"page": page,
|
||||
"page_size": page_size,
|
||||
"platforms": platforms,
|
||||
"available_chat_providers": available_chat_providers,
|
||||
"available_tts_providers": available_tts_providers,
|
||||
"available_stt_providers": available_stt_providers,
|
||||
}
|
||||
)
|
||||
.__dict__
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"获取会话状态列表失败: {e!s}")
|
||||
return Response().error(f"获取会话状态列表失败: {e!s}").__dict__
|
||||
|
||||
async def batch_update_service(self):
|
||||
"""批量更新多个 UMO 的服务状态 (LLM/TTS/Session)
|
||||
|
||||
请求体:
|
||||
{
|
||||
"umos": ["平台:消息类型:会话ID", ...], // 可选,如果不传则根据 scope 筛选
|
||||
"scope": "all" | "group" | "private" | "custom_group", // 可选,批量范围
|
||||
"group_id": "分组ID", // 当 scope 为 custom_group 时必填
|
||||
"llm_enabled": true/false/null, // 可选,null表示不修改
|
||||
"tts_enabled": true/false/null, // 可选
|
||||
"session_enabled": true/false/null // 可选
|
||||
}
|
||||
"""
|
||||
try:
|
||||
data = await request.get_json()
|
||||
umos = data.get("umos", [])
|
||||
scope = data.get("scope", "")
|
||||
group_id = data.get("group_id", "")
|
||||
llm_enabled = data.get("llm_enabled")
|
||||
tts_enabled = data.get("tts_enabled")
|
||||
session_enabled = data.get("session_enabled")
|
||||
|
||||
# 如果没有任何修改
|
||||
if llm_enabled is None and tts_enabled is None and session_enabled is None:
|
||||
return Response().error("至少需要指定一个要修改的状态").__dict__
|
||||
|
||||
# 如果指定了 scope,获取符合条件的所有 umo
|
||||
if scope and not umos:
|
||||
# 如果是自定义分组
|
||||
if scope == "custom_group":
|
||||
if not group_id:
|
||||
return Response().error("请指定分组 ID").__dict__
|
||||
groups = self._get_groups()
|
||||
if group_id not in groups:
|
||||
return Response().error(f"分组 '{group_id}' 不存在").__dict__
|
||||
umos = groups[group_id].get("umos", [])
|
||||
else:
|
||||
async with self.db_helper.get_db() as session:
|
||||
session: AsyncSession
|
||||
result = await session.execute(
|
||||
select(ConversationV2.user_id).distinct()
|
||||
)
|
||||
all_umos = [row[0] for row in result.fetchall()]
|
||||
|
||||
if scope == "group":
|
||||
umos = [
|
||||
u
|
||||
for u in all_umos
|
||||
if ":group:" in u.lower() or ":groupmessage:" in u.lower()
|
||||
]
|
||||
elif scope == "private":
|
||||
umos = [
|
||||
u
|
||||
for u in all_umos
|
||||
if ":private:" in u.lower() or ":friend" in u.lower()
|
||||
]
|
||||
elif scope == "all":
|
||||
umos = all_umos
|
||||
|
||||
if not umos:
|
||||
return Response().error("没有找到符合条件的会话").__dict__
|
||||
|
||||
# 批量更新
|
||||
success_count = 0
|
||||
failed_umos = []
|
||||
|
||||
for umo in umos:
|
||||
try:
|
||||
# 获取现有配置
|
||||
session_config = (
|
||||
sp.get("session_service_config", {}, scope="umo", scope_id=umo)
|
||||
or {}
|
||||
)
|
||||
|
||||
# 更新状态
|
||||
if llm_enabled is not None:
|
||||
session_config["llm_enabled"] = llm_enabled
|
||||
if tts_enabled is not None:
|
||||
session_config["tts_enabled"] = tts_enabled
|
||||
if session_enabled is not None:
|
||||
session_config["session_enabled"] = session_enabled
|
||||
|
||||
# 保存
|
||||
sp.put(
|
||||
"session_service_config",
|
||||
session_config,
|
||||
scope="umo",
|
||||
scope_id=umo,
|
||||
)
|
||||
success_count += 1
|
||||
except Exception as e:
|
||||
logger.error(f"更新 {umo} 服务状态失败: {e!s}")
|
||||
failed_umos.append(umo)
|
||||
|
||||
status_changes = []
|
||||
if llm_enabled is not None:
|
||||
status_changes.append(f"LLM={'启用' if llm_enabled else '禁用'}")
|
||||
if tts_enabled is not None:
|
||||
status_changes.append(f"TTS={'启用' if tts_enabled else '禁用'}")
|
||||
if session_enabled is not None:
|
||||
status_changes.append(f"会话={'启用' if session_enabled else '禁用'}")
|
||||
|
||||
return (
|
||||
Response()
|
||||
.ok(
|
||||
{
|
||||
"message": f"已更新 {success_count} 个会话 ({', '.join(status_changes)})",
|
||||
"success_count": success_count,
|
||||
"failed_count": len(failed_umos),
|
||||
"failed_umos": failed_umos,
|
||||
}
|
||||
)
|
||||
.__dict__
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"批量更新服务状态失败: {e!s}")
|
||||
return Response().error(f"批量更新服务状态失败: {e!s}").__dict__
|
||||
|
||||
async def batch_update_provider(self):
|
||||
"""批量更新多个 UMO 的 Provider 配置
|
||||
|
||||
请求体:
|
||||
{
|
||||
"umos": ["平台:消息类型:会话ID", ...], // 可选
|
||||
"scope": "all" | "group" | "private", // 可选
|
||||
"provider_type": "chat_completion" | "text_to_speech" | "speech_to_text",
|
||||
"provider_id": "provider_id"
|
||||
}
|
||||
"""
|
||||
try:
|
||||
data = await request.get_json()
|
||||
umos = data.get("umos", [])
|
||||
scope = data.get("scope", "")
|
||||
provider_type = data.get("provider_type")
|
||||
provider_id = data.get("provider_id")
|
||||
|
||||
if not provider_type or not provider_id:
|
||||
return (
|
||||
Response()
|
||||
.error("缺少必要参数: provider_type, provider_id")
|
||||
.__dict__
|
||||
)
|
||||
|
||||
# 转换 provider_type
|
||||
provider_type_map = {
|
||||
"chat_completion": ProviderType.CHAT_COMPLETION,
|
||||
"text_to_speech": ProviderType.TEXT_TO_SPEECH,
|
||||
"speech_to_text": ProviderType.SPEECH_TO_TEXT,
|
||||
}
|
||||
if provider_type not in provider_type_map:
|
||||
return (
|
||||
Response()
|
||||
.error(f"不支持的 provider_type: {provider_type}")
|
||||
.__dict__
|
||||
)
|
||||
|
||||
provider_type_enum = provider_type_map[provider_type]
|
||||
|
||||
# 如果指定了 scope,获取符合条件的所有 umo
|
||||
group_id = data.get("group_id", "")
|
||||
if scope and not umos:
|
||||
# 如果是自定义分组
|
||||
if scope == "custom_group":
|
||||
if not group_id:
|
||||
return Response().error("请指定分组 ID").__dict__
|
||||
groups = self._get_groups()
|
||||
if group_id not in groups:
|
||||
return Response().error(f"分组 '{group_id}' 不存在").__dict__
|
||||
umos = groups[group_id].get("umos", [])
|
||||
else:
|
||||
async with self.db_helper.get_db() as session:
|
||||
session: AsyncSession
|
||||
result = await session.execute(
|
||||
select(ConversationV2.user_id).distinct()
|
||||
)
|
||||
all_umos = [row[0] for row in result.fetchall()]
|
||||
|
||||
if scope == "group":
|
||||
umos = [
|
||||
u
|
||||
for u in all_umos
|
||||
if ":group:" in u.lower() or ":groupmessage:" in u.lower()
|
||||
]
|
||||
elif scope == "private":
|
||||
umos = [
|
||||
u
|
||||
for u in all_umos
|
||||
if ":private:" in u.lower() or ":friend" in u.lower()
|
||||
]
|
||||
elif scope == "all":
|
||||
umos = all_umos
|
||||
|
||||
if not umos:
|
||||
return Response().error("没有找到符合条件的会话").__dict__
|
||||
|
||||
# 批量更新
|
||||
success_count = 0
|
||||
failed_umos = []
|
||||
provider_manager = self.core_lifecycle.provider_manager
|
||||
|
||||
for umo in umos:
|
||||
try:
|
||||
await provider_manager.set_provider(
|
||||
provider_id=provider_id,
|
||||
provider_type=provider_type_enum,
|
||||
umo=umo,
|
||||
)
|
||||
success_count += 1
|
||||
except Exception as e:
|
||||
logger.error(f"更新 {umo} Provider 失败: {e!s}")
|
||||
failed_umos.append(umo)
|
||||
|
||||
return (
|
||||
Response()
|
||||
.ok(
|
||||
{
|
||||
"message": f"已更新 {success_count} 个会话的 {provider_type} 为 {provider_id}",
|
||||
"success_count": success_count,
|
||||
"failed_count": len(failed_umos),
|
||||
"failed_umos": failed_umos,
|
||||
}
|
||||
)
|
||||
.__dict__
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"批量更新 Provider 失败: {e!s}")
|
||||
return Response().error(f"批量更新 Provider 失败: {e!s}").__dict__
|
||||
|
||||
# ==================== 分组管理 API ====================
|
||||
|
||||
def _get_groups(self) -> dict:
|
||||
"""获取所有分组"""
|
||||
return sp.get("session_groups", {})
|
||||
|
||||
def _save_groups(self, groups: dict) -> None:
|
||||
"""保存分组"""
|
||||
sp.put("session_groups", groups)
|
||||
|
||||
async def list_groups(self):
|
||||
"""获取所有分组列表"""
|
||||
try:
|
||||
groups = self._get_groups()
|
||||
# 转换为列表格式,方便前端使用
|
||||
groups_list = []
|
||||
for group_id, group_data in groups.items():
|
||||
groups_list.append(
|
||||
{
|
||||
"id": group_id,
|
||||
"name": group_data.get("name", ""),
|
||||
"umos": group_data.get("umos", []),
|
||||
"umo_count": len(group_data.get("umos", [])),
|
||||
}
|
||||
)
|
||||
return Response().ok({"groups": groups_list}).__dict__
|
||||
except Exception as e:
|
||||
logger.error(f"获取分组列表失败: {e!s}")
|
||||
return Response().error(f"获取分组列表失败: {e!s}").__dict__
|
||||
|
||||
async def create_group(self):
|
||||
"""创建新分组"""
|
||||
try:
|
||||
data = await request.json
|
||||
name = data.get("name", "").strip()
|
||||
umos = data.get("umos", [])
|
||||
|
||||
if not name:
|
||||
return Response().error("分组名称不能为空").__dict__
|
||||
|
||||
groups = self._get_groups()
|
||||
|
||||
# 生成唯一 ID
|
||||
import uuid
|
||||
|
||||
group_id = str(uuid.uuid4())[:8]
|
||||
|
||||
groups[group_id] = {
|
||||
"name": name,
|
||||
"umos": umos,
|
||||
}
|
||||
|
||||
self._save_groups(groups)
|
||||
|
||||
return (
|
||||
Response()
|
||||
.ok(
|
||||
{
|
||||
"message": f"分组 '{name}' 创建成功",
|
||||
"group": {
|
||||
"id": group_id,
|
||||
"name": name,
|
||||
"umos": umos,
|
||||
"umo_count": len(umos),
|
||||
},
|
||||
}
|
||||
)
|
||||
.__dict__
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"创建分组失败: {e!s}")
|
||||
return Response().error(f"创建分组失败: {e!s}").__dict__
|
||||
|
||||
async def update_group(self):
|
||||
"""更新分组(改名、增删成员)"""
|
||||
try:
|
||||
data = await request.json
|
||||
group_id = data.get("id")
|
||||
name = data.get("name")
|
||||
umos = data.get("umos")
|
||||
add_umos = data.get("add_umos", [])
|
||||
remove_umos = data.get("remove_umos", [])
|
||||
|
||||
if not group_id:
|
||||
return Response().error("分组 ID 不能为空").__dict__
|
||||
|
||||
groups = self._get_groups()
|
||||
|
||||
if group_id not in groups:
|
||||
return Response().error(f"分组 '{group_id}' 不存在").__dict__
|
||||
|
||||
group = groups[group_id]
|
||||
|
||||
# 更新名称
|
||||
if name is not None:
|
||||
group["name"] = name.strip()
|
||||
|
||||
# 直接设置 umos 列表
|
||||
if umos is not None:
|
||||
group["umos"] = umos
|
||||
else:
|
||||
# 增量更新
|
||||
current_umos = set(group.get("umos", []))
|
||||
if add_umos:
|
||||
current_umos.update(add_umos)
|
||||
if remove_umos:
|
||||
current_umos.difference_update(remove_umos)
|
||||
group["umos"] = list(current_umos)
|
||||
|
||||
self._save_groups(groups)
|
||||
|
||||
return (
|
||||
Response()
|
||||
.ok(
|
||||
{
|
||||
"message": f"分组 '{group['name']}' 更新成功",
|
||||
"group": {
|
||||
"id": group_id,
|
||||
"name": group["name"],
|
||||
"umos": group["umos"],
|
||||
"umo_count": len(group["umos"]),
|
||||
},
|
||||
}
|
||||
)
|
||||
.__dict__
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"更新分组失败: {e!s}")
|
||||
return Response().error(f"更新分组失败: {e!s}").__dict__
|
||||
|
||||
async def delete_group(self):
|
||||
"""删除分组"""
|
||||
try:
|
||||
data = await request.json
|
||||
group_id = data.get("id")
|
||||
|
||||
if not group_id:
|
||||
return Response().error("分组 ID 不能为空").__dict__
|
||||
|
||||
groups = self._get_groups()
|
||||
|
||||
if group_id not in groups:
|
||||
return Response().error(f"分组 '{group_id}' 不存在").__dict__
|
||||
|
||||
group_name = groups[group_id].get("name", group_id)
|
||||
del groups[group_id]
|
||||
|
||||
self._save_groups(groups)
|
||||
|
||||
return Response().ok({"message": f"分组 '{group_name}' 已删除"}).__dict__
|
||||
except Exception as e:
|
||||
logger.error(f"删除分组失败: {e!s}")
|
||||
return Response().error(f"删除分组失败: {e!s}").__dict__
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
{
|
||||
"title": "Custom Rules",
|
||||
"subtitle": "Set custom rules for specific sessions, which take priority over global settings",
|
||||
"buttons": {
|
||||
@@ -93,42 +93,6 @@
|
||||
"batchDeleteConfirm": {
|
||||
"title": "Confirm Batch Delete",
|
||||
"message": "Are you sure you want to delete {count} selected rules? Global settings will be used after deletion."
|
||||
},
|
||||
"batchOperations": {
|
||||
"title": "Batch Operations",
|
||||
"hint": "Quick batch modify session settings",
|
||||
"scope": "Apply to",
|
||||
"scopeSelected": "Selected sessions",
|
||||
"scopeAll": "All sessions",
|
||||
"scopeGroup": "All groups",
|
||||
"scopePrivate": "All private chats",
|
||||
"llmStatus": "LLM Status",
|
||||
"ttsStatus": "TTS Status",
|
||||
"chatProvider": "Chat Model",
|
||||
"ttsProvider": "TTS Model",
|
||||
"apply": "Apply Changes"
|
||||
},
|
||||
"status": {
|
||||
"enabled": "Enabled",
|
||||
"disabled": "Disabled"
|
||||
},
|
||||
"batchOperations": {
|
||||
"title": "Batch Operations",
|
||||
"hint": "Quick batch modify session settings",
|
||||
"scope": "Apply to",
|
||||
"scopeSelected": "Selected sessions",
|
||||
"scopeAll": "All sessions",
|
||||
"scopeGroup": "All groups",
|
||||
"scopePrivate": "All private chats",
|
||||
"llmStatus": "LLM Status",
|
||||
"ttsStatus": "TTS Status",
|
||||
"chatProvider": "Chat Model",
|
||||
"ttsProvider": "TTS Model",
|
||||
"apply": "Apply Changes"
|
||||
},
|
||||
"status": {
|
||||
"enabled": "Enabled",
|
||||
"disabled": "Disabled"
|
||||
},
|
||||
"messages": {
|
||||
"refreshSuccess": "Data refreshed",
|
||||
@@ -141,8 +105,6 @@
|
||||
"deleteError": "Failed to delete",
|
||||
"noChanges": "No changes to save",
|
||||
"batchDeleteSuccess": "Batch delete successful",
|
||||
"batchDeleteError": "Batch delete failed",
|
||||
"batchUpdateError": "Batch update failed",
|
||||
"batchUpdateSuccess": "Batch update success"
|
||||
"batchDeleteError": "Batch delete failed"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
{
|
||||
"title": "自定义规则",
|
||||
"subtitle": "为特定会话设置自定义规则,优先级高于全局配置",
|
||||
"buttons": {
|
||||
@@ -94,24 +94,6 @@
|
||||
"title": "确认批量删除",
|
||||
"message": "确定要删除选中的 {count} 条规则吗?删除后将恢复使用全局配置。"
|
||||
},
|
||||
"batchOperations": {
|
||||
"title": "批量操作",
|
||||
"hint": "快速批量修改会话配置",
|
||||
"scope": "应用范围",
|
||||
"scopeSelected": "选中的会话",
|
||||
"scopeAll": "所有会话",
|
||||
"scopeGroup": "所有群聊",
|
||||
"scopePrivate": "所有私聊",
|
||||
"llmStatus": "LLM 状态",
|
||||
"ttsStatus": "TTS 状态",
|
||||
"chatProvider": "聊天模型",
|
||||
"ttsProvider": "TTS 模型",
|
||||
"apply": "应用更改"
|
||||
},
|
||||
"status": {
|
||||
"enabled": "启用",
|
||||
"disabled": "禁用"
|
||||
},
|
||||
"messages": {
|
||||
"refreshSuccess": "数据已刷新",
|
||||
"loadError": "加载数据失败",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<template>
|
||||
<template>
|
||||
<div class="session-management-page">
|
||||
<v-container fluid class="pa-0">
|
||||
<v-card flat>
|
||||
@@ -111,160 +111,6 @@
|
||||
</v-data-table-server>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
<!-- 批量操作面板 -->
|
||||
<v-card flat class="mt-4">
|
||||
<v-card-title class="d-flex align-center py-3 px-4">
|
||||
<span class="text-h6">{{ tm('batchOperations.title') }}</span>
|
||||
<v-chip size="small" class="ml-2" color="info" variant="outlined">
|
||||
{{ tm('batchOperations.hint') }}
|
||||
</v-chip>
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-row dense>
|
||||
<v-col cols="12" md="6" lg="3">
|
||||
<v-select v-model="batchScope" :items="batchScopeOptions" item-title="label" item-value="value"
|
||||
:label="tm('batchOperations.scope')" hide-details variant="solo-filled" flat density="comfortable">
|
||||
</v-select>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6" lg="3">
|
||||
<v-select v-model="batchLlmStatus" :items="statusOptions" item-title="label" item-value="value"
|
||||
:label="tm('batchOperations.llmStatus')" hide-details clearable variant="solo-filled" flat density="comfortable">
|
||||
</v-select>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6" lg="3">
|
||||
<v-select v-model="batchTtsStatus" :items="statusOptions" item-title="label" item-value="value"
|
||||
:label="tm('batchOperations.ttsStatus')" hide-details clearable variant="solo-filled" flat density="comfortable">
|
||||
</v-select>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6" lg="3">
|
||||
<v-select v-model="batchChatProvider" :items="chatProviderOptions" item-title="label" item-value="value"
|
||||
:label="tm('batchOperations.chatProvider')" hide-details clearable variant="solo-filled" flat density="comfortable">
|
||||
</v-select>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row dense class="mt-3">
|
||||
<v-col cols="12" class="d-flex justify-end">
|
||||
<v-btn color="primary" variant="tonal" size="large" @click="applyBatchChanges"
|
||||
:disabled="!canApplyBatch" :loading="batchUpdating" prepend-icon="mdi-check-all">
|
||||
{{ tm('batchOperations.apply') }}
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
<!-- 分组管理面板 -->
|
||||
<v-card flat class="mt-4">
|
||||
<v-card-title class="d-flex align-center py-3 px-4">
|
||||
<span class="text-h6">分组管理</span>
|
||||
<v-chip size="small" class="ml-2" color="secondary" variant="outlined">
|
||||
{{ groups.length }} 个分组
|
||||
</v-chip>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn v-if="selectedItems.length > 0 && groups.length > 0" color="info" variant="tonal" size="small" class="mr-2">
|
||||
<v-icon start>mdi-folder-plus</v-icon>
|
||||
添加到分组
|
||||
<v-menu activator="parent">
|
||||
<v-list density="compact">
|
||||
<v-list-item v-for="g in groups" :key="g.id" @click="addSelectedToGroup(g.id)">
|
||||
<v-list-item-title>{{ g.name }} ({{ g.umo_count }})</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</v-btn>
|
||||
<v-btn color="success" variant="tonal" size="small" @click="openCreateGroupDialog" prepend-icon="mdi-folder-plus">
|
||||
新建分组
|
||||
</v-btn>
|
||||
</v-card-title>
|
||||
<v-card-text v-if="groups.length > 0">
|
||||
<v-row dense>
|
||||
<v-col v-for="group in groups" :key="group.id" cols="12" sm="6" md="4" lg="3">
|
||||
<v-card variant="outlined" class="pa-3">
|
||||
<div class="d-flex align-center justify-space-between">
|
||||
<div>
|
||||
<div class="font-weight-bold">{{ group.name }}</div>
|
||||
<div class="text-caption text-grey">{{ group.umo_count }} 个会话</div>
|
||||
</div>
|
||||
<div>
|
||||
<v-btn icon size="small" variant="text" @click="openEditGroupDialog(group)">
|
||||
<v-icon size="small">mdi-pencil</v-icon>
|
||||
</v-btn>
|
||||
<v-btn icon size="small" variant="text" color="error" @click="deleteGroup(group)">
|
||||
<v-icon size="small">mdi-delete</v-icon>
|
||||
</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
<v-card-text v-else class="text-center text-grey py-6">
|
||||
暂无分组,点击「新建分组」创建
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
<!-- 分组编辑对话框 -->
|
||||
<v-dialog v-model="groupDialog" max-width="800" @after-enter="loadAvailableUmos">
|
||||
<v-card>
|
||||
<v-card-title class="py-3 px-4">
|
||||
{{ groupDialogMode === 'create' ? '新建分组' : '编辑分组' }}
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-text-field v-model="editingGroup.name" label="分组名称" variant="outlined" hide-details class="mb-4"></v-text-field>
|
||||
<v-row dense>
|
||||
<!-- 左侧:可选会话 -->
|
||||
<v-col cols="5">
|
||||
<div class="text-subtitle-2 mb-2">可选会话 ({{ unselectedUmos.length }})</div>
|
||||
<v-text-field v-model="groupMemberSearch" placeholder="搜索..." variant="outlined" density="compact" hide-details class="mb-2" clearable prepend-inner-icon="mdi-magnify"></v-text-field>
|
||||
<v-list density="compact" class="transfer-list" lines="one">
|
||||
<v-list-item v-for="umo in filteredUnselectedUmos" :key="umo" @click="addToGroup(umo)" class="transfer-item">
|
||||
<template v-slot:prepend>
|
||||
<v-icon size="small" color="grey">mdi-plus</v-icon>
|
||||
</template>
|
||||
<v-list-item-title class="text-caption">{{ formatUmoShort(umo) }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item v-if="filteredUnselectedUmos.length === 0 && !loadingUmos">
|
||||
<v-list-item-title class="text-caption text-grey text-center">无匹配项</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item v-if="loadingUmos">
|
||||
<v-list-item-title class="text-center"><v-progress-circular indeterminate size="20"></v-progress-circular></v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-col>
|
||||
<!-- 中间:操作按钮 -->
|
||||
<v-col cols="2" class="d-flex flex-column align-center justify-center">
|
||||
<v-btn icon size="small" variant="tonal" color="primary" class="mb-2" @click="addAllToGroup" :disabled="unselectedUmos.length === 0">
|
||||
<v-icon>mdi-chevron-double-right</v-icon>
|
||||
</v-btn>
|
||||
<v-btn icon size="small" variant="tonal" color="error" @click="removeAllFromGroup" :disabled="editingGroup.umos.length === 0">
|
||||
<v-icon>mdi-chevron-double-left</v-icon>
|
||||
</v-btn>
|
||||
</v-col>
|
||||
<!-- 右侧:已选会话 -->
|
||||
<v-col cols="5">
|
||||
<div class="text-subtitle-2 mb-2">已选会话 ({{ editingGroup.umos.length }})</div>
|
||||
<v-text-field v-model="groupSelectedSearch" placeholder="搜索..." variant="outlined" density="compact" hide-details class="mb-2" clearable prepend-inner-icon="mdi-magnify"></v-text-field>
|
||||
<v-list density="compact" class="transfer-list" lines="one">
|
||||
<v-list-item v-for="umo in filteredSelectedUmos" :key="umo" @click="removeFromGroup(umo)" class="transfer-item">
|
||||
<template v-slot:prepend>
|
||||
<v-icon size="small" color="error">mdi-minus</v-icon>
|
||||
</template>
|
||||
<v-list-item-title class="text-caption">{{ formatUmoShort(umo) }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item v-if="editingGroup.umos.length === 0">
|
||||
<v-list-item-title class="text-caption text-grey text-center">暂无成员</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
<v-card-actions class="px-4 pb-4">
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn variant="text" @click="groupDialog = false">取消</v-btn>
|
||||
<v-btn color="primary" variant="tonal" @click="saveGroup">保存</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- 添加规则对话框 - 选择 UMO -->
|
||||
<v-dialog v-model="addRuleDialog" max-width="600">
|
||||
@@ -608,29 +454,6 @@ export default {
|
||||
quickEditNameDialog: false,
|
||||
quickEditNameTarget: null,
|
||||
quickEditNameValue: '',
|
||||
// 批量操作
|
||||
batchScope: 'selected',
|
||||
batchGroupId: null,
|
||||
batchLlmStatus: null,
|
||||
batchTtsStatus: null,
|
||||
batchChatProvider: null,
|
||||
batchTtsProvider: null,
|
||||
batchUpdating: false,
|
||||
|
||||
// 分组管理
|
||||
groups: [],
|
||||
groupsLoading: false,
|
||||
groupDialog: false,
|
||||
groupDialogMode: 'create',
|
||||
editingGroup: {
|
||||
id: null,
|
||||
name: '',
|
||||
umos: [],
|
||||
},
|
||||
groupMemberDialog: false,
|
||||
groupMemberTarget: null,
|
||||
groupMemberSearch: '',
|
||||
groupSelectedSearch: '',
|
||||
|
||||
// 提示信息
|
||||
snackbar: false,
|
||||
@@ -706,65 +529,6 @@ export default {
|
||||
value: kb.kb_id
|
||||
}))
|
||||
},
|
||||
batchScopeOptions() {
|
||||
const options = [
|
||||
{ label: this.tm('batchOperations.scopeSelected'), value: 'selected' },
|
||||
{ label: this.tm('batchOperations.scopeAll'), value: 'all' },
|
||||
{ label: this.tm('batchOperations.scopeGroup'), value: 'group' },
|
||||
{ label: this.tm('batchOperations.scopePrivate'), value: 'private' },
|
||||
]
|
||||
// 添加自定义分组选项
|
||||
if (this.groups.length > 0) {
|
||||
options.push({ label: '── 自定义分组 ──', value: '_divider', disabled: true })
|
||||
this.groups.forEach(g => {
|
||||
options.push({ label: `📁 ${g.name} (${g.umo_count})`, value: `custom_group:${g.id}` })
|
||||
})
|
||||
}
|
||||
return options
|
||||
},
|
||||
|
||||
groupOptions() {
|
||||
return this.groups.map(g => ({
|
||||
label: `${g.name} (${g.umo_count} 个会话)`,
|
||||
value: g.id
|
||||
}))
|
||||
},
|
||||
|
||||
statusOptions() {
|
||||
return [
|
||||
{ label: this.tm('status.enabled'), value: true },
|
||||
{ label: this.tm('status.disabled'), value: false },
|
||||
]
|
||||
},
|
||||
|
||||
canApplyBatch() {
|
||||
const hasChanges = this.batchLlmStatus !== null || this.batchTtsStatus !== null ||
|
||||
this.batchChatProvider !== null || this.batchTtsProvider !== null
|
||||
if (this.batchScope === 'selected') {
|
||||
return hasChanges && this.selectedItems.length > 0
|
||||
}
|
||||
return hasChanges
|
||||
},
|
||||
|
||||
// 穿梭框:未选中的UMO列表
|
||||
unselectedUmos() {
|
||||
const selected = new Set(this.editingGroup.umos || [])
|
||||
return this.availableUmos.filter(u => !selected.has(u))
|
||||
},
|
||||
|
||||
// 穿梭框:过滤后的未选中列表
|
||||
filteredUnselectedUmos() {
|
||||
if (!this.groupMemberSearch) return this.unselectedUmos
|
||||
const search = this.groupMemberSearch.toLowerCase()
|
||||
return this.unselectedUmos.filter(u => u.toLowerCase().includes(search))
|
||||
},
|
||||
|
||||
// 穿梭框:过滤后的已选中列表
|
||||
filteredSelectedUmos() {
|
||||
if (!this.groupSelectedSearch) return this.editingGroup.umos || []
|
||||
const search = this.groupSelectedSearch.toLowerCase()
|
||||
return (this.editingGroup.umos || []).filter(u => u.toLowerCase().includes(search))
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
@@ -783,7 +547,6 @@ export default {
|
||||
|
||||
mounted() {
|
||||
this.loadData()
|
||||
this.loadGroups()
|
||||
},
|
||||
|
||||
beforeUnmount() {
|
||||
@@ -1308,242 +1071,6 @@ export default {
|
||||
}
|
||||
this.saving = false
|
||||
},
|
||||
|
||||
async applyBatchChanges() {
|
||||
this.batchUpdating = true
|
||||
try {
|
||||
let scope = this.batchScope
|
||||
let groupId = null
|
||||
let umos = []
|
||||
|
||||
// 处理自定义分组
|
||||
if (scope.startsWith('custom_group:')) {
|
||||
groupId = scope.split(':')[1]
|
||||
scope = 'custom_group'
|
||||
}
|
||||
|
||||
if (scope === 'selected') {
|
||||
umos = this.selectedItems.map(item => item.umo)
|
||||
if (umos.length === 0) {
|
||||
this.showError('请先选择要操作的会话')
|
||||
this.batchUpdating = false
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const tasks = []
|
||||
|
||||
if (this.batchLlmStatus !== null || this.batchTtsStatus !== null) {
|
||||
const serviceData = { scope, umos, group_id: groupId }
|
||||
if (this.batchLlmStatus !== null) {
|
||||
serviceData.llm_enabled = this.batchLlmStatus
|
||||
}
|
||||
if (this.batchTtsStatus !== null) {
|
||||
serviceData.tts_enabled = this.batchTtsStatus
|
||||
}
|
||||
tasks.push(axios.post('/api/session/batch-update-service', serviceData))
|
||||
}
|
||||
|
||||
if (this.batchChatProvider !== null) {
|
||||
tasks.push(axios.post('/api/session/batch-update-provider', {
|
||||
scope,
|
||||
umos,
|
||||
group_id: groupId,
|
||||
provider_type: 'chat_completion',
|
||||
provider_id: this.batchChatProvider || null
|
||||
}))
|
||||
}
|
||||
|
||||
if (this.batchTtsProvider !== null) {
|
||||
tasks.push(axios.post('/api/session/batch-update-provider', {
|
||||
scope,
|
||||
umos,
|
||||
group_id: groupId,
|
||||
provider_type: 'text_to_speech',
|
||||
provider_id: this.batchTtsProvider || null
|
||||
}))
|
||||
}
|
||||
|
||||
if (tasks.length === 0) {
|
||||
this.showError('请至少选择一项要修改的配置')
|
||||
this.batchUpdating = false
|
||||
return
|
||||
}
|
||||
|
||||
const results = await Promise.all(tasks)
|
||||
const allOk = results.every(r => r.data.status === 'ok')
|
||||
|
||||
if (allOk) {
|
||||
this.showSuccess('批量更新成功')
|
||||
this.batchLlmStatus = null
|
||||
this.batchTtsStatus = null
|
||||
this.batchChatProvider = null
|
||||
this.batchTtsProvider = null
|
||||
await this.loadData()
|
||||
} else {
|
||||
this.showError('部分更新失败')
|
||||
}
|
||||
} catch (error) {
|
||||
this.showError(error.response?.data?.message || '批量更新失败')
|
||||
}
|
||||
this.batchUpdating = false
|
||||
},
|
||||
|
||||
// ==================== 分组管理方法 ====================
|
||||
|
||||
async loadGroups() {
|
||||
this.groupsLoading = true
|
||||
try {
|
||||
const response = await axios.get('/api/session/groups')
|
||||
if (response.data.status === 'ok') {
|
||||
this.groups = response.data.data.groups || []
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载分组失败:', error)
|
||||
}
|
||||
this.groupsLoading = false
|
||||
},
|
||||
|
||||
async loadAvailableUmos() {
|
||||
if (this.availableUmos.length > 0) return
|
||||
this.loadingUmos = true
|
||||
try {
|
||||
const response = await axios.get('/api/session/active-umos')
|
||||
if (response.data.status === 'ok') {
|
||||
this.availableUmos = response.data.data.umos || []
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载会话列表失败:', error)
|
||||
}
|
||||
this.loadingUmos = false
|
||||
},
|
||||
|
||||
openCreateGroupDialog() {
|
||||
this.groupDialogMode = 'create'
|
||||
this.editingGroup = { id: null, name: '', umos: [] }
|
||||
this.groupMemberSearch = ''
|
||||
this.groupSelectedSearch = ''
|
||||
this.groupDialog = true
|
||||
},
|
||||
|
||||
openEditGroupDialog(group) {
|
||||
this.groupDialogMode = 'edit'
|
||||
this.editingGroup = { ...group, umos: [...(group.umos || [])] }
|
||||
this.groupMemberSearch = ''
|
||||
this.groupSelectedSearch = ''
|
||||
this.groupDialog = true
|
||||
},
|
||||
|
||||
// 穿梭框操作方法
|
||||
addToGroup(umo) {
|
||||
if (!this.editingGroup.umos.includes(umo)) {
|
||||
this.editingGroup.umos.push(umo)
|
||||
}
|
||||
},
|
||||
|
||||
removeFromGroup(umo) {
|
||||
const idx = this.editingGroup.umos.indexOf(umo)
|
||||
if (idx > -1) {
|
||||
this.editingGroup.umos.splice(idx, 1)
|
||||
}
|
||||
},
|
||||
|
||||
addAllToGroup() {
|
||||
this.unselectedUmos.forEach(umo => {
|
||||
if (!this.editingGroup.umos.includes(umo)) {
|
||||
this.editingGroup.umos.push(umo)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
removeAllFromGroup() {
|
||||
this.editingGroup.umos = []
|
||||
},
|
||||
|
||||
formatUmoShort(umo) {
|
||||
// 简化显示:平台:类型:ID -> 只显示ID部分
|
||||
const parts = umo.split(':')
|
||||
if (parts.length >= 3) {
|
||||
return `${parts[0]}:${parts[2]}`
|
||||
}
|
||||
return umo
|
||||
},
|
||||
|
||||
async saveGroup() {
|
||||
if (!this.editingGroup.name.trim()) {
|
||||
this.showError('分组名称不能为空')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
let response
|
||||
if (this.groupDialogMode === 'create') {
|
||||
response = await axios.post('/api/session/group/create', {
|
||||
name: this.editingGroup.name,
|
||||
umos: this.editingGroup.umos
|
||||
})
|
||||
} else {
|
||||
response = await axios.post('/api/session/group/update', {
|
||||
id: this.editingGroup.id,
|
||||
name: this.editingGroup.name,
|
||||
umos: this.editingGroup.umos
|
||||
})
|
||||
}
|
||||
|
||||
if (response.data.status === 'ok') {
|
||||
this.showSuccess(response.data.data.message)
|
||||
this.groupDialog = false
|
||||
await this.loadGroups()
|
||||
} else {
|
||||
this.showError(response.data.message)
|
||||
}
|
||||
} catch (error) {
|
||||
this.showError(error.response?.data?.message || '保存分组失败')
|
||||
}
|
||||
},
|
||||
|
||||
async deleteGroup(group) {
|
||||
if (!confirm(`确定要删除分组 "${group.name}" 吗?`)) return
|
||||
|
||||
try {
|
||||
const response = await axios.post('/api/session/group/delete', { id: group.id })
|
||||
if (response.data.status === 'ok') {
|
||||
this.showSuccess(response.data.data.message)
|
||||
await this.loadGroups()
|
||||
} else {
|
||||
this.showError(response.data.message)
|
||||
}
|
||||
} catch (error) {
|
||||
this.showError(error.response?.data?.message || '删除分组失败')
|
||||
}
|
||||
},
|
||||
|
||||
openGroupMemberDialog(group) {
|
||||
this.groupMemberTarget = { ...group }
|
||||
this.groupMemberDialog = true
|
||||
},
|
||||
|
||||
async addSelectedToGroup(groupId) {
|
||||
if (this.selectedItems.length === 0) {
|
||||
this.showError('请先选择要添加的会话')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await axios.post('/api/session/group/update', {
|
||||
id: groupId,
|
||||
add_umos: this.selectedItems.map(item => item.umo)
|
||||
})
|
||||
if (response.data.status === 'ok') {
|
||||
this.showSuccess(`已添加 ${this.selectedItems.length} 个会话到分组`)
|
||||
await this.loadGroups()
|
||||
} else {
|
||||
this.showError(response.data.message)
|
||||
}
|
||||
} catch (error) {
|
||||
this.showError(error.response?.data?.message || '添加失败')
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -1560,20 +1087,4 @@ code {
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.transfer-list {
|
||||
max-height: 280px;
|
||||
overflow-y: auto;
|
||||
border: 1px solid rgba(0, 0, 0, 0.12);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.transfer-item {
|
||||
cursor: pointer;
|
||||
transition: background-color 0.15s;
|
||||
}
|
||||
|
||||
.transfer-item:hover {
|
||||
background-color: rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user