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

@@ -0,0 +1 @@
{"specId": "b7e3c1a2-5f84-4d9e-a1b3-8c6d2e4f7a90", "workflowType": "requirements-first", "specType": "feature"}

View File

@@ -0,0 +1,319 @@
# Design Document: 对话历史租户分组展示 (conversation-tenant-view)
## Overview
本设计将对话历史页面从扁平会话列表改造为两层结构:第一层按 `tenant_id` 分组展示租户汇总卡片(会话总数、消息总数、活跃会话数、最近活跃时间),第二层展示某租户下的具体会话列表。点击会话仍可查看消息详情(保留现有第三层功能)。改造涉及三个层面:
1. **后端 API 层** — 在 `conversations_bp` 中新增租户汇总端点 `GET /api/conversations/tenants`,并为现有 `/api/conversations/sessions``/api/conversations/analytics` 端点增加 `tenant_id` 查询参数支持。
2. **业务逻辑层** — 在 `ConversationHistoryManager` 中新增 `get_tenant_summary()` 方法,并为 `get_sessions_paginated()``get_conversation_analytics()` 方法增加 `tenant_id` 过滤参数。
3. **前端展示层** — 在 `dashboard.js` 中实现 `Tenant_List_View``Tenant_Detail_View` 两个视图状态的切换逻辑,包括面包屑导航、统计面板上下文切换、搜索范围限定。
数据模型 `ChatSession``Conversation` 已有 `tenant_id` 字段(`String(50)`, indexed无需数据库迁移。
交互模式与知识库租户分组视图knowledge-tenant-view保持一致。
## Architecture
```mermaid
graph TD
subgraph Frontend["前端 (dashboard.js)"]
TLV[Tenant_List_View<br/>租户卡片列表]
TDV[Tenant_Detail_View<br/>租户会话列表]
MDV[Message_Detail_View<br/>会话消息详情]
Stats[统计面板<br/>全局/租户统计切换]
Breadcrumb[面包屑导航]
end
subgraph API["Flask Blueprint (conversations_bp)"]
EP1["GET /api/conversations/tenants"]
EP2["GET /api/conversations/sessions?tenant_id=X"]
EP3["GET /api/conversations/analytics?tenant_id=X"]
EP4["GET /api/conversations/sessions/&lt;id&gt;"]
EP5["DELETE /api/conversations/sessions/&lt;id&gt;"]
end
subgraph Service["ConversationHistoryManager"]
M1[get_tenant_summary]
M2[get_sessions_paginated<br/>+tenant_id filter]
M3[get_conversation_analytics<br/>+tenant_id filter]
M4[get_session_messages]
M5[delete_session]
end
subgraph DB["SQLAlchemy"]
CS[ChatSession<br/>tenant_id indexed]
CV[Conversation<br/>tenant_id indexed]
end
TLV -->|点击租户卡片| TDV
TDV -->|点击会话行| MDV
TDV -->|面包屑返回| TLV
MDV -->|面包屑返回| TDV
TLV --> EP1
TDV --> EP2
Stats --> EP3
MDV --> EP4
TDV --> EP5
EP1 --> M1
EP2 --> M2
EP3 --> M3
EP4 --> M4
EP5 --> M5
M1 --> CS
M2 --> CS
M3 --> CS & CV
M4 --> CV
M5 --> CS & CV
```
### 设计决策
- **不引入新模型/表**`tenant_id` 已存在于 `ChatSession``Conversation`,聚合查询通过 `GROUP BY` 实现,无需额外的 Tenant 表。
- **视图状态管理在前端**:使用 JS 变量 `conversationCurrentTenantId` 控制当前视图层级,避免引入前端路由框架。与 knowledge-tenant-view 的 `currentTenantId` 模式一致。
- **统计面板复用**:同一个统计面板根据 `conversationCurrentTenantId` 是否为 `null` 决定请求全局或租户级统计。
- **搜索范围自动限定**:当处于 `Tenant_Detail_View` 时,搜索请求自动附加 `tenant_id` 参数。
- **复用现有删除逻辑**`delete_session()` 已实现删除会话及关联消息,无需修改。
## Components and Interfaces
### 1. ConversationHistoryManager 新增/修改方法
```python
# 新增方法
def get_tenant_summary(self) -> List[Dict[str, Any]]:
"""
按 tenant_id 聚合 ChatSession返回租户汇总列表。
返回格式: [
{
"tenant_id": "market_a",
"session_count": 15,
"message_count": 230,
"active_session_count": 5,
"last_active_time": "2026-03-20T10:30:00"
}, ...
]
按 last_active_time 降序排列。
"""
# 修改方法签名
def get_sessions_paginated(
self,
page: int = 1,
per_page: int = 20,
status: Optional[str] = None,
search: str = '',
date_filter: str = '',
tenant_id: Optional[str] = None # 新增
) -> Dict[str, Any]
def get_conversation_analytics(
self,
work_order_id: Optional[int] = None,
days: int = 7,
tenant_id: Optional[str] = None # 新增
) -> Dict[str, Any]
```
### 2. Conversations API 新增/修改端点
| 端点 | 方法 | 变更 | 说明 |
|------|------|------|------|
| `/api/conversations/tenants` | GET | 新增 | 返回租户汇总数组 |
| `/api/conversations/sessions` | GET | 修改 | 增加 `tenant_id` 查询参数 |
| `/api/conversations/analytics` | GET | 修改 | 增加 `tenant_id` 查询参数 |
现有端点保持不变:
- `GET /api/conversations/sessions/<session_id>` — 获取会话消息详情
- `DELETE /api/conversations/sessions/<session_id>` — 删除会话
### 3. 前端组件
| 组件/函数 | 职责 |
|-----------|------|
| `loadConversationTenantList()` | 请求 `/api/conversations/tenants`,渲染租户卡片 |
| `loadConversationTenantDetail(tenantId, page)` | 请求 `/api/conversations/sessions?tenant_id=X`,渲染会话列表 |
| `renderConversationBreadcrumb(tenantId, sessionTitle)` | 渲染面包屑 "对话历史 > {tenant_id}" 或 "对话历史 > {tenant_id} > {session_title}" |
| `loadConversationStats(tenantId)` | 根据 tenantId 是否为 null 请求全局/租户统计 |
| `searchConversationSessions()` | 搜索时自动附加 `conversationCurrentTenantId` |
## Data Models
### ChatSession现有无变更
```python
class ChatSession(Base):
__tablename__ = "chat_sessions"
id = Column(Integer, primary_key=True)
tenant_id = Column(String(50), nullable=False, default="default", index=True)
session_id = Column(String(100), unique=True, nullable=False)
user_id = Column(String(100), nullable=True)
work_order_id = Column(Integer, ForeignKey("work_orders.id"), nullable=True)
title = Column(String(200), nullable=True)
status = Column(String(20), default="active") # active, ended
message_count = Column(Integer, default=0)
source = Column(String(50), nullable=True)
ip_address = Column(String(45), nullable=True)
created_at = Column(DateTime, default=datetime.now)
updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now)
ended_at = Column(DateTime, nullable=True)
```
### Conversation现有无变更
```python
class Conversation(Base):
__tablename__ = "conversations"
id = Column(Integer, primary_key=True)
tenant_id = Column(String(50), nullable=False, default="default", index=True)
session_id = Column(String(100), ForeignKey("chat_sessions.session_id"), nullable=True)
work_order_id = Column(Integer, ForeignKey("work_orders.id"))
user_message = Column(Text, nullable=False)
assistant_response = Column(Text, nullable=False)
timestamp = Column(DateTime, default=datetime.now)
confidence_score = Column(Float)
response_time = Column(Float)
# ... 其他字段
```
### Tenant SummaryAPI 响应结构,非持久化)
```json
{
"tenant_id": "market_a",
"session_count": 15,
"message_count": 230,
"active_session_count": 5,
"last_active_time": "2026-03-20T10:30:00"
}
```
### Analytics 响应结构(扩展)
现有 analytics 响应增加 `tenant_id` 字段(仅当按租户筛选时返回),其余结构不变。
## Correctness Properties
*A property is a characteristic or behavior that should hold true across all valid executions of a system — essentially, a formal statement about what the system should do. Properties serve as the bridge between human-readable specifications and machine-verifiable correctness guarantees.*
### Property 1: Tenant summary aggregation correctness
*For any* set of `ChatSession` records with mixed `tenant_id`, `status`, and `message_count` values, calling `get_tenant_summary()` should return a list where each element's `session_count` equals the number of `ChatSession` records for that `tenant_id`, each `message_count` equals the sum of `message_count` fields for that `tenant_id`, and each `active_session_count` equals the count of `ChatSession` records with `status == 'active'` for that `tenant_id`.
**Validates: Requirements 1.1, 1.2, 1.3, 1.4**
### Property 2: Tenant summary sorted by last_active_time descending
*For any* result returned by `get_tenant_summary()`, the list should be sorted such that for every consecutive pair of elements `(a, b)`, `a.last_active_time >= b.last_active_time`.
**Validates: Requirements 1.5**
### Property 3: Session filtering by tenant, status, and search
*For any* combination of `tenant_id`, `status`, and `search` parameters, all sessions returned by `get_sessions_paginated()` should satisfy all specified filter conditions simultaneously. Specifically: every returned session's `tenant_id` matches the requested `tenant_id`, every returned session's `status` matches the `status` filter (if provided), and every returned session's `title` or `session_id` contains the `search` string (if provided).
**Validates: Requirements 2.1, 2.3**
### Property 4: Pagination consistency with tenant filter
*For any* `tenant_id` and valid `page`/`per_page` values, the sessions returned by `get_sessions_paginated(tenant_id=X, page=P, per_page=N)` should be a correct slice of the full filtered result set. The `total` field should equal the count of all matching sessions, `total_pages` should equal `ceil(total / per_page)`, and the number of returned sessions should equal `min(per_page, total - (page-1)*per_page)` when `page <= total_pages`.
**Validates: Requirements 2.2**
### Property 5: Session deletion removes session and all associated messages
*For any* `ChatSession` and its associated `Conversation` records, after calling `delete_session(session_id)`, querying for the `ChatSession` by `session_id` should return no results, and querying for `Conversation` records with that `session_id` should also return no results.
**Validates: Requirements 6.2**
### Property 6: Search results scoped to tenant
*For any* search query and `tenant_id`, all sessions returned by `get_sessions_paginated(search=Q, tenant_id=X)` should have `tenant_id == X`. The result set should be a subset of what `get_sessions_paginated(search=Q)` returns (without tenant filter).
**Validates: Requirements 7.1, 7.2**
### Property 7: Analytics scoped to tenant
*For any* `tenant_id`, the analytics returned by `get_conversation_analytics(tenant_id=X)` should reflect only `ChatSession` and `Conversation` records with `tenant_id == X`. When `tenant_id` is omitted, the analytics should aggregate across all tenants. Specifically, the conversation total count with a tenant filter should be less than or equal to the global total count.
**Validates: Requirements 8.3, 8.4**
## Error Handling
### API 层错误处理
所有 API 端点使用 try/except 包裹,捕获异常后返回统一错误格式:
| 异常场景 | HTTP 状态码 | 响应格式 |
|----------|------------|---------|
| 参数校验失败(如 `page < 1` | 400 | `{"error": "描述信息"}` |
| 数据库查询异常 | 500 | `{"error": "描述信息"}` |
| 正常但无数据 | 200 | 空数组 `[]``{"sessions": [], "total": 0}` |
### 业务逻辑层错误处理
- `get_tenant_summary()` — 数据库异常时返回空列表 `[]`,记录 error 日志。
- `get_sessions_paginated()` — 异常时返回空结构 `{"sessions": [], "total": 0, ...}`(现有行为保持不变)。
- `get_conversation_analytics()` — 异常时返回空字典 `{}`(现有行为保持不变)。
- `delete_session()` — 异常时返回 `False`,记录 error 日志(现有行为保持不变)。
### 前端错误处理
- API 请求失败时通过 `showNotification(message, 'error')` 展示错误提示。
- 网络超时或断连时显示通用错误消息。
- 删除操作失败时显示具体失败原因。
## Testing Strategy
### 测试框架
- **单元测试**: `pytest`
- **属性测试**: `hypothesis`Python property-based testing 库)
- **每个属性测试最少运行 100 次迭代**
### 属性测试Property-Based Tests
每个 Correctness Property 对应一个属性测试,使用 `hypothesis``@given` 装饰器生成随机输入。
测试标签格式: `Feature: conversation-tenant-view, Property {number}: {property_text}`
| Property | 测试描述 | 生成策略 |
|----------|---------|---------|
| Property 1 | 生成随机 ChatSession 列表(混合 tenant_id、status、message_count验证 `get_tenant_summary()` 聚合正确性 | `st.lists(st.builds(ChatSession, tenant_id=st.sampled_from([...]), status=st.sampled_from(['active','ended']), message_count=st.integers(min_value=0, max_value=100)))` |
| Property 2 | 验证 `get_tenant_summary()` 返回列表按 last_active_time 降序 | 复用 Property 1 的生成策略 |
| Property 3 | 生成随机 tenant_id + status + search 组合,验证过滤结果一致性 | `st.sampled_from(tenant_ids)`, `st.sampled_from(['active','ended',''])`, `st.text(min_size=0, max_size=20)` |
| Property 4 | 生成随机 page/per_page验证分页切片正确性 | `st.integers(min_value=1, max_value=10)` for page/per_page |
| Property 5 | 创建随机会话及关联消息,删除后验证两者均不存在 | `st.text(min_size=1, max_size=50)` for session_id, `st.integers(min_value=1, max_value=10)` for message count |
| Property 6 | 生成随机搜索词和 tenant_id验证搜索结果范围 | `st.text()` for query, `st.sampled_from(tenant_ids)` |
| Property 7 | 生成随机 tenant_id验证 analytics 数据与手动聚合一致 | `st.sampled_from(tenant_ids)` + `st.none()` |
### 单元测试Unit Tests
单元测试聚焦于边界情况和具体示例:
- **边界**: 无 ChatSession 记录时 `get_tenant_summary()` 返回空数组
- **边界**: 不存在的 `tenant_id` 查询返回空列表 + `total=0`
- **示例**: 数据库异常时 API 返回 500
- **示例**: 删除最后一个会话后租户从汇总中消失
- **集成**: 前端 `loadConversationTenantList()` → API → Manager 完整链路
### 测试配置
```python
from hypothesis import settings
@settings(max_examples=100)
```
每个属性测试函数头部添加注释引用设计文档中的 Property 编号,例如:
```python
# Feature: conversation-tenant-view, Property 1: Tenant summary aggregation correctness
@given(sessions=st.lists(chat_session_strategy(), min_size=0, max_size=50))
def test_tenant_summary_aggregation(sessions):
...
```

View File

@@ -0,0 +1,116 @@
# Requirements Document
## Introduction
对话历史租户分组展示功能。当前对话历史页面以扁平的会话列表展示所有 `ChatSession` 记录,缺乏租户(市场)维度的组织结构。本功能将对话历史页面改造为两层结构:第一层按租户分组展示汇总信息(会话总数、消息总数、最近活跃时间等),第二层展示某个租户下的具体会话列表。点击具体会话仍可查看消息详情(保留现有功能)。交互模式与知识库租户分组视图保持一致,包括卡片视图、面包屑导航、搜索范围限定和统计面板上下文切换。
## Glossary
- **Dashboard**: Flask + Jinja2 + Bootstrap 5 构建的 Web 管理后台主页面(`dashboard.html`
- **Conversation_Tab**: Dashboard 中 `#conversation-history-tab` 区域,用于展示和管理对话历史
- **Conversation_API**: Flask Blueprint `conversations_bp`,提供对话相关的 REST API`/api/conversations/*`
- **History_Manager**: `ConversationHistoryManager` 类,封装对话历史的数据库查询与业务逻辑
- **Tenant**: 租户,即市场标识(如 `market_a``market_b`),通过 `ChatSession.tenant_id` 字段区分
- **Tenant_Summary**: 租户汇总信息,包含租户 ID、会话总数、消息总数、活跃会话数、最近活跃时间等聚合数据
- **Tenant_List_View**: 第一层视图,以卡片形式展示所有租户的对话汇总信息
- **Tenant_Detail_View**: 第二层视图,展示某个租户下的具体会话列表(含分页、筛选)
- **ChatSession**: SQLAlchemy 数据模型,包含 `tenant_id``session_id``title``status``message_count``source``created_at``updated_at` 等字段
- **Conversation**: SQLAlchemy 数据模型,表示单条对话消息,包含 `tenant_id``session_id``user_message``assistant_response` 等字段
## Requirements
### Requirement 1: 租户汇总 API
**User Story:** 作为管理员,我希望后端提供按租户分组的对话会话汇总接口,以便前端展示每个租户的对话统计。
#### Acceptance Criteria
1. WHEN a GET request is sent to `/api/conversations/tenants`, THE Conversation_API SHALL return a JSON array of Tenant_Summary objects, each containing `tenant_id`, `session_count`, `message_count`, `active_session_count`, and `last_active_time`
2. THE Conversation_API SHALL compute `session_count` by counting all ChatSession records for each Tenant
3. THE Conversation_API SHALL compute `message_count` by summing the `message_count` field of all ChatSession records for each Tenant
4. THE Conversation_API SHALL compute `active_session_count` by counting ChatSession records with `status == 'active'` for each Tenant
5. THE Conversation_API SHALL sort the Tenant_Summary array by `last_active_time` in descending order
6. WHEN no ChatSession records exist, THE Conversation_API SHALL return an empty JSON array with HTTP status 200
7. IF a database query error occurs, THEN THE Conversation_API SHALL return an error response with HTTP status 500 and a descriptive error message
### Requirement 2: 租户会话列表 API
**User Story:** 作为管理员,我希望后端提供按租户筛选的会话分页接口,以便在点击某个租户后查看该租户下的具体会话列表。
#### Acceptance Criteria
1. WHEN a GET request with query parameter `tenant_id` is sent to `/api/conversations/sessions`, THE Conversation_API SHALL return only the ChatSession records belonging to the specified Tenant
2. THE Conversation_API SHALL support pagination via `page` and `per_page` query parameters when filtering by `tenant_id`
3. THE Conversation_API SHALL support `status` and `search` query parameters for further filtering within a Tenant
4. WHEN the `tenant_id` parameter value does not match any existing ChatSession records, THE Conversation_API SHALL return an empty session list with `total` equal to 0 and HTTP status 200
5. THE History_Manager SHALL accept `tenant_id` as a filter parameter in the `get_sessions_paginated` method and return paginated results scoped to the specified Tenant
### Requirement 3: 租户列表视图(第一层)
**User Story:** 作为管理员,我希望对话历史页面首先展示按租户分组的汇总卡片,以便快速了解各市场的对话活跃度。
#### Acceptance Criteria
1. WHEN the Conversation_Tab is activated, THE Dashboard SHALL display a Tenant_List_View showing one card per Tenant
2. THE Tenant_List_View SHALL display the following information for each Tenant: tenant_id租户名称, session_count会话总数, message_count消息总数, active_session_count活跃会话数, last_active_time最近活跃时间
3. WHEN the Tenant_List_View is loading data, THE Dashboard SHALL display a loading spinner in the Conversation_Tab area
4. WHEN no tenants exist, THE Dashboard SHALL display a placeholder message indicating that no conversation sessions are available
5. THE Tenant_List_View SHALL refresh its data when the user clicks a refresh button
### Requirement 4: 租户详情视图(第二层)
**User Story:** 作为管理员,我希望点击某个租户卡片后能查看该租户下的具体会话列表,以便管理和审查对话内容。
#### Acceptance Criteria
1. WHEN a user clicks on a Tenant card in the Tenant_List_View, THE Dashboard SHALL transition to the Tenant_Detail_View showing ChatSession records for the selected Tenant
2. THE Tenant_Detail_View SHALL display each ChatSession with the following fields: title会话标题, message_count消息数, status状态, source来源, created_at创建时间, updated_at最近更新时间
3. THE Tenant_Detail_View SHALL provide a breadcrumb navigation showing "对话历史 > {tenant_id}" to indicate the current context
4. WHEN the user clicks the breadcrumb "对话历史" link, THE Dashboard SHALL navigate back to the Tenant_List_View
5. THE Tenant_Detail_View SHALL support pagination with configurable page size
6. THE Tenant_Detail_View SHALL support filtering by session status and date range
### Requirement 5: 会话详情查看(第三层保留)
**User Story:** 作为管理员,我希望在租户详情视图中点击某个会话后能查看该会话的消息详情,以便审查具体对话内容。
#### Acceptance Criteria
1. WHEN a user clicks on a ChatSession row in the Tenant_Detail_View, THE Dashboard SHALL display the message detail view showing all Conversation records for the selected ChatSession
2. THE Dashboard SHALL retain the existing message detail display logic and UI layout
3. THE Dashboard SHALL provide a breadcrumb navigation showing "对话历史 > {tenant_id} > {session_title}" in the message detail view
4. WHEN the user clicks the breadcrumb "{tenant_id}" link, THE Dashboard SHALL navigate back to the Tenant_Detail_View for the corresponding Tenant
### Requirement 6: 会话管理操作
**User Story:** 作为管理员,我希望在租户详情视图中能对会话执行删除操作,以便维护对话历史数据。
#### Acceptance Criteria
1. WHILE viewing the Tenant_Detail_View, THE Dashboard SHALL provide a delete button for each ChatSession row
2. WHEN a user deletes a ChatSession in the Tenant_Detail_View, THE Conversation_API SHALL delete the ChatSession and all associated Conversation records
3. WHEN a user deletes a ChatSession, THE Dashboard SHALL refresh the Tenant_Detail_View to reflect the updated data
4. WHEN a user deletes all ChatSession records for a Tenant, THE Dashboard SHALL navigate back to the Tenant_List_View and remove the empty Tenant card
5. IF a ChatSession deletion fails, THEN THE Dashboard SHALL display an error notification with the failure reason
### Requirement 7: 搜索功能适配
**User Story:** 作为管理员,我希望在租户详情视图中搜索会话时,搜索范围限定在当前租户内,以便精确查找。
#### Acceptance Criteria
1. WHILE viewing the Tenant_Detail_View, THE Dashboard SHALL scope the session search to the currently selected Tenant
2. WHEN a search query is submitted in the Tenant_Detail_View, THE Conversation_API SHALL filter search results by the specified `tenant_id`
3. WHEN the search query is cleared, THE Dashboard SHALL restore the full paginated session list for the current Tenant
4. THE History_Manager search method SHALL accept an optional `tenant_id` parameter to limit search scope
### Requirement 8: 统计信息适配
**User Story:** 作为管理员,我希望对话历史统计面板在租户列表视图时展示全局统计,在租户详情视图时展示当前租户的统计,以便获取准确的上下文信息。
#### Acceptance Criteria
1. WHILE the Tenant_List_View is displayed, THE Dashboard SHALL show global conversation statistics (total sessions across all tenants, total messages, total active sessions)
2. WHILE the Tenant_Detail_View is displayed, THE Dashboard SHALL show statistics scoped to the selected Tenant
3. WHEN a GET request with query parameter `tenant_id` is sent to `/api/conversations/analytics`, THE Conversation_API SHALL return analytics data filtered by the specified Tenant
4. WHEN the `tenant_id` parameter is omitted from the analytics request, THE Conversation_API SHALL return global analytics across all tenants

View File

@@ -0,0 +1,142 @@
# Implementation Plan: 对话历史租户分组展示 (conversation-tenant-view)
## Overview
将对话历史页面从扁平会话列表改造为两层结构:第一层按 `tenant_id` 分组展示租户汇总卡片,第二层展示租户下的会话列表。改造涉及 ConversationHistoryManager 业务逻辑层、Flask API 层、前端 dashboard.js 三个层面。交互模式与知识库租户分组视图保持一致。
## Tasks
- [x] 1. ConversationHistoryManager 新增 get_tenant_summary 方法
- [x] 1.1 在 `src/dialogue/conversation_history.py` 中新增 `get_tenant_summary()` 方法
- 使用 SQLAlchemy `GROUP BY ChatSession.tenant_id` 聚合所有 ChatSession 记录
- 计算每个租户的 `session_count`(会话总数)、`message_count`消息总数sum of message_count`active_session_count`status=='active' 的会话数)、`last_active_time`max of updated_at
-`last_active_time` 降序排列
- 数据库异常时返回空列表 `[]`,记录 error 日志
- 无 ChatSession 记录时返回空列表 `[]`
- _Requirements: 1.1, 1.2, 1.3, 1.4, 1.5, 1.6_
- [ ]* 1.2 为 get_tenant_summary 编写属性测试
- **Property 1: Tenant summary aggregation correctness**
- **Property 2: Tenant summary sorted by last_active_time descending**
- 使用 `hypothesis` 生成随机 ChatSession 列表(混合 tenant_id、status、message_count验证聚合正确性和排序
- **Validates: Requirements 1.1, 1.2, 1.3, 1.4, 1.5**
- [x] 2. ConversationHistoryManager 现有方法增加 tenant_id 过滤
- [x] 2.1 为 `get_sessions_paginated()` 增加 `tenant_id` 可选参数
-`src/dialogue/conversation_history.py` 中修改方法签名,增加 `tenant_id: Optional[str] = None`
-`tenant_id` 不为 None 时,在查询中增加 `ChatSession.tenant_id == tenant_id` 过滤条件
- 返回结构不变,仅过滤范围缩小
- _Requirements: 2.1, 2.2, 2.3, 2.5_
- [ ]* 2.2 为 get_sessions_paginated 的 tenant_id 过滤编写属性测试
- **Property 3: Session filtering by tenant, status, and search**
- **Property 4: Pagination consistency with tenant filter**
- **Validates: Requirements 2.1, 2.2, 2.3**
- [x] 2.3 为 `get_conversation_analytics()` 增加 `tenant_id` 可选参数
-`tenant_id` 不为 None 时,所有统计查询增加 `ChatSession.tenant_id == tenant_id``Conversation.tenant_id == tenant_id` 过滤
- 返回结构不变
- _Requirements: 8.3, 8.4_
- [ ]* 2.4 为 get_conversation_analytics 的 tenant_id 过滤编写属性测试
- **Property 7: Analytics scoped to tenant**
- **Validates: Requirements 8.3, 8.4**
- [x] 3. Checkpoint - 确保后端业务逻辑层完成
- Ensure all tests pass, ask the user if questions arise.
- [x] 4. Conversations API 层新增和修改端点
- [x] 4.1 在 `src/web/blueprints/conversations.py` 中新增 `GET /api/conversations/tenants` 端点
- 调用 `history_manager.get_tenant_summary()` 返回租户汇总 JSON 数组
- 使用 try/except 包裹,异常时返回 HTTP 500
- _Requirements: 1.1, 1.5, 1.6, 1.7_
- [x] 4.2 修改 `GET /api/conversations/sessions` 端点,增加 `tenant_id` 查询参数支持
-`request.args` 获取 `tenant_id` 参数,传递给 `history_manager.get_sessions_paginated()`
- _Requirements: 2.1, 2.2, 2.3, 2.4_
- [x] 4.3 修改 `GET /api/conversations/analytics` 端点,增加 `tenant_id` 查询参数支持
-`request.args` 获取 `tenant_id` 参数,传递给 `history_manager.get_conversation_analytics()`
- _Requirements: 8.3, 8.4_
- [ ]* 4.4 为新增和修改的 API 端点编写单元测试
- 测试 `/api/conversations/tenants` 返回正确的汇总数据
- 测试各端点的 `tenant_id` 参数过滤行为
- 测试空数据和异常情况
- _Requirements: 1.1, 1.6, 1.7, 2.4_
- [x] 5. Checkpoint - 确保后端 API 层完成
- Ensure all tests pass, ask the user if questions arise.
- [x] 6. 前端 Tenant_List_View租户列表视图
- [x] 6.1 在 `src/web/static/js/dashboard.js` 中实现 `loadConversationTenantList()` 函数
- 请求 `GET /api/conversations/tenants` 获取租户汇总数据
- 渲染租户卡片列表,每张卡片展示 `tenant_id``session_count``message_count``active_session_count``last_active_time`
- 添加加载中 spinner 状态
- 无租户时展示空状态占位提示
- 卡片点击事件绑定,调用 `loadConversationTenantDetail(tenantId)`
- _Requirements: 3.1, 3.2, 3.3, 3.4_
- [x] 6.2 实现刷新按钮功能
- 在对话历史 tab 区域添加刷新按钮,点击时重新调用 `loadConversationTenantList()`
- _Requirements: 3.5_
- [x] 7. 前端 Tenant_Detail_View租户详情视图
- [x] 7.1 实现 `loadConversationTenantDetail(tenantId, page)` 函数
- 请求 `GET /api/conversations/sessions?tenant_id=X&page=P&per_page=N` 获取会话列表
- 渲染会话表格,展示 title、message_count、status、source、created_at、updated_at
- 实现分页控件
- 支持 status 和 date_filter 筛选
- _Requirements: 4.1, 4.2, 4.5, 4.6_
- [x] 7.2 实现面包屑导航 `renderConversationBreadcrumb(tenantId, sessionTitle)`
- 展示 "对话历史 > {tenant_id}" 面包屑(租户详情视图)
- 展示 "对话历史 > {tenant_id} > {session_title}" 面包屑(消息详情视图)
- 点击 "对话历史" 链接时调用 `loadConversationTenantList()` 返回租户列表视图
- 点击 "{tenant_id}" 链接时调用 `loadConversationTenantDetail(tenantId)` 返回租户详情视图
- 管理 `conversationCurrentTenantId` 状态变量控制视图层级
- _Requirements: 4.3, 4.4, 5.3, 5.4_
- [x] 7.3 在 Tenant_Detail_View 中集成会话管理操作
- 每行会话提供删除按钮,调用 `DELETE /api/conversations/sessions/<session_id>`
- 删除成功后刷新当前租户详情视图
- 删除所有会话后自动返回租户列表视图并移除空租户卡片
- 操作失败时通过 `showNotification` 展示错误提示
- _Requirements: 6.1, 6.2, 6.3, 6.4, 6.5_
- [ ]* 7.4 为删除操作编写属性测试
- **Property 5: Session deletion removes session and all associated messages**
- **Validates: Requirements 6.2**
- [x] 8. 前端搜索和统计面板适配
- [x] 8.1 修改搜索功能 `searchConversationSessions()`
- 在 Tenant_Detail_View 中搜索时自动附加 `tenant_id` 参数
- 清空搜索时恢复当前租户的完整分页列表
- _Requirements: 7.1, 7.2, 7.3_
- [ ]* 8.2 为搜索范围限定编写属性测试
- **Property 6: Search results scoped to tenant**
- **Validates: Requirements 7.1, 7.2**
- [x] 8.3 修改 `loadConversationStats(tenantId)` 函数
-`conversationCurrentTenantId` 为 null 时请求全局统计
-`conversationCurrentTenantId` 有值时请求 `GET /api/conversations/analytics?tenant_id=X`
- _Requirements: 8.1, 8.2_
- [x] 9. 前端 HTML 模板更新
- [x] 9.1 在 `src/web/templates/dashboard.html``#conversation-history-tab` 区域添加必要的 DOM 容器
- 添加面包屑容器、租户卡片列表容器、租户详情容器
- 确保与现有 Bootstrap 5 样式一致,与知识库租户视图风格统一
- _Requirements: 3.1, 4.3_
- [x] 10. Final checkpoint - 确保所有功能集成完成
- Ensure all tests pass, ask the user if questions arise.
## Notes
- Tasks marked with `*` are optional and can be skipped for faster MVP
- Each task references specific requirements for traceability
- Checkpoints ensure incremental validation
- Property tests validate universal correctness properties from the design document
- 数据模型 `ChatSession``Conversation` 已有 `tenant_id` 字段且已建索引,无需数据库迁移
- 交互模式与知识库租户分组视图 (knowledge-tenant-view) 保持一致