feat: 对话历史页面租户分组展示功能

- 新增 ConversationHistoryManager.get_tenant_summary() 按租户聚合会话统计
- get_sessions_paginated() 和 get_conversation_analytics() 增加 tenant_id 过滤
- 新增 GET /api/conversations/tenants 租户汇总端点
- sessions 和 analytics API 端点支持 tenant_id 查询参数
- 前端实现租户卡片列表视图和租户详情会话表格视图
- 实现面包屑导航、搜索范围限定、统计面板上下文切换
- 会话删除后自动检测空租户并返回列表视图
- dashboard.html 添加租户视图 DOM 容器
- 交互模式与知识库租户分组视图保持一致
This commit is contained in:
2026-04-01 16:11:02 +08:00
parent e14e3ee7a5
commit 7013e9db70
27 changed files with 2753 additions and 276 deletions

View File

@@ -14,7 +14,7 @@ from ..core.database import db_manager
from ..core.models import Conversation, WorkOrder, WorkOrderSuggestion, KnowledgeEntry, ChatSession
from ..core.redis_manager import redis_manager
from src.config.unified_config import get_config
from sqlalchemy import and_, or_, desc
from sqlalchemy import and_, or_, desc, func, case
logger = logging.getLogger(__name__)
@@ -634,7 +634,8 @@ class ConversationHistoryManager:
def get_conversation_analytics(
self,
work_order_id: Optional[int] = None,
days: int = 7
days: int = 7,
tenant_id: Optional[str] = None
) -> Dict[str, Any]:
"""获取对话分析数据包含AI建议统计"""
try:
@@ -652,6 +653,8 @@ class ConversationHistoryManager:
conv_query = session.query(Conversation)
if work_order_id:
conv_query = conv_query.filter(Conversation.work_order_id == work_order_id)
if tenant_id is not None:
conv_query = conv_query.filter(Conversation.tenant_id == tenant_id)
conversations = conv_query.filter(
Conversation.timestamp >= cutoff_date
@@ -718,6 +721,49 @@ class ConversationHistoryManager:
logger.error(f"获取对话分析数据失败: {e}")
return {}
# ==================== 租户汇总方法 ====================
def get_tenant_summary(self) -> List[Dict[str, Any]]:
"""
按 tenant_id 聚合 ChatSession返回租户汇总列表。
按 last_active_time 降序排列。
数据库异常或无记录时返回空列表。
"""
try:
with db_manager.get_session() as session:
results = session.query(
ChatSession.tenant_id,
func.count(ChatSession.id).label('session_count'),
func.coalesce(func.sum(ChatSession.message_count), 0).label('message_count'),
func.sum(
case(
(ChatSession.status == 'active', 1),
else_=0
)
).label('active_session_count'),
func.max(ChatSession.updated_at).label('last_active_time')
).group_by(
ChatSession.tenant_id
).order_by(
desc('last_active_time')
).all()
summary = []
for row in results:
summary.append({
'tenant_id': row.tenant_id,
'session_count': row.session_count,
'message_count': int(row.message_count),
'active_session_count': int(row.active_session_count),
'last_active_time': row.last_active_time.isoformat() if row.last_active_time else None
})
return summary
except Exception as e:
logger.error(f"获取租户汇总失败: {e}")
return []
# ==================== 会话管理方法 ====================
def get_sessions_paginated(
@@ -726,13 +772,17 @@ class ConversationHistoryManager:
per_page: int = 20,
status: Optional[str] = None,
search: str = '',
date_filter: str = ''
date_filter: str = '',
tenant_id: Optional[str] = None
) -> Dict[str, Any]:
"""分页获取会话列表"""
try:
with db_manager.get_session() as session:
query = session.query(ChatSession)
if tenant_id is not None:
query = query.filter(ChatSession.tenant_id == tenant_id)
if status:
query = query.filter(ChatSession.status == status)