Files
lcbp3/specs/06-Decision-Records/ADR-026-document-chat-ui-pattern.md
T
admin 1564f8648d
CI / CD Pipeline / build (push) Successful in 4m10s
CI / CD Pipeline / deploy (push) Successful in 3m52s
690524:1919 ADR-028-228-migration #04
2026-05-24 19:19:46 +07:00

16 KiB
Raw Blame History

ADR-026: Document Chat UI Pattern

Status: Accepted Date: 2026-05-19 Decision Makers: Development Team, UX Lead, System Architect Related Documents:

หมายเหตุ: 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)

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