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:
1
.kiro/specs/conversation-tenant-view/.config.kiro
Normal file
1
.kiro/specs/conversation-tenant-view/.config.kiro
Normal file
@@ -0,0 +1 @@
|
||||
{"specId": "b7e3c1a2-5f84-4d9e-a1b3-8c6d2e4f7a90", "workflowType": "requirements-first", "specType": "feature"}
|
||||
319
.kiro/specs/conversation-tenant-view/design.md
Normal file
319
.kiro/specs/conversation-tenant-view/design.md
Normal 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/<id>"]
|
||||
EP5["DELETE /api/conversations/sessions/<id>"]
|
||||
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 Summary(API 响应结构,非持久化)
|
||||
|
||||
```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):
|
||||
...
|
||||
```
|
||||
116
.kiro/specs/conversation-tenant-view/requirements.md
Normal file
116
.kiro/specs/conversation-tenant-view/requirements.md
Normal 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
|
||||
142
.kiro/specs/conversation-tenant-view/tasks.md
Normal file
142
.kiro/specs/conversation-tenant-view/tasks.md
Normal 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) 保持一致
|
||||
Reference in New Issue
Block a user