VDS AI Factory · Thiết kế đích · rev 3
Answer Card multi-channel — một template, nhiều kênh
Card template có N tab thiết kế (Web / FB / Zalo / text). Runtime gọi
render(template_id, type, data) → trả string đúng markup kênh: web=HTML,
zalo/fb=text theo syntax kênh, gửi qua channel API. Render server-side hết . Một cơ chế cho agent + workflow.
template N-tab · web/fb/zalo/text
render(id, type, data) → string
Zalo/FB qua tool-exec oa.send_message
ADR-027 + ADR-028 rev3
1 · Block agent sinh + render function
Agent/node sinh block trong content — type = channel đích
``` card
{"template_id" : "goi_cuoc_data", "version" : 3,
"type" : "web" , // web=HTML · zalo/fb=text · agent truyền
"data" : {"ten": "V90", "gia": "90.000đ"}} // data sinh runtime
```
→ render(template_id, type, data, allowlist) → output theo channel
2 · type quyết định output
web
HTML string → SSE → chat UI
FE nhét vào bubble
zalo
text syntax Zalo → tool-exec
oa.send_message → Zalo API
fb
text syntax FB → tool-exec
→ FB channel API
text
plain text fallback
SMS / kênh không format
3 · Đường đi — một seam, nhiều kênh
1 · sinh
LLM / node
block {template_id, type, data} trong text — card THAY text
2 · render
render lib
render(id,type,data): fetch variant [cache] → validate → strip allowlist
3 · output
theo type
card_json / Zalo JSON / FB JSON / text
4a · web
chat UI
card_json qua SSE → answer-card.tsx render
4b · zalo/fb
tool-exec
message JSON → oa.send_message → channel API
4 · Ví dụ output — cùng data, 4 format
Input chung — agent sinh MỘT lần (chỉ đổi type)
``` card
{"template_id" : "goi_cuoc_data", "version" : 3, "type" : "<web|fb|zalo|text>" ,
"data" : {"ten": "V90", "gia": "90.000đ/tháng", "dung_luong": "30GB",
"uu_dai": "Gọi nội mạng miễn phí", "anh": "https://cdn.example.com/v90.png",
"dang_ky_url": "https://vm.viettel.vn/dk/v90"}}
```
web → HTML
zalo → text
fb → text
text → plain
type=web → HTML string → FE nhét vào bubble
<div class="vds-card">
<img src="https://cdn.example.com/v90.png" alt="Gói V90">
<h3>Gói V90 — 90.000đ/tháng</h3>
<ul>
<li><b>Dung lượng:</b> 30GB</li>
<li><b>Ưu đãi:</b> Gọi nội mạng miễn phí</li>
</ul>
<a href="https://vm.viettel.vn/dk/v90" class="btn">Đăng ký</a>
</div>
type=zalo → text theo syntax Zalo → tool-exec oa.send_message
Gói V90 — 90.000đ/tháng
📊 Dung lượng: 30GB
🎁 Ưu đãi: Gọi nội mạng miễn phí
👉 Đăng ký: https://vm.viettel.vn/dk/v90
type=fb → text theo syntax FB → tool-exec → FB API
*Gói V90 — 90.000đ/tháng*
• Dung lượng: 30GB
• Ưu đãi: Gọi nội mạng miễn phí
Đăng ký ngay: https://vm.viettel.vn/dk/v90
type=text → plain (SMS / kênh không format)
Gói V90 — 90.000đ/tháng
- Dung lượng: 30GB
- Ưu đãi: Gọi nội mạng miễn phí
Đăng ký: https://vm.viettel.vn/dk/v90
Output luôn là STRING — render(id, type, data) → string. Mỗi kênh có markup riêng: web=HTML, zalo=text+emoji Zalo, fb=text markdown FB (*bold*), text=plain. Cùng data, render lib map vào template variant từng kênh.
Allowlist strip áp mọi kênh — URL trong output ngoài card_action_url_allowlist bị strip trước khi gửi.
⚠ Giới hạn: text/HTML string KHÔNG có rich card native (button bấm, carousel) của Zalo/FB — button chỉ là link trong text. Muốn button native phải trả JSON attachment (đường khác, cân sau).
5 · Quyết định kiến trúc (cần ADR duyệt)
QĐ-1 Template multi-tab blocker
card_template = component kind thứ 5, N-tab {web/fb/zalo/text}. project-svc CRUD + version-svc versioning. ADR-027.
card đóng băng cùng Workspace Version; N variant lưu trong cùng component.
QĐ-2 Render server-side blocker
render(id, type, data) → STRING server-side . Web=HTML, zalo/fb=text theo syntax kênh. KHÔNG FE-parse. ADR-028.
output luôn string đúng markup kênh → adapter/FE nhét thẳng; nhất quán 1 seam.
QĐ-3 type source
Agent truyền type trong block. Không truyền → default web. Render lib chọn variant theo type.
QĐ-4 Bảo vệ + degrade
Strip allowlist + validate variant server-side. Card THAY text (không double). Template thiếu tab type → degrade text (không vỡ).
6 · Build order (provider → consumer)
1 version-svcstorage N-tab
kind card_template + v0-model + multi-tab schema (variant per channel)
T-960/967 +mở rộng
2 project-svcCRUD N-tab
CRUD card_template multi-tab (authoring N variant) + delete-gate
T-963/965 +mở rộng
3 vds-platform-commonrender lib
render(id,type,data)→output đa kênh; cache layout; validate+strip
net-new
4 agent-runtimeweb render
parse block → render(web) → card_json SSE; card THAY text
net-new
5 workflow-runtimerender
block trong answer/end → render theo channel
net-new
6 channel adapteromnichannel
Zalo/FB: render → message JSON → tool-exec oa.send_message outbound
net-new
7 studio-svc FEauthoring
authoring N-tab (thiết kế web/zalo/fb); chat UI render card_json (reuse answer-card.tsx)
net-new
Điểm còn phải chốt
Zalo/FB outbound đồng bộ hay async — web streaming đồng bộ; Zalo/FB có thể async (khách không chờ SSE). Luồng gửi cần chốt.
Ai sinh block — LLM tự do (linh hoạt, validate+degrade) vs DSL-structured. Có thể cả hai: DSL định template_id+type, LLM điền data.
Template thiếu tab — type=zalo mà chưa thiết kế tab Zalo → degrade text.
Hard gate + lịch sử rev
Rev 0 frame-riêng — 5 lỗ live-run (binding rỗng, double-render, chatflow/tool dead-end, DB read).
Rev 1 fenced-block FE — giải 5 lỗ nhưng chỉ web.
Rev 3 multi-channel server-side (chốt) — Zalo/FB ép server render; 5 lỗ xử: data runtime, card thay text, adapter chung, cache layout.
Duyệt ADR-027 + ADR-028 → /architecture-change fan-out 7 stage.
7 · Sequence — render server-side theo type
4 phase: sinh block (type) → render string theo type → web SSE → Zalo/FB channel API.
8 · Overview — bản đồ component
2 plane: control (authoring N-tab) vs data (render string theo type).
tasks/ANSWER-CARD-DESIGN.md (rev 3)
ADR-027 · ADR-028 rev3
channel: conversation-svc channel · Zalo OA tool-exec
reuse answer-card.tsx