16 KiB
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-024: Intent Classification Strategy
- ADR-023A: Unified AI Architecture — Model Revision
- 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 ของเอกสารที่กำลังดูอยู่
ความท้าทาย:
- Context switching — ถ้าเปิดหน้าใหม่ ผู้ใช้จะเสีย context ของเอกสารที่กำลังดู
- Screen real estate — หน้าจอต้องแสดงทั้งเอกสารและ chat พร้อมกัน
- Multi-device — ต้องรองรับ desktop (จอใหญ่) และ tablet (ไซต์ก่อสร้าง)
- 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)
// 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:
{
"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/