feat: Add Chat UX improvements with notifications and @mention support
- Add ActionBar component with expandable toolbar for mobile - Add @mention functionality with autocomplete dropdown - Add browser notification system (push, sound, vibration) - Add NotificationSettings modal for user preferences - Add mention badges on room list cards - Add ReportPreview with Markdown rendering and copy/download - Add message copy functionality with hover actions - Add backend mentions field to messages with Alembic migration - Add lots field to rooms, remove templates - Optimize WebSocket database session handling - Various UX polish (animations, accessibility) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
# Change: Add LOT Field and Remove Room Templates
|
||||
|
||||
## Why
|
||||
|
||||
1. **模板功能未被使用**:前端 UI 未實作模板選擇功能,後端雖有完整實作但實際上無人使用。預設模板中的 email 地址(如 `maintenance_team@panjit.com.tw`)也不存在於實際 AD 系統中。
|
||||
|
||||
2. **需要追蹤 LOT 批號**:生產線異常事件需要記錄相關的 LOT(批號)資訊,以便追蹤受影響的產品批次。一個事件可能涉及多個 LOT,且 LOT 資訊需要在事件處理過程中可以新增或修改。
|
||||
|
||||
## What Changes
|
||||
|
||||
### 1. 移除 Room Templates 功能
|
||||
|
||||
**後端移除:**
|
||||
- 刪除 `RoomTemplate` 模型 (`app/modules/chat_room/models.py`)
|
||||
- 刪除 `template_service.py` (`app/modules/chat_room/services/template_service.py`)
|
||||
- 移除 `app/main.py` 中的模板初始化代碼
|
||||
- 移除 `app/modules/chat_room/router.py` 中的模板相關端點和邏輯
|
||||
- 刪除 `tr_room_templates` 資料表(Alembic migration)
|
||||
|
||||
**前端移除:**
|
||||
- 移除 `useRoomTemplates` hook
|
||||
- 移除 `RoomTemplate` type 定義
|
||||
- 移除 `roomsService.getTemplates()` 函數
|
||||
|
||||
### 2. 新增 LOT 欄位
|
||||
|
||||
**後端新增:**
|
||||
- 在 `IncidentRoom` 模型新增 `lots` 欄位(JSON 陣列格式)
|
||||
- 更新 `CreateRoomRequest` 和 `UpdateRoomRequest` schema 支援 lots
|
||||
- 新增 `PUT /api/rooms/{room_id}/lots` 端點用於更新 LOT 清單
|
||||
- Alembic migration 新增欄位
|
||||
|
||||
**前端新增:**
|
||||
- 建立房間 modal 新增 LOT 輸入區(動態新增/刪除行)
|
||||
- 房間詳情頁顯示和編輯 LOT 清單
|
||||
- 更新相關 types 和 API 呼叫
|
||||
|
||||
### 3. UI 設計
|
||||
|
||||
LOT 輸入區:
|
||||
```
|
||||
LOT 批號(可選)
|
||||
┌──────────────────────┐
|
||||
│ LOT-2024-001 │ [x]
|
||||
└──────────────────────┘
|
||||
┌──────────────────────┐
|
||||
│ LOT-2024-002 │ [x]
|
||||
└──────────────────────┘
|
||||
[+ 新增 LOT]
|
||||
```
|
||||
|
||||
- 每行一個 LOT 輸入框
|
||||
- 右側 [x] 按鈕可刪除該行
|
||||
- 底部 [+ 新增 LOT] 按鈕新增空白行
|
||||
- LOT 欄位為選填
|
||||
|
||||
## Impact
|
||||
|
||||
- **Affected specs**: `chat-room` (移除 Room Templates 需求,新增 LOT 需求)
|
||||
- **Affected code**:
|
||||
- `app/modules/chat_room/models.py` - 移除 RoomTemplate,新增 lots 欄位
|
||||
- `app/modules/chat_room/services/template_service.py` - 刪除
|
||||
- `app/modules/chat_room/services/__init__.py` - 移除 template_service 匯出
|
||||
- `app/modules/chat_room/router.py` - 移除模板端點,新增 LOT 端點
|
||||
- `app/modules/chat_room/schemas.py` - 新增 lots 欄位
|
||||
- `app/main.py` - 移除模板初始化
|
||||
- `frontend/src/types/index.ts` - 移除 RoomTemplate,新增 lots
|
||||
- `frontend/src/hooks/useRooms.ts` - 移除 useRoomTemplates
|
||||
- `frontend/src/services/rooms.ts` - 移除 getTemplates,新增 updateLots
|
||||
- `frontend/src/pages/RoomList.tsx` - 移除模板引用,新增 LOT 輸入
|
||||
- `frontend/src/pages/RoomDetail.tsx` - 新增 LOT 顯示和編輯
|
||||
- **Breaking changes**:
|
||||
- 移除 `GET /api/rooms/templates` 端點
|
||||
- 移除 `POST /api/rooms` 的 `template` 參數支援
|
||||
- 刪除 `tr_room_templates` 資料表
|
||||
- **Database migration**:
|
||||
- 刪除 `tr_room_templates` 資料表
|
||||
- 新增 `lots` 欄位到 `tr_incident_rooms` 資料表
|
||||
@@ -0,0 +1,118 @@
|
||||
# chat-room Spec Delta
|
||||
|
||||
## REMOVED Requirements
|
||||
|
||||
### Requirement: Room Templates
|
||||
~~The system SHALL support predefined room templates for common incident types to streamline room creation with preset metadata and initial members.~~
|
||||
|
||||
**Reason for removal**: 功能未被使用,前端 UI 未實作模板選擇功能,預設模板中的 email 地址不存在於實際 AD 系統。
|
||||
|
||||
#### Scenario: Create room from template (REMOVED)
|
||||
- This scenario is removed along with the requirement
|
||||
|
||||
---
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: LOT Batch Number Tracking
|
||||
The system SHALL support tracking multiple LOT (batch numbers) associated with each incident room. LOT information helps identify affected product batches for quality traceability and recall purposes.
|
||||
|
||||
#### Scenario: Create room with LOT numbers
|
||||
- **WHEN** an authenticated user sends `POST /api/rooms` with body:
|
||||
```json
|
||||
{
|
||||
"title": "Quality Issue on Line 3",
|
||||
"incident_type": "quality_issue",
|
||||
"severity": "high",
|
||||
"location": "Building A",
|
||||
"lots": ["LOT-2024-001", "LOT-2024-002"]
|
||||
}
|
||||
```
|
||||
- **THEN** the system SHALL create the room with the provided LOT numbers
|
||||
- **AND** store lots as a JSON array in the database
|
||||
- **AND** return the room details including the lots array
|
||||
|
||||
#### Scenario: Create room without LOT numbers
|
||||
- **WHEN** an authenticated user creates a room without specifying lots
|
||||
- **THEN** the system SHALL create the room with an empty lots array
|
||||
- **AND** allow lots to be added later via update
|
||||
|
||||
#### Scenario: Update room LOT numbers
|
||||
- **WHEN** an authenticated user with editor or owner role sends `PATCH /api/rooms/{room_id}` with:
|
||||
```json
|
||||
{
|
||||
"lots": ["LOT-2024-001", "LOT-2024-002", "LOT-2024-003"]
|
||||
}
|
||||
```
|
||||
- **THEN** the system SHALL replace the existing lots with the new array
|
||||
- **AND** update last_updated_at timestamp
|
||||
- **AND** return the updated room details
|
||||
|
||||
#### Scenario: Add single LOT to existing room
|
||||
- **WHEN** an authenticated user with editor or owner role sends `POST /api/rooms/{room_id}/lots` with:
|
||||
```json
|
||||
{
|
||||
"lot": "LOT-2024-004"
|
||||
}
|
||||
```
|
||||
- **THEN** the system SHALL append the LOT to the existing lots array
|
||||
- **AND** prevent duplicate LOT entries
|
||||
- **AND** return the updated lots array
|
||||
|
||||
#### Scenario: Remove single LOT from room
|
||||
- **WHEN** an authenticated user with editor or owner role sends `DELETE /api/rooms/{room_id}/lots/{lot_id}`
|
||||
- **THEN** the system SHALL remove the specified LOT from the array
|
||||
- **AND** return the updated lots array
|
||||
|
||||
#### Scenario: Viewer cannot modify LOT numbers
|
||||
- **WHEN** a user with viewer role attempts to update LOT numbers
|
||||
- **THEN** the system SHALL return status 403 with "Insufficient permissions"
|
||||
|
||||
#### Scenario: LOT numbers displayed in room details
|
||||
- **WHEN** a user sends `GET /api/rooms/{room_id}`
|
||||
- **THEN** the response SHALL include the lots array
|
||||
- **AND** lots SHALL be returned in the order they were added
|
||||
|
||||
---
|
||||
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Create Incident Room
|
||||
The system SHALL allow authenticated users to create a new incident room with metadata including title, incident type, severity level, location, description, and optional LOT batch numbers. Each room SHALL be assigned a unique identifier and timestamp upon creation.
|
||||
|
||||
#### Scenario: Create room with LOT numbers
|
||||
- **WHEN** an authenticated user sends `POST /api/rooms` with body:
|
||||
```json
|
||||
{
|
||||
"title": "Line 3 Conveyor Belt Stopped",
|
||||
"incident_type": "equipment_failure",
|
||||
"severity": "high",
|
||||
"location": "Building A, Line 3",
|
||||
"description": "Conveyor belt motor overheating, production halted",
|
||||
"lots": ["LOT-2024-001"]
|
||||
}
|
||||
```
|
||||
- **THEN** the system SHALL create a new incident_rooms record with:
|
||||
- Unique room_id (UUID)
|
||||
- Provided metadata fields including lots
|
||||
- Status set to "active"
|
||||
- created_by set to current user's ID
|
||||
- created_at timestamp
|
||||
- **AND** automatically add the creator as a room member with "owner" role
|
||||
- **AND** return status 201 with the room details including room_id and lots
|
||||
|
||||
### Requirement: Update Room Status and Metadata
|
||||
The system SHALL allow room owners and editors to update room metadata including LOT batch numbers, and allow owners to transition room status through its lifecycle (active -> resolved -> archived).
|
||||
|
||||
#### Scenario: Update room metadata with LOT
|
||||
- **WHEN** a room owner sends `PATCH /api/rooms/{room_id}` with:
|
||||
```json
|
||||
{
|
||||
"severity": "critical",
|
||||
"description": "Updated: Fire hazard detected",
|
||||
"lots": ["LOT-2024-001", "LOT-2024-002"]
|
||||
}
|
||||
```
|
||||
- **THEN** the system SHALL update only the provided fields including lots
|
||||
- **AND** record the update in room_activity log
|
||||
- **AND** set last_updated_at timestamp
|
||||
@@ -0,0 +1,77 @@
|
||||
# Tasks: Add LOT Field and Remove Room Templates
|
||||
|
||||
## Phase 1: Remove Room Templates (Backend) ✅
|
||||
|
||||
- [x] **T-1.1**: 刪除 `app/modules/chat_room/services/template_service.py`
|
||||
- [x] **T-1.2**: 更新 `app/modules/chat_room/services/__init__.py` 移除 template_service 匯出
|
||||
- [x] **T-1.3**: 更新 `app/main.py` 移除 `template_service.initialize_default_templates()` 呼叫
|
||||
- [x] **T-1.4**: 更新 `app/modules/chat_room/router.py` 移除:
|
||||
- `GET /rooms/templates` 端點
|
||||
- 建立房間時的 template 參數處理
|
||||
- [x] **T-1.5**: 刪除 `RoomTemplate` 模型 (`app/modules/chat_room/models.py`)
|
||||
- [x] **T-1.6**: 建立 Alembic migration 刪除 `tr_room_templates` 資料表
|
||||
|
||||
## Phase 2: Add LOT Field (Backend) ✅
|
||||
|
||||
- [x] **T-2.1**: 在 `IncidentRoom` 模型新增 `lots` 欄位 (JSON/Text 類型)
|
||||
- [x] **T-2.2**: 建立 Alembic migration 新增 `lots` 欄位到 `tr_incident_rooms`
|
||||
- [x] **T-2.3**: 更新 `app/modules/chat_room/schemas.py`:
|
||||
- `CreateRoomRequest` 新增 `lots: Optional[List[str]]`
|
||||
- `UpdateRoomRequest` 新增 `lots: Optional[List[str]]`
|
||||
- `RoomResponse` 新增 `lots: List[str]`
|
||||
- 新增 `AddLotRequest` schema
|
||||
- [x] **T-2.4**: 更新 `room_service.py`:
|
||||
- `create_room()` 支援 lots 參數
|
||||
- `update_room()` 支援 lots 更新
|
||||
- [x] **T-2.5**: 更新 `app/modules/chat_room/router.py` 新增:
|
||||
- `POST /api/rooms/{room_id}/lots` - 新增單一 LOT
|
||||
- `DELETE /api/rooms/{room_id}/lots/{lot}` - 刪除單一 LOT
|
||||
- [x] **T-2.6**: 執行 migration 並測試 API
|
||||
|
||||
## Phase 3: Remove Room Templates (Frontend) ✅
|
||||
|
||||
- [x] **T-3.1**: 更新 `frontend/src/types/index.ts` 移除 `RoomTemplate` interface
|
||||
- [x] **T-3.2**: 更新 `frontend/src/services/rooms.ts` 移除 `getTemplates()` 方法
|
||||
- [x] **T-3.3**: 更新 `frontend/src/hooks/useRooms.ts` 移除:
|
||||
- `roomKeys.templates()`
|
||||
- `useRoomTemplates()` hook
|
||||
- [x] **T-3.4**: 更新 `frontend/src/hooks/index.ts` 移除 `useRoomTemplates` 匯出
|
||||
- [x] **T-3.5**: 更新 `frontend/src/pages/RoomList.tsx` 移除 `useRoomTemplates()` 呼叫
|
||||
|
||||
## Phase 4: Add LOT Field (Frontend) ✅
|
||||
|
||||
- [x] **T-4.1**: 更新 `frontend/src/types/index.ts`:
|
||||
- `Room` interface 新增 `lots: string[]`
|
||||
- `CreateRoomRequest` 新增 `lots?: string[]`
|
||||
- [x] **T-4.2**: 更新 `frontend/src/services/rooms.ts` 新增:
|
||||
- `addLot(roomId: string, lot: string)` 方法
|
||||
- `removeLot(roomId: string, lot: string)` 方法
|
||||
- [x] **T-4.3**: 更新 `CreateRoomModal` 元件 (`RoomList.tsx`):
|
||||
- 新增 LOT 輸入區塊(動態新增/刪除行)
|
||||
- 整合到表單提交邏輯
|
||||
- [x] **T-4.4**: 更新 `RoomDetail.tsx`:
|
||||
- 顯示 LOT 清單
|
||||
- 新增 LOT 編輯功能(新增/刪除按鈕)
|
||||
- [x] **T-4.5**: 新增 `useAddLot` 和 `useRemoveLot` hooks
|
||||
|
||||
## Phase 5: Testing & Cleanup ✅
|
||||
|
||||
- [x] **T-5.1**: 更新 `hooks/useRooms.test.ts` 移除模板相關 mock,新增 LOT mock
|
||||
- [x] **T-5.2**: Frontend build passes (npm run build)
|
||||
- [x] **T-5.3**: Frontend tests pass (63 tests)
|
||||
- [x] **T-5.4**: 清理未使用的 import 和程式碼
|
||||
|
||||
## Dependencies
|
||||
|
||||
- T-1.* 可以並行處理
|
||||
- T-2.1, T-2.2 必須在 T-2.3-T-2.6 之前完成
|
||||
- T-3.* 可以在 T-1.* 完成後並行處理
|
||||
- T-4.* 需要 T-2.* 和 T-3.* 完成後才能開始
|
||||
- T-5.* 在所有實作完成後進行
|
||||
|
||||
## Summary
|
||||
|
||||
All phases completed successfully:
|
||||
- Backend templates removed and LOT field added
|
||||
- Frontend templates removed and LOT UI implemented
|
||||
- Build and tests pass
|
||||
@@ -0,0 +1,130 @@
|
||||
# Design: Optimize WebSocket Database Sessions
|
||||
|
||||
## Context
|
||||
|
||||
Task Reporter 使用 WebSocket 進行即時通訊,目前實作在 WebSocket 連線期間持有單一資料庫 Session。這在少量用戶時可行,但隨著用戶增加會造成連線池耗盡。
|
||||
|
||||
**現況分析:**
|
||||
- 連線池: 5 + 10 = 15 個連線
|
||||
- WebSocket 持有 Session 直到斷線
|
||||
- 50 用戶同時在線 = 需要 50 個 Session
|
||||
- 結果: 連線池耗盡,後續請求阻塞
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- 支援 100+ 並發 WebSocket 連線
|
||||
- 資料庫操作即時寫入(不使用佇列)
|
||||
- 修復 sequence_number 競爭條件
|
||||
- 可配置的連線池參數
|
||||
|
||||
**Non-Goals:**
|
||||
- 不改用 async SQLAlchemy(保持簡單)
|
||||
- 不實作訊息佇列(維持即時寫入)
|
||||
- 不改變 API 介面
|
||||
|
||||
## Decisions
|
||||
|
||||
### Decision 1: 短期 Session 模式
|
||||
|
||||
**選擇:** 每次 DB 操作使用獨立 Session,操作完成立即釋放。
|
||||
|
||||
**實作:**
|
||||
```python
|
||||
# database.py
|
||||
@contextmanager
|
||||
def get_db_context():
|
||||
db = SessionLocal()
|
||||
try:
|
||||
yield db
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
# router.py (改前)
|
||||
db = next(get_db())
|
||||
while True:
|
||||
message = create_message(db, ...) # 共用 Session
|
||||
|
||||
# router.py (改後)
|
||||
while True:
|
||||
with get_db_context() as db:
|
||||
message = create_message(db, ...) # 每次獨立
|
||||
```
|
||||
|
||||
**替代方案考慮:**
|
||||
1. **連線池擴大**: 只增加 pool_size 到 100+
|
||||
- 優點: 最少改動
|
||||
- 缺點: 浪費資源,MySQL 連線數有限
|
||||
|
||||
2. **Async SQLAlchemy**: 使用 aiomysql
|
||||
- 優點: 真正非阻塞
|
||||
- 缺點: 需要大幅重構,增加複雜度
|
||||
|
||||
3. **訊息佇列**: Redis/內存佇列 + 批次寫入
|
||||
- 優點: 最高效能
|
||||
- 缺點: 複雜度高,可能丟失資料
|
||||
|
||||
### Decision 2: Sequence Number 鎖定策略
|
||||
|
||||
**選擇:** 使用 `SELECT ... FOR UPDATE` 鎖定 + 重試機制
|
||||
|
||||
```python
|
||||
def create_message(db, room_id, ...):
|
||||
max_retries = 3
|
||||
for attempt in range(max_retries):
|
||||
try:
|
||||
# 使用 FOR UPDATE 鎖定該房間的最大 sequence
|
||||
max_seq = db.execute(
|
||||
text("SELECT MAX(sequence_number) FROM tr_messages WHERE room_id = :room_id FOR UPDATE"),
|
||||
{"room_id": room_id}
|
||||
).scalar()
|
||||
next_seq = (max_seq or 0) + 1
|
||||
# ... create message ...
|
||||
db.commit()
|
||||
return message
|
||||
except IntegrityError:
|
||||
db.rollback()
|
||||
if attempt == max_retries - 1:
|
||||
raise
|
||||
```
|
||||
|
||||
**替代方案:**
|
||||
1. **AUTO_INCREMENT 子欄位**: 每個房間獨立計數器表
|
||||
- 需要額外表,增加 JOIN 成本
|
||||
|
||||
2. **樂觀鎖**: 使用版本號重試
|
||||
- 高並發時重試次數可能很高
|
||||
|
||||
### Decision 3: 連線池配置
|
||||
|
||||
**選擇:** 環境變數可配置
|
||||
|
||||
```python
|
||||
# 生產環境建議值
|
||||
DB_POOL_SIZE=20 # 常駐連線
|
||||
DB_MAX_OVERFLOW=30 # 額外連線 (總共最多 50)
|
||||
DB_POOL_TIMEOUT=10 # 等待連線秒數
|
||||
DB_POOL_RECYCLE=1800 # 30 分鐘回收
|
||||
```
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
| 風險 | 影響 | 緩解措施 |
|
||||
|------|------|----------|
|
||||
| 頻繁取得/釋放 Session 增加開銷 | 輕微效能下降 | 連線池的 `pool_pre_ping` 減少無效連線 |
|
||||
| FOR UPDATE 可能造成鎖等待 | 高並發時延遲 | 設定合理的鎖等待超時 |
|
||||
| 每次操作獨立事務 | 無法跨操作 rollback | 本系統每個操作獨立,無此需求 |
|
||||
|
||||
## Migration Plan
|
||||
|
||||
1. **Phase 1**: 部署新連線池配置(無風險)
|
||||
2. **Phase 2**: 新增 context manager(無風險)
|
||||
3. **Phase 3**: 修改 WebSocket router(需測試)
|
||||
4. **Phase 4**: 修復 sequence 鎖定(需測試)
|
||||
|
||||
**Rollback:** 每個 Phase 獨立,可單獨回滾。
|
||||
|
||||
## Open Questions
|
||||
|
||||
1. 是否需要連線池監控指標(如 Prometheus metrics)?
|
||||
2. 是否需要實作連線健康檢查端點?
|
||||
@@ -0,0 +1,26 @@
|
||||
# Change: Optimize WebSocket Database Sessions for Production
|
||||
|
||||
**Status**: ✅ COMPLETED - Ready for archive
|
||||
|
||||
## Why
|
||||
|
||||
目前 WebSocket 連線在整個生命週期中持有單一資料庫 Session,造成連線池快速耗盡。當 50+ 用戶同時在線時,15 個連線池容量無法支撐,導致資料庫操作阻塞或失敗。此外,sequence_number 的計算存在競爭條件,可能導致訊息順序錯誤。
|
||||
|
||||
## What Changes
|
||||
|
||||
- **BREAKING**: 移除 WebSocket 連線的長期 Session 持有模式
|
||||
- 改用短期 Session 模式:每次 DB 操作獨立取得連線
|
||||
- 增加連線池容量配置(可透過環境變數調整)
|
||||
- 修復 sequence_number 競爭條件(使用資料庫層級鎖定)
|
||||
- 新增環境變數支援動態調整連線池參數
|
||||
|
||||
## Impact
|
||||
|
||||
- Affected specs: realtime-messaging
|
||||
- Affected code:
|
||||
- `app/core/database.py` - 連線池配置
|
||||
- `app/core/config.py` - 新增環境變數
|
||||
- `app/modules/realtime/router.py` - WebSocket Session 管理
|
||||
- `app/modules/realtime/services/message_service.py` - Sequence 鎖定
|
||||
- `.env.example` - 新增配置說明
|
||||
- `.env` - 同步更新目前使用的環境變數檔案
|
||||
@@ -0,0 +1,52 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Short-lived Database Sessions for WebSocket
|
||||
The system SHALL process WebSocket messages using short-lived database sessions that are acquired and released for each individual operation, rather than holding a session for the entire WebSocket connection lifetime.
|
||||
|
||||
#### Scenario: Message creation with short session
|
||||
- **WHEN** a user sends a message via WebSocket
|
||||
- **THEN** the system acquires a database session
|
||||
- **AND** creates the message with proper sequence number
|
||||
- **AND** commits the transaction
|
||||
- **AND** releases the session immediately
|
||||
- **AND** broadcasts the message to room members
|
||||
|
||||
#### Scenario: Concurrent message handling
|
||||
- **WHEN** multiple users send messages simultaneously
|
||||
- **THEN** each message operation uses an independent database session
|
||||
- **AND** sequence numbers are correctly assigned without duplicates
|
||||
- **AND** no connection pool exhaustion occurs
|
||||
|
||||
### Requirement: Message Sequence Number Integrity
|
||||
The system SHALL guarantee unique, monotonically increasing sequence numbers per room using database-level locking to prevent race conditions during concurrent message creation.
|
||||
|
||||
#### Scenario: Concurrent sequence assignment
|
||||
- **WHEN** two users send messages to the same room at the exact same time
|
||||
- **THEN** each message receives a unique sequence number
|
||||
- **AND** the sequence numbers are consecutive without gaps or duplicates
|
||||
|
||||
#### Scenario: High concurrency sequence safety
|
||||
- **WHEN** 50+ users send messages to the same room simultaneously
|
||||
- **THEN** all messages receive correct unique sequence numbers
|
||||
- **AND** the operation does not cause deadlocks
|
||||
|
||||
### Requirement: Configurable Database Connection Pool
|
||||
The system SHALL support environment variable configuration for database connection pool parameters to optimize for different deployment scales.
|
||||
|
||||
#### Scenario: Custom pool size configuration
|
||||
- **WHEN** the application starts with `DB_POOL_SIZE=20` environment variable
|
||||
- **THEN** the connection pool maintains 20 persistent connections
|
||||
|
||||
#### Scenario: Pool overflow configuration
|
||||
- **WHEN** the application starts with `DB_MAX_OVERFLOW=30` environment variable
|
||||
- **THEN** the connection pool can expand up to 30 additional connections beyond the pool size
|
||||
|
||||
#### Scenario: Pool timeout configuration
|
||||
- **WHEN** all connections are in use and a new request arrives
|
||||
- **AND** `DB_POOL_TIMEOUT=10` is configured
|
||||
- **THEN** the request waits up to 10 seconds for an available connection
|
||||
- **AND** raises an error if no connection becomes available
|
||||
|
||||
#### Scenario: Default configuration
|
||||
- **WHEN** no database pool environment variables are set
|
||||
- **THEN** the system uses production-ready defaults (pool_size=20, max_overflow=30, timeout=10, recycle=1800)
|
||||
@@ -0,0 +1,51 @@
|
||||
# Tasks: Optimize WebSocket Database Sessions
|
||||
|
||||
## Phase 1: Database Configuration
|
||||
|
||||
- [x] **T-1.1**: 在 `app/core/config.py` 新增連線池環境變數
|
||||
- `DB_POOL_SIZE` (預設: 20)
|
||||
- `DB_MAX_OVERFLOW` (預設: 30)
|
||||
- `DB_POOL_TIMEOUT` (預設: 10)
|
||||
- `DB_POOL_RECYCLE` (預設: 1800)
|
||||
- [x] **T-1.2**: 更新 `app/core/database.py` 使用新的環境變數配置
|
||||
- [x] **T-1.3**: 更新 `.env.example` 加入新配置說明
|
||||
- [x] **T-1.4**: 同步更新 `.env` 加入新的環境變數(使用生產環境建議值)
|
||||
|
||||
## Phase 2: Context Manager for Short Sessions
|
||||
|
||||
- [x] **T-2.1**: 在 `app/core/database.py` 新增 `get_db_context()` context manager
|
||||
- [ ] **T-2.2**: 新增 async 版本 `get_async_db_context()` (可選,若未來需要)
|
||||
|
||||
## Phase 3: WebSocket Router Refactoring
|
||||
|
||||
- [x] **T-3.1**: 修改 `app/modules/realtime/router.py` 移除長期 Session 持有
|
||||
- [x] **T-3.2**: 每個訊息處理改用 `with get_db_context() as db:` 模式
|
||||
- [x] **T-3.3**: 確保連線認證和房間成員檢查也使用短期 Session
|
||||
|
||||
## Phase 4: Sequence Number Race Condition Fix
|
||||
|
||||
- [x] **T-4.1**: 修改 `MessageService.create_message()` 使用 `SELECT ... FOR UPDATE`
|
||||
- [ ] ~~**T-4.2**: 或改用資料庫 AUTO_INCREMENT + 觸發器方案~~ (不需要,已採用 FOR UPDATE)
|
||||
- [x] **T-4.3**: 測試並發訊息場景確認無重複 sequence
|
||||
- 測試腳本: `tests/test_concurrent_messages.py`
|
||||
- 測試結果: 100 條訊息從 20 個用戶並發發送,全部成功無重複
|
||||
|
||||
## Phase 5: Testing & Documentation
|
||||
|
||||
- [x] **T-5.1**: 壓力測試 50+ 並發連線
|
||||
- 測試: 100 threads × 10 queries = 1000 次連線
|
||||
- 結果: 100% 成功,263.7 QPS
|
||||
- [x] **T-5.2**: 驗證連線池不會耗盡
|
||||
- Pool size: 20, 0 overflow during test
|
||||
- [x] **T-5.3**: 驗證 sequence_number 無重複
|
||||
- 100 條並發訊息,100 個唯一 sequence numbers
|
||||
- [x] **T-5.4**: 更新部署文件
|
||||
- 更新 `.env.example` 加入連線池配置說明
|
||||
|
||||
## Dependencies
|
||||
|
||||
- T-1.* 必須先完成
|
||||
- T-2.* 在 T-1.* 之後
|
||||
- T-3.* 依賴 T-2.*
|
||||
- T-4.* 可與 T-3.* 並行
|
||||
- T-5.* 在所有實作完成後
|
||||
179
openspec/changes/archive/2025-12-08-fix-chat-ux-issues/design.md
Normal file
179
openspec/changes/archive/2025-12-08-fix-chat-ux-issues/design.md
Normal file
@@ -0,0 +1,179 @@
|
||||
# Design: Fix Chat UX Issues
|
||||
|
||||
## Technical Design
|
||||
|
||||
### 1. 發文者顯示名稱 (Sender Display Name)
|
||||
|
||||
#### 後端改動
|
||||
|
||||
**Schema 變更 (`app/modules/realtime/schemas.py`):**
|
||||
```python
|
||||
class MessageResponse(BaseModel):
|
||||
message_id: str
|
||||
room_id: str
|
||||
sender_id: str
|
||||
sender_display_name: Optional[str] = None # 新增欄位
|
||||
content: str
|
||||
# ... 其他欄位
|
||||
```
|
||||
|
||||
**訊息查詢修改 (`app/modules/realtime/services/message_service.py`):**
|
||||
```python
|
||||
from app.modules.auth.models import User
|
||||
|
||||
# 在 get_messages() 中 JOIN users 表
|
||||
messages = (
|
||||
db.query(Message, User.display_name)
|
||||
.outerjoin(User, Message.sender_id == User.user_id)
|
||||
.filter(Message.room_id == room_id)
|
||||
.order_by(desc(Message.created_at))
|
||||
.limit(limit)
|
||||
.all()
|
||||
)
|
||||
|
||||
# 轉換為 response
|
||||
for msg, display_name in messages:
|
||||
msg_response = MessageResponse.from_orm(msg)
|
||||
msg_response.sender_display_name = display_name or msg.sender_id
|
||||
```
|
||||
|
||||
**WebSocket 廣播修改 (`app/modules/realtime/schemas.py`):**
|
||||
```python
|
||||
class MessageBroadcast(BaseModel):
|
||||
type: str = "message"
|
||||
message_id: str
|
||||
sender_id: str
|
||||
sender_display_name: Optional[str] = None # 新增
|
||||
# ... 其他欄位
|
||||
```
|
||||
|
||||
#### 前端改動
|
||||
|
||||
**RoomDetail.tsx:**
|
||||
```tsx
|
||||
// 將 message.sender_id 改為
|
||||
{message.sender_display_name || message.sender_id}
|
||||
```
|
||||
|
||||
### 2. 統一時區為 GMT+8
|
||||
|
||||
#### 前端工具函數 (`frontend/src/utils/datetime.ts`)
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* 格式化日期時間為 GMT+8 (台灣時間)
|
||||
*/
|
||||
export function formatDateTimeGMT8(date: Date | string): string {
|
||||
const d = typeof date === 'string' ? new Date(date) : date
|
||||
return d.toLocaleString('zh-TW', {
|
||||
timeZone: 'Asia/Taipei',
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化時間為 GMT+8 (僅時:分)
|
||||
*/
|
||||
export function formatTimeGMT8(date: Date | string): string {
|
||||
const d = typeof date === 'string' ? new Date(date) : date
|
||||
return d.toLocaleString('zh-TW', {
|
||||
timeZone: 'Asia/Taipei',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
#### 使用位置
|
||||
- `RoomDetail.tsx` - 訊息時間
|
||||
- `RoomList.tsx` - 房間最後更新時間
|
||||
- `Reports.tsx` - 報告生成時間
|
||||
|
||||
### 3. AI 報告生成問題修復
|
||||
|
||||
#### 新增健康檢查端點 (`app/modules/report_generation/router.py`)
|
||||
|
||||
```python
|
||||
@router.get("/health", response_model=schemas.HealthCheckResponse)
|
||||
async def check_dify_health():
|
||||
"""檢查 DIFY AI 服務連線狀態"""
|
||||
if not settings.DIFY_API_KEY:
|
||||
return {"status": "error", "message": "DIFY_API_KEY 未設定"}
|
||||
|
||||
try:
|
||||
# 測試 API 連線
|
||||
result = await dify_service.test_connection()
|
||||
return {"status": "ok", "message": "AI 服務正常"}
|
||||
except Exception as e:
|
||||
return {"status": "error", "message": str(e)}
|
||||
```
|
||||
|
||||
#### 啟動時檢查 (`app/main.py`)
|
||||
|
||||
```python
|
||||
@app.on_event("startup")
|
||||
async def startup_event():
|
||||
# 檢查 DIFY API Key
|
||||
if not settings.DIFY_API_KEY:
|
||||
logger.warning("DIFY_API_KEY not configured - AI report generation will be unavailable")
|
||||
```
|
||||
|
||||
#### 前端改善 (`frontend/src/hooks/useReports.ts`)
|
||||
|
||||
```typescript
|
||||
// 新增輪詢直到報告完成或失敗
|
||||
const pollReportStatus = async (reportId: string) => {
|
||||
const maxAttempts = 60 // 最多輪詢 2 分鐘
|
||||
let attempts = 0
|
||||
|
||||
while (attempts < maxAttempts) {
|
||||
const status = await api.get(`/rooms/${roomId}/reports/${reportId}`)
|
||||
if (status.data.status === 'completed' || status.data.status === 'failed') {
|
||||
return status.data
|
||||
}
|
||||
await new Promise(resolve => setTimeout(resolve, 2000))
|
||||
attempts++
|
||||
}
|
||||
|
||||
throw new Error('報告生成超時')
|
||||
}
|
||||
```
|
||||
|
||||
## Data Flow
|
||||
|
||||
### Message Flow with Display Name
|
||||
|
||||
```
|
||||
1. User sends message via WebSocket
|
||||
2. Backend creates message in DB
|
||||
3. Backend queries User table for sender's display_name
|
||||
4. Backend broadcasts MessageBroadcast with sender_display_name
|
||||
5. Frontend displays sender_display_name in chat bubble
|
||||
```
|
||||
|
||||
### Report Generation Flow (Fixed)
|
||||
|
||||
```
|
||||
1. User clicks "Generate Report"
|
||||
2. Frontend: POST /api/rooms/{id}/reports/generate
|
||||
3. Backend: Creates report record (status=pending)
|
||||
4. Backend: Returns report_id immediately
|
||||
5. Frontend: Starts polling GET /api/rooms/{id}/reports/{report_id}
|
||||
6. Backend: Background task updates status (collecting_data → generating_content → assembling_document → completed)
|
||||
7. Backend: Broadcasts WebSocket updates for each status change
|
||||
8. Frontend: Updates UI based on poll response OR WebSocket message
|
||||
9. If completed: Enable download button
|
||||
10. If failed: Show error message
|
||||
```
|
||||
|
||||
## Database Changes
|
||||
|
||||
無資料庫結構變更。僅新增 JOIN 查詢。
|
||||
|
||||
## Configuration Changes
|
||||
|
||||
無新增設定項目。僅改善現有 DIFY_API_KEY 的檢查機制。
|
||||
@@ -0,0 +1,37 @@
|
||||
# Proposal: Fix Chat UX Issues
|
||||
|
||||
## Status: DRAFT
|
||||
|
||||
## Why
|
||||
|
||||
使用者回報三個影響使用體驗的問題:
|
||||
|
||||
1. **聊天室無法辨識發文者**:訊息只顯示 email (sender_id),無法快速識別是誰發的,不像 LINE 等通訊軟體會顯示使用者名稱。
|
||||
|
||||
2. **時間顯示錯誤**:系統使用 UTC 時間,但使用者期望看到 GMT+8 (台灣時間)。目前前端依賴瀏覽器 locale,可能導致不同使用者看到不同時區的時間。
|
||||
|
||||
3. **AI 報告生成卡住**:點擊生成報告後一直停在「準備中」狀態,沒有進一步的回應或錯誤訊息。
|
||||
|
||||
## What Changes
|
||||
|
||||
### 1. 發文者顯示名稱
|
||||
- 後端 API 在回傳訊息時加入 `sender_display_name` 欄位
|
||||
- 從 `tr_users` 表格 JOIN 取得 display_name
|
||||
- 前端顯示 display_name 而非 sender_id
|
||||
|
||||
### 2. 統一時區為 GMT+8
|
||||
- 前端建立時間格式化工具函數,統一轉換為 GMT+8
|
||||
- 所有時間顯示使用該工具函數
|
||||
- 後端維持 UTC 儲存(國際標準做法)
|
||||
|
||||
### 3. AI 報告生成問題修復
|
||||
- 新增 DIFY API 連線測試端點
|
||||
- 啟動時檢查 DIFY_API_KEY 是否設定
|
||||
- 改善錯誤訊息顯示
|
||||
- 前端新增輪詢機制確保狀態更新
|
||||
|
||||
## Impact
|
||||
|
||||
- **低風險**:不影響現有資料結構,僅新增欄位和修改顯示邏輯
|
||||
- **向後相容**:sender_display_name 為選填欄位,舊訊息會 fallback 顯示 sender_id
|
||||
- **效能影響極小**:只增加一個 LEFT JOIN 查詢
|
||||
@@ -0,0 +1,59 @@
|
||||
# ai-report-generation Specification
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: DIFY Service Health Check
|
||||
|
||||
The system SHALL provide a health check mechanism to verify DIFY AI service connectivity and configuration.
|
||||
|
||||
#### Scenario: Check DIFY configuration on startup
|
||||
- **WHEN** the application starts
|
||||
- **AND** `DIFY_API_KEY` is not configured
|
||||
- **THEN** the system SHALL log a warning message: "DIFY_API_KEY not configured - AI report generation will be unavailable"
|
||||
|
||||
#### Scenario: DIFY health check endpoint
|
||||
- **WHEN** a user sends `GET /api/reports/health`
|
||||
- **AND** `DIFY_API_KEY` is not configured
|
||||
- **THEN** the system SHALL return:
|
||||
```json
|
||||
{
|
||||
"status": "error",
|
||||
"message": "DIFY_API_KEY 未設定,請聯繫系統管理員"
|
||||
}
|
||||
```
|
||||
|
||||
#### Scenario: DIFY service unreachable
|
||||
- **WHEN** a user sends `GET /api/reports/health`
|
||||
- **AND** `DIFY_API_KEY` is configured
|
||||
- **BUT** the DIFY service cannot be reached
|
||||
- **THEN** the system SHALL return:
|
||||
```json
|
||||
{
|
||||
"status": "error",
|
||||
"message": "無法連接 AI 服務,請稍後再試"
|
||||
}
|
||||
```
|
||||
|
||||
### Requirement: Report Generation Status Polling
|
||||
|
||||
The frontend SHALL implement polling mechanism to ensure report status updates are received even if WebSocket connection is unstable.
|
||||
|
||||
#### Scenario: Poll report status after generation trigger
|
||||
- **WHEN** a user triggers report generation
|
||||
- **AND** receives the initial `report_id`
|
||||
- **THEN** the frontend SHALL poll `GET /api/rooms/{room_id}/reports/{report_id}` every 2 seconds
|
||||
- **AND** continue polling until status is "completed" or "failed"
|
||||
- **AND** timeout after 120 seconds with user-friendly error message
|
||||
|
||||
#### Scenario: Display generation progress
|
||||
- **WHEN** polling returns status "collecting_data"
|
||||
- **THEN** the UI SHALL display "正在收集聊天室資料..."
|
||||
- **WHEN** polling returns status "generating_content"
|
||||
- **THEN** the UI SHALL display "AI 正在分析並生成報告內容..."
|
||||
- **WHEN** polling returns status "assembling_document"
|
||||
- **THEN** the UI SHALL display "正在組裝報告文件..."
|
||||
|
||||
#### Scenario: Display generation error
|
||||
- **WHEN** polling returns status "failed"
|
||||
- **THEN** the UI SHALL display the `error_message` from the response
|
||||
- **AND** provide option to retry generation
|
||||
@@ -0,0 +1,37 @@
|
||||
# realtime-messaging Specification
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Message Sender Display Name
|
||||
|
||||
The system SHALL include the sender's display name in message responses and broadcasts, enabling the UI to show user-friendly names instead of email addresses.
|
||||
|
||||
#### Scenario: Message response includes display name
|
||||
- **WHEN** a message is retrieved via REST API or WebSocket
|
||||
- **THEN** the response SHALL include `sender_display_name` field
|
||||
- **AND** the display name SHALL be obtained by joining with the `tr_users` table
|
||||
- **AND** if the sender does not exist in `tr_users`, the field SHALL fallback to `sender_id`
|
||||
|
||||
#### Scenario: WebSocket broadcast includes display name
|
||||
- **WHEN** a new message is broadcast via WebSocket
|
||||
- **THEN** the broadcast SHALL include `sender_display_name` field
|
||||
- **AND** the value SHALL be the sender's display name from `tr_users` table
|
||||
|
||||
#### Scenario: Historical messages include display name
|
||||
- **WHEN** a client requests message history via `GET /api/rooms/{room_id}/messages`
|
||||
- **THEN** each message in the response SHALL include `sender_display_name`
|
||||
- **AND** messages from unknown users SHALL show their `sender_id` as fallback
|
||||
|
||||
### Requirement: GMT+8 Timezone Display
|
||||
|
||||
The frontend SHALL display all timestamps in GMT+8 (Asia/Taipei) timezone for consistent user experience across all browsers.
|
||||
|
||||
#### Scenario: Message timestamp in GMT+8
|
||||
- **WHEN** a message is displayed in the chat room
|
||||
- **THEN** the timestamp SHALL be formatted in GMT+8 timezone
|
||||
- **AND** use format "HH:mm" for today's messages
|
||||
- **AND** use format "MM/DD HH:mm" for older messages
|
||||
|
||||
#### Scenario: Room list timestamps in GMT+8
|
||||
- **WHEN** the room list is displayed
|
||||
- **THEN** the "last updated" time SHALL be formatted in GMT+8 timezone
|
||||
@@ -0,0 +1,78 @@
|
||||
# Tasks: Fix Chat UX Issues
|
||||
|
||||
## Phase 1: 發文者顯示名稱
|
||||
|
||||
### T-1.1: 後端 Schema 新增 sender_display_name
|
||||
- [x] 在 `MessageResponse` schema 新增 `sender_display_name: Optional[str]` 欄位
|
||||
- [x] 在 `MessageBroadcast` schema 新增 `sender_display_name: Optional[str]` 欄位
|
||||
|
||||
### T-1.2: 後端 Service 查詢加入 User JOIN
|
||||
- [x] 修改 `MessageService.get_messages()` 使用 LEFT JOIN 取得 display_name
|
||||
- [x] 新增 `MessageService.get_display_name()` helper 方法
|
||||
- [x] 修改 `MessageService.search_messages()` 使用 LEFT JOIN 取得 display_name
|
||||
|
||||
### T-1.3: WebSocket 廣播包含 display_name
|
||||
- [x] 修改 WebSocket MESSAGE handler 查詢並包含 sender_display_name
|
||||
- [x] 修改 WebSocket EDIT_MESSAGE handler 包含 sender_display_name
|
||||
- [x] 修改 REST API create_message 端點包含 sender_display_name
|
||||
|
||||
### T-1.4: 前端顯示 display_name
|
||||
- [x] 更新 `Message` 型別定義包含 `sender_display_name` 欄位
|
||||
- [x] 更新 `MessageBroadcast` 型別定義包含 `sender_display_name` 欄位
|
||||
- [x] 修改 `RoomDetail.tsx` 顯示 `sender_display_name || sender_id`
|
||||
- [x] 修改 `useWebSocket.ts` 傳遞 `sender_display_name`
|
||||
|
||||
## Phase 2: 統一時區為 GMT+8
|
||||
|
||||
### T-2.1: 建立時間格式化工具
|
||||
- [x] 建立 `frontend/src/utils/datetime.ts`
|
||||
- [x] 實作 `formatDateTimeGMT8(date)` 函數
|
||||
- [x] 實作 `formatTimeGMT8(date)` 函數
|
||||
- [x] 實作 `formatMessageTime(date)` 函數 (智慧顯示)
|
||||
- [x] 實作 `formatRelativeTimeGMT8(date)` 函數 (相對時間)
|
||||
|
||||
### T-2.2: 套用到所有時間顯示
|
||||
- [x] `RoomDetail.tsx` - 訊息時間改用 `formatMessageTime`
|
||||
- [x] `RoomList.tsx` - 最後活動時間改用 `formatRelativeTimeGMT8`
|
||||
|
||||
## Phase 3: AI 報告生成問題修復
|
||||
|
||||
### T-3.1: 後端 DIFY 健康檢查
|
||||
- [x] 在 `dify_client.py` 新增 `test_connection()` 方法
|
||||
- [x] 新增 `HealthCheckResponse` schema
|
||||
- [x] 新增 `GET /api/reports/health` 端點檢查 DIFY 狀態
|
||||
- [x] 啟動時檢查 DIFY_API_KEY 並記錄警告
|
||||
- [x] 在 main.py 註冊 health_router
|
||||
|
||||
### T-3.2: 改善錯誤處理與顯示
|
||||
- [x] 確保 background task 錯誤正確寫入 report.error_message (已存在)
|
||||
- [x] WebSocket 廣播失敗狀態時包含具體錯誤 (已存在)
|
||||
|
||||
### T-3.3: 前端輪詢機制
|
||||
- [x] 修改 `useReports.ts` 新增 `useReportPolling()` hook
|
||||
- [x] 新增 `useGenerateReportWithPolling()` hook
|
||||
- [x] 修改 `RoomDetail.tsx` 實作輪詢邏輯
|
||||
- [x] 顯示各階段進度訊息 (準備中 → 收集資料 → AI 生成 → 組裝文件)
|
||||
- [x] 失敗時顯示錯誤訊息給使用者
|
||||
|
||||
### T-3.4: 報告下載中文檔名修復
|
||||
- [x] 修改 `router.py` download_report 使用 RFC 5987 編碼處理中文檔名
|
||||
- [x] 使用 `urllib.parse.quote()` 對檔名進行 URL 編碼
|
||||
- [x] 提供 ASCII fallback 檔名相容舊版客戶端
|
||||
|
||||
## Phase 4: 測試與驗證
|
||||
|
||||
### T-4.1: 測試發文者顯示
|
||||
- [ ] 測試新訊息 WebSocket 廣播包含 display_name
|
||||
- [ ] 測試歷史訊息 API 回傳包含 display_name
|
||||
- [ ] 測試未知使用者 (不在 users 表) fallback 顯示 sender_id
|
||||
|
||||
### T-4.2: 測試時區顯示
|
||||
- [ ] 測試訊息時間顯示為台灣時間
|
||||
- [ ] 測試跨日訊息時間正確
|
||||
|
||||
### T-4.3: 測試 AI 報告
|
||||
- [ ] 測試 DIFY_API_KEY 未設定時的錯誤訊息
|
||||
- [ ] 測試報告生成完整流程
|
||||
- [ ] 測試生成失敗時的錯誤顯示
|
||||
- [ ] 測試報告下載中文檔名正確顯示
|
||||
@@ -0,0 +1,33 @@
|
||||
# Change: Improve Chat UX - Action Bar and Features
|
||||
|
||||
## Why
|
||||
目前聊天室的主要操作按鈕(生成報告、上傳檔案、添加人員)位於頁首,當訊息增多時需要向上捲動才能操作,影響使用效率。此外,報告僅提供 Word 下載,缺少即時預覽功能;聊天介面也缺少複製訊息、@提及、通知等現代化功能。
|
||||
|
||||
## What Changes
|
||||
|
||||
### 1. Action Bar Redesign (類似 LINE 佈局)
|
||||
- 將常用操作按鈕從頁首移至輸入框區域附近
|
||||
- 新增可展開的工具列,包含:上傳檔案、生成報告、添加人員
|
||||
- 保持頁首僅顯示房間資訊和設定
|
||||
|
||||
### 2. Report Preview Enhancement
|
||||
- 報告生成後以 Markdown 格式在頁面內預覽
|
||||
- 提供「複製 Markdown」按鈕,方便轉貼發布
|
||||
- 保留原有 Word 下載功能
|
||||
|
||||
### 3. Message Actions
|
||||
- 新增訊息複製按鈕(hover 顯示)
|
||||
- 新增 @mention 功能(輸入 @ 觸發成員選單)
|
||||
|
||||
### 4. Notification System
|
||||
- 瀏覽器 Push Notification(需用戶授權)
|
||||
- 新訊息音效提示
|
||||
- 移動端震動提示(支援 Vibration API)
|
||||
|
||||
## Impact
|
||||
- Affected specs: `chat-room`, `realtime-messaging`, `ai-report-generation`
|
||||
- Affected code:
|
||||
- `frontend/src/pages/RoomDetail.tsx` - 主要介面重構
|
||||
- `frontend/src/components/` - 新增 ActionBar, ReportPreview, MentionInput 組件
|
||||
- `app/modules/realtime/` - @mention 解析和通知
|
||||
- `app/modules/report_generation/` - Markdown 輸出端點
|
||||
@@ -0,0 +1,33 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Markdown Report Output
|
||||
The report generation system SHALL provide reports in Markdown format for in-page preview.
|
||||
|
||||
#### Scenario: Get report as Markdown
|
||||
- **WHEN** user requests `GET /api/rooms/{room_id}/reports/{report_id}/markdown`
|
||||
- **AND** the report status is `completed`
|
||||
- **THEN** the system returns the report content in Markdown format
|
||||
- **AND** the Markdown includes all report sections (summary, timeline, participants, etc.)
|
||||
|
||||
#### Scenario: Markdown includes metadata
|
||||
- **WHEN** generating Markdown output
|
||||
- **THEN** the output includes a metadata header with room info, LOT numbers, dates
|
||||
- **AND** the format is suitable for copy-paste to other platforms
|
||||
|
||||
### Requirement: In-Page Report Preview
|
||||
The frontend SHALL display a preview of the generated report within the chat room interface.
|
||||
|
||||
#### Scenario: Display report preview
|
||||
- **WHEN** user clicks on a completed report
|
||||
- **THEN** a modal or drawer opens showing the Markdown-rendered report
|
||||
- **AND** the preview includes proper formatting (headers, tables, lists)
|
||||
|
||||
#### Scenario: Copy Markdown content
|
||||
- **WHEN** user clicks "Copy Markdown" in the preview
|
||||
- **THEN** the raw Markdown text is copied to clipboard
|
||||
- **AND** a success toast notification is shown
|
||||
|
||||
#### Scenario: Download Word from preview
|
||||
- **WHEN** user clicks "Download Word" in the preview
|
||||
- **THEN** the .docx file is downloaded
|
||||
- **AND** the filename uses the report title
|
||||
@@ -0,0 +1,32 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Action Bar Layout
|
||||
The chat room interface SHALL provide an action bar near the message input area for quick access to common operations.
|
||||
|
||||
#### Scenario: Action bar displays common actions
|
||||
- **WHEN** user views a chat room
|
||||
- **THEN** an action bar is displayed above or beside the message input
|
||||
- **AND** the action bar includes: upload file, generate report, add member buttons
|
||||
|
||||
#### Scenario: Action bar toggle on mobile
|
||||
- **WHEN** user is on a mobile device
|
||||
- **THEN** the action bar buttons can be collapsed/expanded
|
||||
- **AND** a single toggle button reveals the full action options
|
||||
|
||||
### Requirement: Message Copy Action
|
||||
Users SHALL be able to copy individual message content to clipboard.
|
||||
|
||||
#### Scenario: Copy message content
|
||||
- **WHEN** user hovers over a message
|
||||
- **THEN** a copy button appears
|
||||
- **AND** clicking the button copies the message content to clipboard
|
||||
- **AND** a success toast notification is shown
|
||||
|
||||
### Requirement: Notification Settings
|
||||
Users SHALL be able to configure notification preferences for the chat room.
|
||||
|
||||
#### Scenario: Configure notification settings
|
||||
- **WHEN** user accesses notification settings
|
||||
- **THEN** user can enable/disable sound notifications
|
||||
- **AND** user can enable/disable browser push notifications
|
||||
- **AND** user can enable/disable vibration (on supported devices)
|
||||
@@ -0,0 +1,61 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: @Mention Support
|
||||
The messaging system SHALL support @mention functionality to tag specific users in messages.
|
||||
|
||||
#### Scenario: Trigger mention autocomplete
|
||||
- **WHEN** user types `@` in the message input
|
||||
- **THEN** a dropdown menu appears showing room members
|
||||
- **AND** the list filters as user continues typing
|
||||
- **AND** user can select a member using keyboard or mouse
|
||||
|
||||
#### Scenario: Insert mention into message
|
||||
- **WHEN** user selects a member from the mention dropdown
|
||||
- **THEN** the mention is inserted as `@display_name`
|
||||
- **AND** the mention is stored with the user_id reference
|
||||
- **AND** the mention is visually highlighted in the message
|
||||
|
||||
#### Scenario: Mention notification
|
||||
- **WHEN** a message containing @mention is sent
|
||||
- **THEN** the mentioned user receives a highlighted notification
|
||||
- **AND** the notification indicates they were mentioned
|
||||
|
||||
### Requirement: Browser Push Notifications
|
||||
The system SHALL support browser push notifications for new messages.
|
||||
|
||||
#### Scenario: Request notification permission
|
||||
- **WHEN** user first visits the chat room
|
||||
- **THEN** the system prompts for notification permission
|
||||
- **AND** the permission state is stored locally
|
||||
|
||||
#### Scenario: Send push notification
|
||||
- **WHEN** a new message arrives while the tab is not focused
|
||||
- **AND** user has granted notification permission
|
||||
- **THEN** a browser push notification is displayed
|
||||
- **AND** clicking the notification focuses the chat room
|
||||
|
||||
### Requirement: Sound and Vibration Alerts
|
||||
The system SHALL support audio and haptic feedback for new messages.
|
||||
|
||||
#### Scenario: Play notification sound
|
||||
- **WHEN** a new message arrives
|
||||
- **AND** sound notifications are enabled
|
||||
- **THEN** a notification sound is played
|
||||
|
||||
#### Scenario: Vibrate on mobile
|
||||
- **WHEN** a new message arrives on a mobile device
|
||||
- **AND** vibration is enabled
|
||||
- **AND** the device supports Vibration API
|
||||
- **THEN** the device vibrates briefly
|
||||
|
||||
### Requirement: Mention Data Storage
|
||||
Messages with @mentions SHALL store the mention metadata for querying.
|
||||
|
||||
#### Scenario: Store mention references
|
||||
- **WHEN** a message with @mentions is created
|
||||
- **THEN** the `mentions` field stores an array of mentioned user_ids
|
||||
- **AND** the message content preserves the @display_name format
|
||||
|
||||
#### Scenario: Query messages mentioning user
|
||||
- **WHEN** fetching messages that mention a specific user
|
||||
- **THEN** messages with that user_id in `mentions` array are returned
|
||||
@@ -0,0 +1,80 @@
|
||||
# Tasks: Improve Chat UX v2
|
||||
|
||||
## Phase 1: Action Bar Redesign
|
||||
|
||||
### T-1.1: 前端 Action Bar 組件
|
||||
- [x] 建立 `ActionBar.tsx` 組件(輸入框上方工具列)
|
||||
- [x] 實作展開/收合按鈕群組
|
||||
- [x] 整合上傳檔案按鈕
|
||||
- [x] 整合生成報告按鈕
|
||||
- [x] 整合添加人員按鈕
|
||||
|
||||
### T-1.2: RoomDetail 介面重構
|
||||
- [x] 移除頁首的操作按鈕
|
||||
- [x] 整合 ActionBar 到輸入區域
|
||||
- [x] 調整 header 僅顯示房間資訊
|
||||
- [x] 響應式佈局調整(mobile/desktop)
|
||||
|
||||
## Phase 2: Report Markdown Preview
|
||||
|
||||
### T-2.1: 後端 Markdown 輸出
|
||||
- [x] 新增 `GET /api/rooms/{room_id}/reports/{report_id}/markdown` 端點
|
||||
- [x] 實作 `DocxService.to_markdown()` 方法
|
||||
- [x] 從 AI JSON 內容生成結構化 Markdown
|
||||
|
||||
### T-2.2: 前端預覽組件
|
||||
- [x] 建立 `ReportPreview.tsx` 組件
|
||||
- [x] 整合 Markdown 渲染(react-markdown)
|
||||
- [x] 實作「複製 Markdown」按鈕
|
||||
- [x] 實作「下載 Word」按鈕
|
||||
- [x] 新增預覽 Modal/Drawer
|
||||
|
||||
## Phase 3: Message Actions
|
||||
|
||||
### T-3.1: 訊息複製功能
|
||||
- [x] 在訊息 hover 時顯示操作按鈕
|
||||
- [x] 實作複製訊息內容功能
|
||||
- [x] 複製成功提示 (icon change feedback)
|
||||
|
||||
### T-3.2: @Mention 功能 - 前端
|
||||
- [x] 建立 `MentionInput.tsx` 組件
|
||||
- [x] 輸入 `@` 時觸發成員選單
|
||||
- [x] 支援鍵盤選擇(上下鍵、Enter)
|
||||
- [x] 顯示成員 display_name
|
||||
- [x] 訊息中 @mention 高亮顯示
|
||||
|
||||
### T-3.3: @Mention 功能 - 後端
|
||||
- [x] 修改 Message model 新增 `mentions` JSON 欄位
|
||||
- [x] 訊息建立時解析 @mention (parse_mentions function)
|
||||
- [x] 建立 Alembic migration
|
||||
|
||||
## Phase 4: Notification System
|
||||
|
||||
### T-4.1: 瀏覽器通知
|
||||
- [x] 建立 `NotificationService.ts`
|
||||
- [x] 請求通知權限 UI
|
||||
- [x] 新訊息時發送 Push Notification
|
||||
- [x] 點擊通知跳轉到對應房間
|
||||
|
||||
### T-4.2: 音效與震動
|
||||
- [x] 新增訊息提示音效檔案 (base64 embedded)
|
||||
- [x] 實作音效播放(可設定開關)
|
||||
- [x] 實作移動端震動提示(Vibration API)
|
||||
- [x] 新增通知設定介面
|
||||
|
||||
### T-4.3: @Mention 通知
|
||||
- [x] 被 @mention 時強調通知 (special sound + notification)
|
||||
- [x] 在房間列表顯示 mention 徽章 (client-side tracking with Zustand/localStorage)
|
||||
|
||||
## Phase 5: Testing & Polish
|
||||
|
||||
### T-5.1: 測試
|
||||
- [x] Action Bar 功能測試 (build verified)
|
||||
- [x] Report Preview 測試 (build verified)
|
||||
- [x] @Mention 完整流程測試 (build verified)
|
||||
- [x] 通知系統測試(權限、音效、震動)(build verified)
|
||||
|
||||
### T-5.2: UI 微調
|
||||
- [x] 動畫過渡效果 (ActionBar expand/collapse, NotificationSettings modal)
|
||||
- [ ] 深色模式相容性 (deferred - requires theme system)
|
||||
- [x] 無障礙支援 (ARIA labels, roles, keyboard support)
|
||||
Reference in New Issue
Block a user