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"}}
```
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à STRINGrender(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Đ-1Template multi-tabblocker

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Đ-2Render server-sideblocker

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Đ-3type source

Agent truyền type trong block. Không truyền → default web. Render lib chọn variant theo type.

QĐ-4Bả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)

1version-svcstorage N-tab
kind card_template + v0-model + multi-tab schema (variant per channel)
T-960/967 +mở rộng
2project-svcCRUD N-tab
CRUD card_template multi-tab (authoring N variant) + delete-gate
T-963/965 +mở rộng
3vds-platform-commonrender lib
render(id,type,data)→output đa kênh; cache layout; validate+strip
net-new
4agent-runtimeweb render
parse block → render(web) → card_json SSE; card THAY text
net-new
5workflow-runtimerender
block trong answer/end → render theo channel
net-new
6channel adapteromnichannel
Zalo/FB: render → message JSON → tool-exec oa.send_message outbound
net-new
7studio-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

Hard gate + lịch sử rev

  1. Rev 0 frame-riêng — 5 lỗ live-run (binding rỗng, double-render, chatflow/tool dead-end, DB read).
  2. Rev 1 fenced-block FE — giải 5 lỗ nhưng chỉ web.
  3. 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.
  4. 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).