370 lines
16 KiB
Markdown
370 lines
16 KiB
Markdown
# ADR-026: Document Chat UI Pattern
|
||
|
||
**Status:** Accepted
|
||
**Date:** 2026-05-19
|
||
**Decision Makers:** Development Team, UX Lead, System Architect
|
||
**Related Documents:**
|
||
- [ADR-025: AI Tool Layer Architecture](./ADR-025-ai-tool-layer-architecture.md)
|
||
- [ADR-024: Intent Classification Strategy](./ADR-024-intent-classification-strategy.md)
|
||
- [ADR-023A: Unified AI Architecture — Model Revision](./ADR-023A-unified-ai-architecture.md)
|
||
- [CONTEXT.md](../../CONTEXT.md)
|
||
|
||
> **หมายเหตุ:** ADR นี้กำหนดรูปแบบ UI สำหรับ Document Chat — ช่องสนทนากับ AI ใน context ของเอกสารที่กำลังดูอยู่ เป็นการเชื่อมต่อระหว่างผู้ใช้กับ AI Runtime Layer (ADR-024/025)
|
||
|
||
---
|
||
|
||
## Context and Problem Statement
|
||
|
||
เมื่อ Intent Classifier (ADR-024) และ AI Tool Layer (ADR-025) พร้อมใช้งาน ผู้ใช้ต้องการช่องทางโต้ตอบกับ AI ใน context ของเอกสารที่กำลังดูอยู่
|
||
|
||
ความท้าทาย:
|
||
1. **Context switching** — ถ้าเปิดหน้าใหม่ ผู้ใช้จะเสีย context ของเอกสารที่กำลังดู
|
||
2. **Screen real estate** — หน้าจอต้องแสดงทั้งเอกสารและ chat พร้อมกัน
|
||
3. **Multi-device** — ต้องรองรับ desktop (จอใหญ่) และ tablet (ไซต์ก่อสร้าง)
|
||
4. **Workflow continuity** — ผู้ใช้อาจสลับไปมาระหว่างดูเอกสารและถาม AI หลายครั้ง
|
||
|
||
---
|
||
|
||
## Decision Drivers
|
||
|
||
- **Preserve document context** — ผู้ใช้ต้องเห็นเอกสารและ chat พร้อมกัน
|
||
- **Minimize interruption** — ไม่ควร block หน้าจอ หรือพาไปหน้าใหม่
|
||
- **Responsive design** — รองรับ 1920×1080 (office) ถึง 768×1024 (tablet ไซต์)
|
||
- **Collapsible** — ผู้ใช้ควบคุมได้ว่าจะเปิด/ปิด chat หรือไม่
|
||
- **Consistent placement** — อยู่ตำแหน่งเดียวกันทุกหน้า เพื่อ muscle memory
|
||
|
||
---
|
||
|
||
## Considered Options
|
||
|
||
### Option A: Modal (Overlay Dialog)
|
||
|
||
Chat แสดงเป็น modal กลางจอ พร้อม backdrop มืด
|
||
|
||
**Pros:**
|
||
- Implement ง่าย ใช้ shadcn Dialog ได้เลย
|
||
- โฟกัสที่ chat สุดๆ ไม่มีสิ่งรบกวน
|
||
|
||
**Cons:**
|
||
- ❌ บดบังเอกสารทั้งหมด — เสีย context
|
||
- ❌ ปิด modal = สนทนาหาย — ไม่เหมาะกับการสลับไปมา
|
||
- ❌ ไม่เห็นข้อมูลเอกสารระหว่างพิมพ์คำถาม
|
||
|
||
### Option B: Separate Page (/documents/[id]/chat)
|
||
|
||
Chat เป็นหน้าแยกต่างหาก มี URL ของตัวเอง
|
||
|
||
**Pros:**
|
||
- URL shareable — ส่งลิงก์สนทนาให้คนอื่นได้
|
||
- หน้าจอเต็มที่สำหรับ chat history ยาวๆ
|
||
|
||
**Cons:**
|
||
- ❌ Context switching รุนแรง — ต้องกด Back เพื่อดูเอกสาร
|
||
- ❌ ต้องโหลดเอกสารซ้ำในหน้า chat (หรือเก็บ state ซับซ้อน)
|
||
- ❌ ไม่สามารถดูเอกสารและ chat พร้อมกัน
|
||
|
||
### Option C: Side-panel (Right side, collapsible) ✅ (เลือก)
|
||
|
||
Chat แสดงเป็น panel ทางขวา กด toggle เปิด/ปิดได้
|
||
|
||
**Pros:**
|
||
- ✅ เอกสารยังเห็นอยู่ — context ไม่หาย
|
||
- ✅ สลับไปมาง่าย — toggle เปิด/ปิดไม่กระทบเอกสาร
|
||
- ✅ รองรับ responsive — desktop (fixed 400px), tablet (30% width), mobile (bottom sheet)
|
||
- ✅ ตำแหน่ง consistent — อยู่ขวาทุกหน้า
|
||
|
||
**Cons:**
|
||
- เอกสารหลักเหลือพื้นที่น้อยลงเมื่อเปิด chat
|
||
- Implement resizable ซับซ้อนกว่า modal (แต่ v1 ใช้ fixed width)
|
||
|
||
---
|
||
|
||
## Decision
|
||
|
||
**เลือก Option C: Right-side collapsible side-panel**
|
||
|
||
---
|
||
|
||
## Layout Specification
|
||
|
||
### Desktop (≥ 1024px)
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ Header │
|
||
├─────────────────────────────────────┬───────────────────────┤
|
||
│ │ │
|
||
│ Document Content │ [Toggle] AI Chat │
|
||
│ (remaining width) │ ───────────────── │
|
||
│ │ User: สรุป RFA นี้ │
|
||
│ • Drawing A-101 │ ───────────────── │
|
||
│ • Revision 3 │ AI: ตอบ... │
|
||
│ • Status: Approved │ ───────────────── │
|
||
│ │ [Suggested Actions] │
|
||
│ │ • View latest RFA │
|
||
│ │ • Create new RFA │
|
||
│ │ │
|
||
└─────────────────────────────────────┴───────────────────────┘
|
||
```
|
||
|
||
- **Panel width:** 400px (fixed)
|
||
- **Toggle button:** มุมขวาบนของเอกสาร (แยกจาก panel)
|
||
- **Animation:** slide in/out 200ms ease-out
|
||
- **Z-index:** 40 (สูงกว่าเอกสาร แต่ต่ำกว่า modal/dialog อื่น)
|
||
|
||
### Tablet (768px – 1023px)
|
||
|
||
```
|
||
┌────────────────────────────────────────┐
|
||
│ Header │
|
||
├────────────────────┬───────────────────┤
|
||
│ │ │
|
||
│ Document │ AI Chat (30%) │
|
||
│ (70%) │ │
|
||
│ │ │
|
||
└────────────────────┴───────────────────┘
|
||
```
|
||
|
||
- **Panel width:** 30% of viewport
|
||
- **Min-width:** 320px (ถ้าน้อยกว่า ให้เป็น overlay แทน)
|
||
|
||
### Mobile (< 768px)
|
||
|
||
```
|
||
┌─────────────────────────────┐
|
||
│ Header │
|
||
├─────────────────────────────┤
|
||
│ │
|
||
│ Document Content │
|
||
│ (เต็มจอ) │
|
||
│ │
|
||
│ │
|
||
├─────────────────────────────┤
|
||
│ [💬] Toggle Chat │ ← floating button
|
||
└─────────────────────────────┘
|
||
|
||
เมื่อกด Toggle:
|
||
┌─────────────────────────────┐
|
||
│ Header │
|
||
├─────────────────────────────┤
|
||
│ AI Chat (Bottom Sheet) │
|
||
│ ───────────────────── │
|
||
│ สูง 60% ของจอ │
|
||
│ │
|
||
│ [Drag handle] │
|
||
└─────────────────────────────┘
|
||
```
|
||
|
||
- **Pattern:** Bottom sheet (shadcn Sheet with side="bottom")
|
||
- **Height:** 60% of viewport (expandable to 90%)
|
||
- **Backdrop:** มี overlay มืดบางๆ บนเอกสาร
|
||
|
||
---
|
||
|
||
## Component Structure
|
||
|
||
```
|
||
frontend/components/ai/
|
||
├── ai-chat-panel.tsx ← หลัก (รวมทุก breakpoint)
|
||
├── ai-chat-toggle.tsx ← ปุ่มเปิด/ปิด (floating บน mobile)
|
||
├── ai-chat-messages.tsx ← message list + bubble
|
||
├── ai-chat-input.tsx ← input + send button
|
||
├── ai-suggested-actions.tsx ← action chips
|
||
└── hooks/
|
||
└── use-ai-chat.ts ← TanStack Query + state
|
||
```
|
||
|
||
---
|
||
|
||
## State Management
|
||
|
||
### Local State (per page)
|
||
|
||
```typescript
|
||
// useAiChat hook
|
||
interface AiChatState {
|
||
isOpen: boolean; // panel เปิดอยู่หรือไม่
|
||
messages: ChatMessage[]; // สนทนาใน session นี้
|
||
isLoading: boolean; // AI กำลังตอบ
|
||
suggestedActions: SuggestedAction[]; // actions ล่าสุดจาก AI
|
||
}
|
||
```
|
||
|
||
### Persistence
|
||
|
||
- **Session storage:** เก็บ `messages` ต่อ session (refresh = หาย)
|
||
- **No server persistence:** v1 ไม่เก็บ chat history บน server (ลด scope)
|
||
- **Context preservation:** `documentPublicId` + `documentType` ส่งทุก request เพื่อให้ AI รู้ context
|
||
|
||
---
|
||
|
||
## Integration with AI Runtime Layer
|
||
|
||
### Request Flow
|
||
|
||
```
|
||
User พิมพ์ใน Chat Input
|
||
│
|
||
▼
|
||
AiChatInput → useAiChat.sendMessage()
|
||
│
|
||
▼
|
||
POST /api/ai/chat
|
||
{
|
||
"query": "สรุปเอกสารนี้",
|
||
"context": {
|
||
"type": "drawing",
|
||
"publicId": "0195..."
|
||
}
|
||
}
|
||
│
|
||
▼
|
||
AI Gateway (ADR-023A)
|
||
│
|
||
├─→ Intent Classifier (ADR-024)
|
||
│
|
||
├─→ AI Tool Layer (ADR-025) ถ้าเป็น GET_* intent
|
||
│
|
||
└─→ RAG Pipeline ถ้าเป็น RAG_QUERY
|
||
│
|
||
▼
|
||
Ollama (gemma4:e2b)
|
||
│
|
||
▼
|
||
Response → Stream/Chunk → UI
|
||
```
|
||
|
||
### Context Injection
|
||
|
||
ทุก request อัตโนมัติแนบ `context` จากหน้าปัจจุบัน:
|
||
|
||
| Page | context.type | context.publicId |
|
||
|------|--------------|------------------|
|
||
| /drawings/[id] | "drawing" | drawing.publicId |
|
||
| /rfas/[id] | "rfa" | rfa.publicId |
|
||
| /transmittals/[id] | "transmittal" | transmittal.publicId |
|
||
| /correspondences/[id] | "correspondence" | correspondence.publicId |
|
||
|
||
---
|
||
|
||
## UX Patterns
|
||
|
||
### Initial State
|
||
|
||
- **Default:** Panel ปิด (ผู้ใช้ต้องกดเปิดเอง)
|
||
- **First visit:** แสดง subtle hint (pulse animation ที่ toggle button) ครั้งเดียว
|
||
- **Returning user:** จำ state จาก session storage (ถ้าเคยเปิดไว้ → เปิดต่อ)
|
||
|
||
### Message Types
|
||
|
||
| Type | ลักษณะ | ตัวอย่าง |
|
||
|------|--------|----------|
|
||
| **User** | ขวา, primary color | "สรุป RFA นี้ให้หน่อย" |
|
||
| **AI Text** | ซ้าย, สีธรรมดา | ข้อความตอบ |
|
||
| **AI Tool Result** | ซ้าย, card style | รายการ RFA 3 รายการ |
|
||
| **AI Suggestion** | ซ้ายล่าง, chip buttons | "ควรสร้าง RFA ใหม่?" |
|
||
| **System** | กลาง, จางๆ | "กำลังเชื่อมต่อ AI..." |
|
||
|
||
### Suggested Actions
|
||
|
||
AI ตอบพร้อม suggested actions (ถ้ามี):
|
||
|
||
```
|
||
┌─────────────────────────────────┐
|
||
│ AI: สรุป RFA-0042 │
|
||
│ • ส่ง 2024-05-10 │
|
||
│ • สถานะ: รอตอบกลับ │
|
||
│ • มี 3 drawings │
|
||
├─────────────────────────────────┤
|
||
│ [ดู RFA ฉบับเต็ม] [สร้าง RFA ตัวถัดไป] │
|
||
└─────────────────────────────────┘
|
||
```
|
||
|
||
- Action กดได้ทันที ไม่ต้องพิมพ์ใหม่
|
||
- ถ้ากด → ส่ง query ใหม่อัตโนมัติ (เช่น "ดู RFA ฉบับเต็ม")
|
||
|
||
---
|
||
|
||
## Error Handling
|
||
|
||
### Network Error
|
||
|
||
- แสดง "ไม่สามารถเชื่อมต่อ AI ได้ กรุณาลองใหม่" + ปุ่ม Retry
|
||
- ไม่ clear messages ที่มีอยู่
|
||
|
||
### AI Timeout (> 10s)
|
||
|
||
- แสดง "AI ตอบช้าเกินไป กรุณาลองอีกครั้ง"
|
||
- Log ใน ai_audit_logs สำหรับ debug
|
||
|
||
### Permission Error (จาก Tool Layer)
|
||
|
||
- แสดง "คุณไม่มีสิทธิ์เข้าถึงข้อมูลนี้" (จาก message ที่ Tool Layer คืน)
|
||
- ไม่ expose technical details
|
||
|
||
---
|
||
|
||
## Accessibility
|
||
|
||
- **Keyboard:** Toggle ด้วย `Ctrl/Cmd + .` (custom shortcut)
|
||
- **Focus trap:** เมื่อ panel เปิด focus อยู่ใน panel จนกว่าจะปิด
|
||
- **Screen reader:** อ่าน "AI Chat panel opened" / "AI message received"
|
||
- **Reduced motion:** ปิด animation เมื่อ user ตั้งค่า reduced motion
|
||
|
||
---
|
||
|
||
## Audit & Observability
|
||
|
||
ทุก interaction บันทึกใน `ai_audit_logs`:
|
||
|
||
```json
|
||
{
|
||
"action": "chat_message",
|
||
"contextType": "drawing",
|
||
"contextPublicId": "0195...",
|
||
"query": "สรุปเอกสารนี้",
|
||
"responseType": "text",
|
||
"hasSuggestedActions": true,
|
||
"latencyMs": 2500,
|
||
"projectPublicId": "...",
|
||
"userPublicId": "..."
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Consequences
|
||
|
||
### Positive
|
||
- Context ของเอกสารไม่หายระหว่างถาม AI
|
||
- สลับไปมาระหว่างเอกสารและ chat ได้ราบรื่น
|
||
- Responsive รองรับทั้ง office และไซต์ก่อสร้าง
|
||
- ตำแหน่ง consistent — ผู้ใช้รู้ว่าหา chat ที่ไหน
|
||
|
||
### Negative
|
||
- เอกสารหลักเหลือพื้นที่น้อยลงบนจอเล็ก (แต่ collapsible ช่วยได้)
|
||
- Mobile bottom sheet อาจบดบังเนื้อหาส่วนล่างของเอกสาร
|
||
- Chat history ไม่ persist (refresh หาย) — v2 อาจเพิ่ม server persistence
|
||
|
||
### Risks
|
||
- User เปิด chat ทิ้งไว้แล้วลืม → สิ้นเปลืองพื้นที่จอ → mitigate ด้วย auto-collapse เมื่อ navigate ไปหน้าอื่น
|
||
- Mobile bottom sheet gesture ชนกับ scrolling → mitigate ด้วย drag handle ชัดเจน
|
||
|
||
---
|
||
|
||
## Out of Scope (Phase 4)
|
||
|
||
- **Multi-document chat** — chat ที่ context หลายเอกสารพร้อมกัน
|
||
- **Persistent chat history** — เก็บสนทนาย้อนหลังบน server
|
||
- **Real-time collaboration** — หลายคน chat ในห้องเดียวกัน
|
||
- **Voice input** — พิมพ์อย่างเดียว (v1)
|
||
|
||
---
|
||
|
||
## Migration Notes (ADR-009)
|
||
|
||
- ไม่มี schema change — ADR-026 เป็น frontend-only decision
|
||
- ใช้ตาราง `ai_audit_logs` ที่มีอยู่แล้วสำหรับ logging
|
||
- เพิ่ม component ใน `frontend/components/ai/`
|
||
|