Chuyển tới nội dung chính

Tích hợp Thông báo vào AI Chat

Hướng dẫn tích hợp menu thông báo và luồng Notification-to-Chat cho ứng dụng mobile/web.

Tổng quan

Khi người dùng click vào một thông báo, ứng dụng mở màn hình chat với nội dung thông báo được stream trực tiếp, kèm theo các nút CTA (Call-to-Action) để người dùng tương tác tiếp.

┌─────────────────────┐     ┌──────────────────────────────┐     ┌────────────────────────┐
│ Notification List │────▶│ Chat Draft (stream content) │────▶│ Chat Session (agent) │
│ GET /list │ │ WS: fetch_context │ │ WS: chat + source │
└─────────────────────┘ └──────────────────────────────┘ └────────────────────────┘

API Endpoints

1. Lấy danh sách thông báo

GET /api/notification/v2/{appCode}/message/list
ParamTypeRequiredDescription
appCodestringpathMã ứng dụng: nebula
groupTypestringqueryLọc theo nhóm: signal (sự kiện), other (hệ thống), trống = tất cả
pagenumberqueryTrang (default: 0)
sizenumberquerySố lượng (default: 20, max: 100)

Response:

{
"status": 200,
"data": [
{
"id": "69cb3e99ef4af67a8ae4d72a",
"type": "company_insider_transaction",
"title": "Nebula AI",
"summary": "Chủ tịch HQC gom thêm 23,6 triệu cổ phiếu khi giá ở đáy 52 tuần",
"readStatus": false,
"isLatest": true,
"createdDate": 1774060800,
"ticker": "HQC",
"iconType": "company_insider_transaction"
}
],
"total": 42
}

2. Đếm thông báo chưa đọc (Badge)

GET /api/notification/v2/{appCode}/message/count

Response: { "status": 200, "data": 5 }

3. Đánh dấu đã xem (Clear badge)

POST /api/notification/v2/{appCode}/message/update-view-all

Gọi khi người dùng mở panel thông báo. Xóa badge count.

4. Đánh dấu đã đọc (1 item)

POST /api/notification/v2/{appCode}/message/update-read/{id}

5. Đánh dấu tất cả đã đọc

POST /api/notification/v2/{appCode}/message/update-read-all

Tích hợp Chat

Flow chi tiết

Bước 1: Mở Notification Draft

Khi click vào notification item, navigate tới chat với source params:

Web:  /chat?source=notification&source_id={notification_id}
App: Mở màn hình chat, truyền source="notification", source_id={notification_id}

Bước 2: Stream nội dung qua WebSocket

Sau khi WebSocket connected + authenticated, gửi event fetch_context:

{
"type": "fetch_context",
"source": "notification",
"source_id": "69cb3e99ef4af67a8ae4d72a"
}

Server sẽ stream response theo SSE protocol (có event_id):

// 1. Message start
{
"type": "message_start",
"message_type": "context",
"source": "notification",
"source_id": "69cb3e99ef4af67a8ae4d72a",
"message_id": "0b1a7d03-6e81-49d8-aa60-41683860c178",
"event_id": "evt-1"
}

// 2. Text content (chunked - typing effect)
{ "type": "content_block_start", "index": 0, "content_block": { "type": "text" }, "event_id": "evt-2" }
{ "type": "content_block_delta", "index": 0, "delta": { "type": "text_delta", "text": "Chủ tịch HQC " }, "event_id": "evt-3" }
{ "type": "content_block_delta", "index": 0, "delta": { "type": "text_delta", "text": "gom thêm 23,6 " }, "event_id": "evt-4" }
// ... more text chunks
{ "type": "content_block_stop", "index": 0, "event_id": "evt-N" }

