# Frontend Integration Guide — Manim Agent API > **Base URL**: `https:///v1` > **Interactive Docs**: `/docs` (Swagger) · `/scalar` (Premium UI) · `/openapi.json` (Raw spec) --- ## 1. Authentication Hệ thống sử dụng **Supabase Auth** với JWT (HS256). | Header | Value | |--------|-------| | `Authorization` | `Bearer ` | **Lấy token từ Supabase JS SDK:** ```javascript const { data: { session } } = await supabase.auth.getSession(); const token = session.access_token; // Gắn vào mọi request const headers = { Authorization: `Bearer ${token}` }; ``` > **Lưu ý**: Khi `AUTH_MODE=off` (chỉ dev), server tự dùng user mặc định — không cần header. --- ## 2. Quy trình sản xuất video (Production Pipeline) ```mermaid sequenceDiagram participant FE as Frontend participant API as API Server participant W as Worker (Celery) FE->>API: 1. POST /projects API-->>FE: Project {id} FE->>API: 2. POST /projects/{id}/scenes API-->>FE: Scene {id} FE->>API: 3. POST /scenes/{id}/generate-storyboard API-->>FE: Scene (storyboard_status=pending_review) Note over FE,API: [HITL GATE] Chờ User duyệt nội dung FE->>API: 4. POST /scenes/{id}/approve-storyboard FE->>API: 5. POST /scenes/{id}/plan API-->>FE: Scene (plan_status=pending_review) Note over FE,API: [HITL GATE] Chờ User duyệt Plan & Script FE->>API: 6. POST /scenes/{id}/approve-plan FE->>API: 7. POST /scenes/{id}/approve-voice-script FE->>API: 8. POST /scenes/{id}/voice API-->>FE: 202 {voice_job_id, poll_path} loop Poll every 3s FE->>API: GET /voice-jobs/{id} end FE->>API: 9. POST /scenes/{id}/sync-timeline API-->>FE: Scene (sync_segments populated) FE->>API: 10. POST /scenes/{id}/builder-review-loop Note right of API: mode: "hitl" API-->>FE: 202 {job_id, review_loop_status=running} FE->>API: 11. WS /ws/{scene_id}?token=JWT alt Review Loop Fail/Timeout API-->>FE: Event: builder.review_loop.hitl_pending Note over FE,API: [HITL GATE] User quyết định chạy tiếp hoặc dừng FE->>API: POST /scenes/{id}/hitl-ack-builder-review else Review Loop Success API-->>FE: Event: builder.review_loop.completed end FE->>API: 12. GET /scenes/{id} API-->>FE: Scene (review_loop_status=completed) FE->>API: 13. POST /projects/{id}/render (full quality) API-->>FE: 202 {job_id} loop Poll every 5s FE->>API: GET /jobs/{id} end FE->>API: 14. GET /jobs/{id}/signed-video-url API-->>FE: {signed_url, expires_in_seconds} ``` --- ## 3. API Reference — Tất cả Endpoints ### 3.1 Projects #### `POST /v1/projects` — Tạo dự án mới - **Rate limit**: 2/minute - **Request Body**: ```json { "title": "Binary Search Visualization", "description": "Giải thích cách hoạt động của Binary Search", "source_language": "vi", "target_scenes": 3, "use_primitives": true, "config": {} } ``` - **Response** `201`: ```json { "id": "a1b2c3d4-...", "user_id": "...", "title": "Binary Search Visualization", "description": "...", "source_language": "vi", "target_scenes": 3, "config": {"use_primitives": true}, "status": "draft", "created_at": "2026-05-05T12:00:00Z", "updated_at": "2026-05-05T12:00:00Z" } ``` #### `GET /v1/projects` — Danh sách dự án - **Pagination**: Mặc định 20 items/page. Sử dụng query params `page` và `limit`. - **Response** `200`: `PaginatedResponse` ```json { "items": [...], "total": 150, "page": 1, "limit": 20, "pages": 8 } ``` #### `GET /v1/projects/{project_id}` — Chi tiết dự án - **Response** `200`: `Project` - **Error** `404`: Project không tồn tại hoặc không thuộc user #### `PATCH /v1/projects/{project_id}` — Cập nhật dự án - **Request Body**: `ProjectUpdate` (title, description, source_language, status, ...) - **Response** `200`: `Project` #### `DELETE /v1/projects/{project_id}` — Xóa dự án - **Response** `204`: No Content #### `GET /v1/projects/{project_id}/scenes` — Danh sách scenes - **Pagination**: Mặc định 100 items/page. - **Response** `200`: `PaginatedResponse` #### `POST /v1/projects/{project_id}/scenes` — Tạo scene mới - **Rate limit**: 10/minute - **Request Body**: ```json { "scene_order": 0, "storyboard_text": null, "voice_script": null } ``` - **Response** `201`: `Scene` #### `POST /v1/projects/{project_id}/scenes/batch` — Tạo/Cập nhật hàng loạt scene - **Request Body**: `Scene[]` - **Response** `200`: `Scene[]` #### `POST /v1/projects/{project_id}/approve-storyboard` — Duyệt tất cả storyboard - **Response** `200`: `Scene[]` (tất cả scenes đã được approve) - **Error** `409`: Có scene chưa ở trạng thái `pending_review` #### `GET /v1/projects/{project_id}/pipeline-runs` — Lịch sử pipeline runs - **Response** `200`: `PipelineRun[]` --- ### 3.2 Scenes — AI Pipeline #### `GET /v1/scenes/{scene_id}` — Lấy thông tin scene - **Response** `200`: `Scene` (xem schema bên dưới) #### `POST /v1/scenes/{scene_id}/generate-storyboard` — Director Agent - **Rate limit**: 5/minute · **Sync** (10-30s) - **Request Body** (optional): ```json { "brief_override": "Tập trung vào phần so sánh O(n) vs O(log n)" } ``` - **Response** `200`: `Scene` với `storyboard_status = "pending_review"` - **Errors**: `404` scene not found · `409` storyboard already approved #### `POST /v1/scenes/{scene_id}/approve-storyboard` — Duyệt storyboard - **Response** `200`: `Scene` với `storyboard_status = "approved"` - **Errors**: `409` không ở trạng thái `pending_review` · `400` storyboard trống #### `PATCH /v1/scenes/{scene_id}` — Cập nhật scene - **Request Body**: `SceneUpdate` (scene_order, storyboard_text, voice_script, ...) - **Response** `200`: `Scene` #### `DELETE /v1/scenes/{scene_id}` — Xóa scene - **Response** `204`: No Content #### `POST /v1/scenes/{scene_id}/approve` — Duyệt storyboard (Individual) - Tương đương với `approve-storyboard`. #### `POST /v1/scenes/{scene_id}/plan` — Planner Agent - **Rate limit**: 10/minute · **Sync** (10-20s) - **Precondition**: `storyboard_status = "approved"` - **Response** `200`: `Scene` với `plan_status = "pending_review"`, `planner_output` populated - **Errors**: `400` storyboard chưa approved · `404` scene not found #### `POST /v1/scenes/{scene_id}/approve-plan` — Duyệt plan - **Response** `200`: `Scene` với `plan_status = "approved"` #### `POST /v1/scenes/{scene_id}/approve-voice-script` — Duyệt voice script - **Response** `200`: `Scene` với `voice_script_status = "approved"` #### `POST /v1/scenes/{scene_id}/voice` — TTS Synthesis (Piper) - **Async** → returns `202` - **Request Body** (optional): ```json { "voice_script_override": "Nội dung lời thoại tùy chỉnh", "language": "vi" } ``` - **Response** `202`: ```json { "voice_job_id": "550e8400-...", "status": "queued", "poll_path": "/v1/voice-jobs/550e8400-..." } ``` - **Errors**: `400` thiếu text để synthesize · `400` storyboard chưa approved #### `POST /v1/scenes/{scene_id}/sync-timeline` — Đồng bộ beats với audio - **Preconditions**: Cần có `planner_output` và `timestamps` (TTS đã hoàn thành) - **Response** `200`: `Scene` với `sync_segments` populated - **Errors**: `400` thiếu plan hoặc timestamps #### `POST /v1/scenes/{scene_id}/generate-code` — Builder Agent - **Rate limit**: 5/minute · **Sync** (15-60s) - **Request Body** (optional): ```json { "enqueue_preview": true } ``` - **Response** `200`: ```json { "scene": { "...Scene object with manim_code populated..." }, "preview_job_id": "uuid-or-null" } ``` #### `POST /v1/scenes/{scene_id}/review-round` — Chạy 1 round review - **Sync** (30-120s) — nặng, nên dùng `builder-review-loop` thay thế - **Request Body** (optional): ```json { "preview_job_id": "uuid-of-completed-render" } ``` - **Response** `200`: ```json { "static_parse_ok": true, "static_imports_ok": true, "code_review": { "passed": true, "score": 8, "feedback": "..." }, "code_review_passed": true, "visual_review": { "passed": true, "score": 7, "feedback": "..." }, "visual_review_passed": true, "early_stop": true, "metrics": {} } ``` #### `POST /v1/scenes/{scene_id}/builder-review-loop` — Full Orchestrator - **Rate limit**: 2/minute · **Async** → returns `202` - **Precondition**: `storyboard_status = "approved"` - **Request Body** (optional): ```json { "mode": "auto", "preview_poll_timeout_seconds": 120 } ``` - `mode`: `"auto"` (tự động pass/fail) hoặc `"hitl"` (dừng để chờ user) - **Response** `202`: ```json { "scene_id": "...", "job_id": "celery-task-uuid", "review_loop_status": "running" } ``` #### `POST /v1/scenes/{scene_id}/hitl-ack-builder-review` — HITL Control - Sử dụng khi `mode = "hitl"` và `review_loop_status = "hitl_pending"` - **Request Body**: ```json { "action": "continue", "extra_rounds": 3 } ``` - `action`: `"continue"` (tiếp tục loop) · `"revert"` (reset về trạng thái ban đầu) · `"stop"` (dừng hẳn) - **Response** `200`: ```json { "scene": { "...updated Scene..." }, "message": "Continued in background (job_id=...)." } ``` --- ### 3.3 Render Jobs #### `POST /v1/projects/{project_id}/render` — Tạo render job - **Rate limit**: 5/minute - **Idempotency**: Gửi Header `X-Idempotency-Key` để tránh tạo job trùng lặp khi retry. - **Request Body**: ```json { "render_type": "full", "quality": "1080p", "scene_id": "uuid-of-scene", "webhook_url": null } ``` - `quality`: `"720p"` · `"1080p"` · `"4k"` - **Response** `202`: ```json { "job_id": "...", "status": "queued" } ``` #### `GET /v1/jobs/{job_id}` — Trạng thái render job - **Response** `200`: ```json { "id": "...", "project_id": "...", "scene_id": "...", "job_type": "full", "render_quality": "1080p", "status": "rendering", "progress": 45, "logs": null, "asset_url": null, "error_code": null, "created_at": "...", "started_at": "...", "completed_at": null, "metadata": {} } ``` - `status`: `"queued"` → `"rendering"` → `"completed"` / `"failed"` / `"cancelled"` #### `GET /v1/jobs/{job_id}/signed-video-url` — Download video - **Precondition**: Job `status = "completed"` - **Response** `200`: ```json { "signed_url": "https://...supabase.co/storage/v1/object/sign/...", "expires_in_seconds": 3600 } ``` - **Error** `409`: Job chưa hoàn thành --- ### 3.4 Voice Jobs #### `GET /v1/voice-jobs/{voice_job_id}` — Trạng thái voice job - **Response** `200`: ```json { "id": "...", "project_id": "...", "scene_id": "...", "status": "completed", "progress": 100, "asset_url": "https://...signed-url...", "voice_engine": "piper", "created_at": "...", "started_at": "...", "completed_at": "..." } ``` - `status`: `"queued"` → `"synthesizing"` → `"completed"` / `"failed"` --- ### 3.5 Primitives #### `GET /v1/primitives/catalog` — Danh mục component có sẵn - **No auth required** - **Response** `200`: Danh sách các animation primitives có sẵn cho Builder Agent --- ### 3.6 Health Checks (không cần auth) | Endpoint | Mô tả | |----------|--------| | `GET /health` | Liveness probe → `{"status": "ok"}` | | `GET /ready` | Readiness probe → `{"status": "ready", "redis": true}` hoặc `503` | --- ## 4. Data Schemas ### Scene (object chính xuyên suốt pipeline) ```typescript interface Scene { id: string; // UUID project_id: string; scene_order: number; // 0-indexed // Content storyboard_text: string | null; voice_script: string | null; planner_output: object | null; // Execution plan JSON sync_segments: object | null; // Beat-to-audio alignment manim_code: string | null; // Generated Python code manim_code_version: number; // Audio audio_url: string | null; timestamps: object | null; duration_seconds: number | null; // Status fields (state machines) storyboard_status: "missing" | "pending_review" | "approved"; plan_status: "missing" | "pending_review" | "approved"; voice_script_status: "missing" | "pending_review" | "approved"; review_loop_status: "idle" | "running" | "completed" | "hitl_pending" | "failed"; created_at: string; // ISO 8601 updated_at: string; } ``` ### Project ```typescript interface Project { id: string; user_id: string; title: string; description: string | null; source_language: string; // "vi", "en" target_scenes: number | null; config: Record; status: "draft" | "processing" | "completed" | "archived"; created_at: string; updated_at: string; } ``` --- ## 5. State Machines ### Storyboard Status ``` missing → [generate-storyboard] → pending_review → [approve-storyboard] → approved ``` ### Plan Status ``` missing → [plan] → pending_review → [approve-plan] → approved ``` ### Review Loop Status ``` idle → [builder-review-loop] → running → completed → hitl_pending → [hitl-ack: continue] → running → [hitl-ack: revert] → idle → [hitl-ack: stop] → failed → failed ``` --- ## 6. WebSocket Real-time Updates ### Kết nối ``` ws:///v1/ws/{scene_id}?token= ``` ### Heartbeat Gửi `"ping"` → nhận `"pong"` ### Tin nhắn từ server ```json { "ts": "2026-05-05T12:00:00Z", "component": "builder.review_loop", "phase": "round_start", "message": "Starting Round 1", "scene_id": "uuid" } ``` ### Danh sách Phase Events | Component | Phase | Ý nghĩa | |-----------|-------|---------| | `api.scenes` | `storyboard_start` | Bắt đầu sinh storyboard | | `api.scenes` | `storyboard_ok` | Storyboard hoàn thành | | `api.scenes` | `plan_start` | Bắt đầu lên plan | | `api.scenes` | `plan_ok` | Plan hoàn thành | | `api.scenes` | `sync_ok` | Đồng bộ beats thành công | | `api.render` | `job_queued` | Render job đã vào hàng đợi | | `api.render` | `celery_enqueued` | Task đã dispatch cho worker | | `worker.render` | `job_completed` | Render xong | | `worker.tts` | `job_completed` | TTS xong | | `builder.review_loop` | `round_start` | Bắt đầu round mới | | `builder.review_loop` | `round_end` | Kết thúc round | | `builder.review_loop` | `completed` | Loop kết thúc thành công | --- ## 7. Error Handling ### Error Envelope Mọi lỗi nghiệp vụ đều trả về cấu trúc `AppException` chuẩn hóa: ```json { "error": { "code": "insufficient_funds", "message": "Không đủ tài nguyên để thực hiện thao tác", "request_id": "req_123456789", "details": { "missing": "..." } } } ``` > **request_id**: Luôn đính kèm trong header `X-Request-ID` của mọi response. ### Error Code Catalog | HTTP | Code | Mô tả | Cách xử lý | |------|------|--------|-------------| | 400 | `validation_error` | Request body không hợp lệ | Kiểm tra payload | | 401 | `http_error` | Token thiếu hoặc hết hạn | Refresh token Supabase | | 404 | `http_error` | Resource không tồn tại (hoặc không có quyền) | Kiểm tra ID | | 409 | `http_error` | Trạng thái không cho phép thao tác | Kiểm tra status | | 422 | `validation_error` | Dữ liệu không đúng ràng buộc | Xem `details[]` | | 429 | Rate limited | Vượt quá giới hạn request | Chờ và retry | | 500 | `internal_error` | Lỗi server không mong đợi | Report bug | | 503 | `redis_unavailable` | Redis/broker không khả dụng | Retry sau 30s | | 503 | `broker_unavailable` | Celery queue không khả dụng | Retry sau 30s | --- ## 8. Rate Limits | Endpoint | Limit | Lý do | |----------|-------|-------| | `POST /projects` | 2/min | Ngăn spam tạo project | | `POST /scenes/{id}/generate-storyboard` | 5/min | LLM call tốn tài nguyên | | `POST /scenes/{id}/plan` | 10/min | LLM call | | `POST /scenes/{id}/generate-code` | 5/min | LLM call nặng | | `POST /scenes/{id}/builder-review-loop` | 2/min | Pipeline dài, đắt tiền nhất | Khi bị rate limited, response trả HTTP `429` với header `Retry-After`. --- ## 9. Polling Strategy | Resource | Interval | Timeout | Ghi chú | |----------|----------|---------|---------| | Voice Job | 2-3s | 5 phút | Status: `queued` → `synthesizing` → `completed` | | Render Job | 5-10s | 15 phút | Dùng WebSocket nếu có thể | | Review Loop | 10s | 30 phút | **Nên dùng WebSocket** thay vì polling | **Ví dụ polling code:** ```javascript async function pollJob(url, intervalMs = 3000, timeoutMs = 300000) { const start = Date.now(); while (Date.now() - start < timeoutMs) { const res = await fetch(url, { headers }); const data = await res.json(); if (data.status === "completed") return data; if (data.status === "failed") throw new Error(data.error_code); await new Promise(r => setTimeout(r, intervalMs)); } throw new Error("Polling timeout"); } ``` --- ## 10. Input Constraints | Field | Max Length | Ghi chú | |-------|-----------|---------| | `title` (Project) | 500 chars | Bắt buộc, ≥1 char | | `description` (Project) | 20,000 chars | Tùy chọn | | `storyboard_text` (Scene) | 20,000 chars | Tùy chọn | | `voice_script` (Scene) | 200,000 chars | Kịch bản lời thoại | | `voice_script_override` | 20,000 chars | Override text cho TTS | | `brief_override` | 20,000 chars | Ghi chú thêm cho Director | | `target_scenes` | 1-20 | Số lượng scene mục tiêu | | `scene_order` | ≥ 0 | 0-indexed | | `source_language` | 2-16 chars | Mã ngôn ngữ (vi, en) | --- ## 11. Quick Start Code (JavaScript) ```javascript const API = "https://your-api.hf.space/v1"; const headers = { "Authorization": `Bearer ${token}`, "Content-Type": "application/json" }; // 1. Create project const project = await fetch(`${API}/projects`, { method: "POST", headers, body: JSON.stringify({ title: "Demo Video", target_scenes: 1 }) }).then(r => r.json()); // 2. Create scene const scene = await fetch(`${API}/projects/${project.id}/scenes`, { method: "POST", headers, body: JSON.stringify({ scene_order: 0 }) }).then(r => r.json()); // 3. Generate storyboard (sync, ~20s) const s1 = await fetch(`${API}/scenes/${scene.id}/generate-storyboard`, { method: "POST", headers }).then(r => r.json()); // 4. Approve storyboard await fetch(`${API}/scenes/${scene.id}/approve-storyboard`, { method: "POST", headers }); // 5. Generate plan (sync, ~15s) await fetch(`${API}/scenes/${scene.id}/plan`, { method: "POST", headers }).then(r => r.json()); // 6. Approve plan + voice script await fetch(`${API}/scenes/${scene.id}/approve-plan`, { method: "POST", headers }); await fetch(`${API}/scenes/${scene.id}/approve-voice-script`, { method: "POST", headers }); // 7. TTS (async) const voice = await fetch(`${API}/scenes/${scene.id}/voice`, { method: "POST", headers }).then(r => r.json()); const voiceResult = await pollJob(`${API}${voice.poll_path}`, 3000, 300000); // 8. Sync timeline await fetch(`${API}/scenes/${scene.id}/sync-timeline`, { method: "POST", headers }); // 9. Builder-review loop (async) const loop = await fetch(`${API}/scenes/${scene.id}/builder-review-loop`, { method: "POST", headers, body: JSON.stringify({ mode: "auto" }) }).then(r => r.json()); // 10. Monitor via WebSocket const ws = new WebSocket(`wss://your-api.hf.space/v1/ws/${scene.id}?token=${token}`); ws.onmessage = (e) => console.log("Event:", JSON.parse(e.data)); ``` --- ## 12. Cơ chế Human-In-The-Loop (HITL) Hệ thống được thiết kế để AI và Con người phối hợp. Có 2 loại can thiệp chính: ### 12.1 Gated Approvals (Chốt chặn duyệt) Các endpoint `/approve-*` không chỉ là thủ tục, chúng là **điều kiện bắt buộc** để mở khóa các bước tiếp theo: - Không duyệt Storyboard → Không thể chạy Planner. - Không duyệt Plan/Voice Script → Không thể chạy Builder (vì Builder cần plan cuối cùng để viết code). **Tại sao?**: Để tránh việc AI sinh code dựa trên một kịch bản sai, gây lãng phí tài nguyên LLM. ### 12.2 Decision Gate (Cổng quyết định trong vòng lặp) Khi chạy `builder-review-loop` với `mode: "hitl"`, nếu AI không thể tự sửa lỗi sau số vòng lặp quy định (thường là 3-5 vòng), hệ thống sẽ: 1. Phát event `builder.review_loop.hitl_pending` qua WebSocket. 2. Chuyển `review_loop_status` sang `hitl_pending`. 3. **Dừng lại** và giữ nguyên trạng thái. **Frontend cần làm gì?**: - Hiển thị thông báo cho User: "AI đang gặp khó khăn, bạn muốn làm gì?". - Hiển thị code hiện tại và lỗi (lấy từ `Scene.manim_code` và log). - Gọi `POST /v1/scenes/{id}/hitl-ack-builder-review` với một trong các action: - `continue`: Cho AI thêm cơ hội (set `extra_rounds`). - `revert`: Quay về trạng thái ban đầu để user tự sửa code/plan bằng tay. - `stop`: Đánh dấu thất bại và dừng pipeline của scene này.