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:
egg
2025-12-08 08:20:37 +08:00
parent 92834dbe0e
commit 599802b818
72 changed files with 6810 additions and 702 deletions

View File

@@ -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` 資料表

View File

@@ -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

View File

@@ -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

View File

@@ -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. 是否需要實作連線健康檢查端點?

View File

@@ -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` - 同步更新目前使用的環境變數檔案

View File

@@ -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)

View File

@@ -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.* 在所有實作完成後

View 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 的檢查機制。

View File

@@ -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 查詢

View File

@@ -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

View File

@@ -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

View File

@@ -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 未設定時的錯誤訊息
- [ ] 測試報告生成完整流程
- [ ] 測試生成失敗時的錯誤顯示
- [ ] 測試報告下載中文檔名正確顯示

View File

@@ -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 輸出端點

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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)