// 3. CTA block (single event, full JSON)
{
"type": "content_block_start",
"index": 1,
"content_block": {
"type": "cta",
"intro": "Bạn muốn tìm hiểu thêm gì?",
"items": [
{ "label": "Định giá: Cổ phiếu đang đắt hay rẻ?", "description": "Biết giá trị thực để mua/bán có cơ sở" },
{ "label": "Xu hướng giá & Thời điểm hành động", "description": "Xác định vùng hỗ trợ, kháng cự" },
{ "label": "Gợi ý phân bổ vốn & quản lý rủi ro", "description": "Xác định tỷ trọng an toàn" }
]
},
"event_id": "evt-CTA"
}
{ "type": "content_block_stop", "index": 1, "event_id": "evt-CTA-stop" }

// 4. Message stop
{ "type": "message_stop", "event_id": "evt-end" }

Lưu ý quan trọng:

  • Text chunks stream giống agent response bình thường (typing effect)
  • CTA block chỉ chứa label + description, không chứa prompt (prompt được resolve ở backend)
  • CTA items render dạng buttons cho user chọn

Bước 3: User tương tác

Khi user click CTA hoặc gõ text, gửi WS chat message kèm sourcesource_id:

{
"type": "chat",
"message": "Định giá: Cổ phiếu đang đắt hay rẻ?\nBiết giá trị thực để mua/bán có cơ sở",
"source": "notification",
"source_id": "69cb3e99ef4af67a8ae4d72a"
}

Backend sẽ:

  1. Tạo session mới
  2. Lưu notification content vào history (AI message)
  3. Inject notification context vào agent (hidden system message)
  4. Match CTA prompt nếu user chọn CTA
  5. Agent xử lý và stream response

Sau khi session tạo, nhận new_session_id từ stream events → cập nhật URL/state.

Bước 4: Chat tiếp tục

Sau khi agent respond, user có thể chat tiếp bình thường trong session đó. Notification context đã được lưu trong checkpoint.

UI Guidelines

Notification List Panel

┌──────────────────────────────┐
│ ← Thông báo ② ☑ │ ← Back, title, badge, mark-all-read
├──────────────────────────────┤
│ [Tất cả] [Sự kiện] [Hệ thống] │ ← Tabs (groupType filter)
├──────────────────────────────┤
│ Summary notification 1 ● │ ← Unread dot
│ 23 phút trước │
├──────────────────────────────┤
│ Summary notification 2 │ ← Read (no dot)
│ 2 giờ trước │
├──────────────────────────────┤
│ ... │
└──────────────────────────────┘

Notification Draft View

┌──────────────────────────────┐
│ [Notification content text │ ← Streamed with typing effect
│ displayed as AI message] │
│ │
│ ┌────────────────────────┐ │
│ │ Định giá: Cổ phiếu... │ │ ← CTA buttons
│ │ Biết giá trị thực... │ │
│ └────────────────────────┘ │
│ ┌────────────────────────┐ │
│ │ Xu hướng giá & ... │ │
│ │ Xác định vùng hỗ trợ │ │
│ └────────────────────────┘ │
│ │
│ [Input: Hỏi bất kỳ điều gì] │ ← Chat input (user có thể gõ tự do)
└──────────────────────────────┘

Badge

  • Bell icon hiện badge count khi > 0
  • Count > 10 hiện 10+
  • Mở panel → gọi update-view-all → badge reset về 0

Tab Filters

TabgroupType paramMô tả
Tất cả(trống)Tất cả thông báo
Sự kiệnsignalTín hiệu, sự kiện doanh nghiệp
Hệ thốngotherThông báo hệ thống

Xử lý Edge Cases

CaseXử lý
Notification bị xóa/hết hạnfetch_context trả text lỗi, không có CTA
User gõ text thay vì click CTAVẫn hoạt động, source_id gửi kèm → agent có context
F5 trên URL ?source=notificationShow skeleton loading → init xong → stream content
Click notification khác khi đang streamReset state + navigate mới + stream content mới
Notification không có CTAChỉ hiện content text, user gõ tự do