Spaces:
Sleeping
Sleeping
github-actions[bot]
[Render] Cuong2004/Manim-Agent-Worker-Render @ 1d7c417 (run 25583057312)
a1fdb19 | # Frontend Integration Guide — Manim Agent API | |
| > **Base URL**: `https://<API_HOST>/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 <SUPABASE_JWT>` | | |
| **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<Project>` | |
| ```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<Scene>` | |
| #### `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<string, any>; | |
| 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://<HOST>/v1/ws/{scene_id}?token=<JWT_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. | |