# Jimeng Clone — AI Video Generation Platform

> This file is the single source of truth for AI-assisted development on this project.
> AI agents (including Claude Code) should read this file first before making any changes.

## Quick Start

### Development Environment
- **Backend**: Python 3.12 + Django 4.2 + DRF + SimpleJWT
- **Frontend**: React 18 + Vite 6 + TypeScript 5 + Zustand
- **Database**: SQLite (dev) / Aliyun RDS MySQL (prod)
- **UI Library**: Arco Design (`@arco-design/web-react`)
- **Charts**: ECharts 6 + echarts-for-react

### Setup Commands
```bash
# Backend
cd backend
python -m venv venv && source venv/bin/activate
pip install -r requirements.txt
python manage.py migrate
python manage.py runserver 8000

# Frontend
cd web
npm install
npm run dev          # Dev server at localhost:5173
npm run build        # Production build: tsc -b && vite build
```

### Test Commands
```bash
# Frontend unit tests
cd web && npx vitest run

# Backend checks
cd backend && python manage.py check
```

### Admin Credentials (Dev)
- Username: `admin` / Password: `admin123`

## Project Architecture

### Directory Structure
```
jimeng-clone/
├── backend/                 # Django REST Framework backend
│   ├── config/              # Django settings, urls, wsgi
│   │   ├── settings.py      # Main config (DB, CORS, JWT, apps)
│   │   └── urls.py          # Root URL routing
│   ├── apps/
│   │   ├── accounts/        # User auth: models, views, serializers, urls
│   │   └── generation/      # Video generation: models, views, serializers, urls
│   ├── utils/               # Shared utilities (geo_client, anomaly_detector, alert_service, tos_client)
│   ├── data/                # Offline data files (ip2region.xdb)
│   ├── requirements.txt     # Python dependencies
│   └── Dockerfile           # Python 3.12 + gunicorn
├── web/                     # React 18 + Vite frontend
│   ├── src/
│   │   ├── App.tsx          # Root router (all routes defined here)
│   │   ├── main.tsx         # Entry point
│   │   ├── pages/           # Page-level components (one per route)
│   │   ├── components/      # Reusable UI components
│   │   ├── store/           # Zustand state stores
│   │   ├── lib/             # Utilities (API client, log center)
│   │   └── types/           # TypeScript type definitions
│   ├── nginx.conf           # Production nginx config
│   └── Dockerfile           # Node 18 + nginx
├── k8s/                     # Kubernetes deployment configs
├── docs/                    # PRD, design review documents
├── .gitea/workflows/        # CI/CD pipeline
└── .autonomous/             # Autonomous skill task tracking
```

### Key Files (Most Commonly Modified)
- `web/src/App.tsx` — Route definitions. Modify when adding new pages.
- `web/src/store/auth.ts` — Auth state (login/logout/JWT refresh)
- `web/src/store/generation.ts` — Video generation state & API calls
- `web/src/store/inputBar.ts` — Input bar state (mode, files, prompt)
- `web/src/lib/api.ts` — Axios client with JWT interceptor. API base URL configured here.
- `web/src/types/index.ts` — All TypeScript interfaces
- `backend/config/settings.py` — Django settings (DB, CORS, JWT, installed apps)
- `backend/config/urls.py` — Root URL routing
- `backend/apps/accounts/models.py` — User model (extends AbstractUser)
- `backend/apps/generation/models.py` — GenerationRecord + QuotaConfig models
- `backend/apps/generation/views.py` — All API endpoints (video, admin, profile)
- `backend/apps/generation/urls.py` — API URL patterns

## Development Conventions

### Code Style
- **Frontend**: TypeScript strict mode, functional components, named exports
- **Backend**: PEP 8, function-based views with `@api_view` decorator
- **CSS**: Inline styles + Arco Design component props (no separate CSS modules)

### Naming Conventions
- **React Components**: PascalCase files (e.g., `LoginPage.tsx`, `InputBar.tsx`)
- **Stores**: camelCase files (e.g., `auth.ts`, `generation.ts`)
- **Django Apps**: lowercase (e.g., `accounts`, `generation`)
- **API URLs**: `/api/v1/` prefix, no trailing slashes in URL patterns
- **Database**: snake_case (Django default)

### State Management Pattern
- Zustand stores in `web/src/store/`, one store per domain
- Each store exports a `use[Name]Store` hook
- API calls are defined inside stores (not in components)
- Pattern: `const doSomething = useAuthStore((s) => s.doSomething)`

### Component Pattern
- Pages in `web/src/pages/` — full-page components mounted by router
- Reusable components in `web/src/components/`
- Protected routes use `<ProtectedRoute>` wrapper, admin routes add `requireAdmin` prop

### Backend Pattern
- Function-based views with `@api_view(['GET', 'POST'])` decorator
- Auth via `@permission_classes([IsAuthenticated])`
- Admin endpoints check `request.user.is_staff`
- Singleton pattern for QuotaConfig (pk always = 1)

## API Endpoints

### Auth (`/api/v1/auth/`)
| Method | Endpoint | Description |
|--------|----------|-------------|
| POST | `/api/v1/auth/register` | User registration (disabled) |
| POST | `/api/v1/auth/login` | JWT login (returns access + refresh tokens, creates ActiveSession) |
| POST | `/api/v1/auth/token/refresh` | Refresh JWT access token |
| GET | `/api/v1/auth/me` | Get current user info + quota + team + must_change_password |
| POST | `/api/v1/auth/change-password` | Change own password (clears must_change_password) |

### Video Generation (`/api/v1/`)
| Method | Endpoint | Description |
|--------|----------|-------------|
| POST | `/api/v1/media/upload` | Upload reference media files |
| POST | `/api/v1/video/generate` | Submit video generation task |
| GET | `/api/v1/video/tasks` | List user's generation tasks |
| GET | `/api/v1/video/tasks/<uuid>` | Get task status & result |

### Admin (`/api/v1/admin/`)
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/api/v1/admin/stats` | Dashboard statistics & charts data |
| GET | `/api/v1/admin/users` | List all users (search, filter, paginate) |
| POST | `/api/v1/admin/users/create` | Create new user |
| GET/PUT | `/api/v1/admin/users/<id>` | Get/update user details |
| PUT | `/api/v1/admin/users/<id>/quota` | Update user quota limits |
| PUT | `/api/v1/admin/users/<id>/status` | Toggle user active status |
| GET | `/api/v1/admin/records` | List all generation records |
| GET/PUT | `/api/v1/admin/settings` | Get/update global settings (QuotaConfig) |
| GET | `/api/v1/admin/teams` | List all teams |
| POST | `/api/v1/admin/teams/create` | Create new team |
| GET/PUT | `/api/v1/admin/teams/<id>` | Get/update team details |
| POST | `/api/v1/admin/teams/<id>/topup` | Add seconds to team pool |
| PUT | `/api/v1/admin/teams/<id>/set-pool` | Directly set team total seconds pool |
| POST | `/api/v1/admin/teams/<id>/admin` | Create team admin user |
| GET | `/api/v1/admin/anomalies` | Login anomaly records (filter by team/rule/level/date) |
| POST | `/api/v1/admin/test-feishu` | Send test Feishu alert message |
| POST | `/api/v1/admin/teams/<id>/auto-learn` | Auto-learn expected regions from login history |
| POST | `/api/v1/admin/teams/<id>/apply-learned-regions` | Apply auto-learned regions to team |
| GET | `/api/v1/admin/logs` | Audit logs (filter by action/operator/date) |
| GET | `/api/v1/admin/assets/overview` | Content assets: global stats + per-team summary |
| GET | `/api/v1/admin/assets/team/<id>/members` | Content assets: team members with video stats |
| GET | `/api/v1/admin/assets/user/<id>/videos` | Content assets: user's completed videos (paginated) |

### Team Admin Assets (`/api/v1/team/assets/`)
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/api/v1/team/assets/overview` | Team stats + per-member video summary |
| GET | `/api/v1/team/assets/member/<id>/videos` | Member's completed videos (paginated) |

### Profile (`/api/v1/profile/`)
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/api/v1/profile/overview` | User consumption summary |
| GET | `/api/v1/profile/records` | User's own generation records |

## Database Models

### User (extends AbstractUser)
- `email` (unique), `daily_seconds_limit` (default: 600), `monthly_seconds_limit` (default: 6000)
- `must_change_password` (default: True) — forces password change on first login
- `team` (FK to Team), `is_team_admin`, `disabled_by` (''|'admin'|'system')
- `created_at`, `updated_at`

### Team
- `name`, `total_seconds_pool`, `total_seconds_used`, `monthly_seconds_limit`, `daily_member_limit_default`
- `expected_regions` (CharField 500, comma-separated cities for anomaly detection R1)
- `disabled_by` (''|'admin'|'system'), `is_active`

### GenerationRecord
- `user` (FK), `task_id` (UUID), `ark_task_id`, `prompt`, `mode` (universal|keyframe)
- `model` (seedance_2.0|seedance_2.0_fast), `aspect_ratio`, `duration`, `seconds_consumed`
- `status` (queued|processing|completed|failed), `result_url`, `error_message`, `reference_urls` (JSON)
- Index: (user, created_at)

### AdminAuditLog
- `operator` (FK User, SET_NULL), `operator_name` (denormalized), `action` (12 choices)
- `target_type`, `target_id`, `target_name`, `before` (JSON), `after` (JSON)
- `ip_address`, `created_at` (indexed)

### ActiveSession
- `user` (FK), `session_id` (UUID, unique), `device_type` (desktop|mobile|unknown)
- `user_agent`, `created_at`
- Used for concurrent session limiting via JWT session_id claim

### LoginRecord
- `user` (FK), `team` (FK, redundant for efficient R4/R5 queries), `ip_address`, `user_agent`
- `geo_country`, `geo_province`, `geo_city`, `geo_source` ('online'|'offline'|'skip'|'failed')
- `created_at` (indexed)

### TeamAnomalyConfig (OneToOne → Team)
- Per-team anomaly detection thresholds (null = use global default)
- `r1_enabled`, `r2_enabled`/`r2_window_seconds`, `r3_enabled`/`r3_window_seconds`/`r3_max_count`
- `r4_enabled`/`r4_window_seconds`/`r4_city_count`, `r5_enabled`/`r5_days`/`r5_country_count`

### LoginAnomaly
- `team` (FK), `user` (FK), `login_record` (FK)
- `level` (warning|critical), `rule` (region_mismatch|impossible_travel|login_frequency|multi_city|overseas_ip_diversity)
- `detail` (JSON), `alerted`, `auto_disabled`, `disabled_target` (user|team|'')
- `created_at` (indexed)

### QuotaConfig (Singleton, pk=1)
- `default_daily_seconds_limit`, `default_monthly_seconds_limit`
- `announcement`, `announcement_enabled`
- `max_desktop_sessions` (default: 1), `max_mobile_sessions` (default: 0)
- Anomaly detection global defaults: `anomaly_detection_enabled`, R1-R5 enabled/thresholds
- `feishu_alert_mobiles`, `sms_alert_mobiles`, `alert_cooldown_seconds`
- `updated_at`

## Frontend Routes

| Path | Component | Auth | Description |
|------|-----------|------|-------------|
| `/login` | LoginPage | No | Login page |
| `/` | VideoGenerationPage | Yes | Main video generation UI |
| `/profile` | ProfilePage | Yes | User consumption overview |
| `/admin/dashboard` | DashboardPage | Admin | Stats & charts |
| `/admin/users` | UsersPage | Admin | User management |
| `/admin/records` | RecordsPage | Admin | Generation records |
| `/admin/settings` | SettingsPage | Admin | Global quota, announcement & anomaly detection config |
| `/admin/security` | AnomalyLogPage | Admin | Login anomaly records (security log) |
| `/admin/logs` | AuditLogsPage | Admin | Admin operation audit logs |
| `/admin/assets` | AdminAssetsPage | Admin | Content assets (team→member→video hierarchy) |
| `/team/assets` | TeamAssetsPage | TeamAdmin | Team content assets (member→video hierarchy) |

## Incremental Development Guide

### How to Add Features to This Project

This project was generated and maintained using **agent-auto**（多 Agent 协作自动开发系统）。
项目路径：`/Users/maidong/Desktop/zyc/研究openclaw/agent-auto/`

增量开发有两种方式：

#### 方式 1：Direct AI Development（小改动）
Bug 修复、UI 微调、单个接口等小改动，直接在 Claude Code 中描述需求即可。
Claude Code 会读取本文件（CLAUDE.md）理解项目上下文。

示例：
- "Add a logout button to the navbar"
- "Fix the date format in the records table"
- "修改轮询间隔为 3 分钟"
- "Change the default daily quota from 600 to 1200 seconds"

**IMPORTANT — 每次改动后必须更新文档：**

1. **更新 `CLAUDE.md`**（本文件）：
   - 新 API 端点 → 加到 API Endpoints 表
   - 新页面/路由 → 加到 Frontend Routes 表
   - 新 model/字段 → 加到 Database Models 区
   - 配置变更 → 更新对应区
   - 始终在底部 **Change History** 表加一行

2. **更新 Claude Code memory**（如有）：
   - 更新 `~/.claude/projects/` 下的 memory 文件
   - 确保跨会话的 AI 也能了解变更

3. **非架构性改动也要记录**：
   - Bug fix / UI 微调 / 依赖更新 / 配置值变更 → 都加到 Change History

未更新文档会导致后续 AI 会话使用过期上下文，引发冲突或重复工作。

#### 方式 2：agent-auto（大功能 / 全栈新模块）
涉及 3+ 文件、新模块、跨模块重构等复杂需求，使用 agent-auto 自动完成全流程：

```
需求 → PRD → HTML 原型 → 设计评审 → 全栈代码 → 测试 → Bug 修复循环 → 完成
```

**运行命令：**
```bash
cd /Users/maidong/Desktop/zyc/研究openclaw/agent-auto

# 增量需求（在已有项目上加功能）
npx tsx src/index.ts --resume /Users/maidong/Desktop/zyc/研究openclaw/视频生成平台/jimeng-clone "你的增量需求描述"

# 崩溃恢复 / 继续执行
npx tsx src/index.ts --resume /Users/maidong/Desktop/zyc/研究openclaw/视频生成平台/jimeng-clone
```

**agent-auto 会自动：**
1. 读取 `.agent-auto/state.json` 恢复项目状态
2. product-agent 修订 PRD（`docs/prd.md`）
3. design-agent 更新 HTML 原型（`prototype/`）
4. product-agent 设计评审（`docs/design-review.md`，APPROVED/REJECTED 循环）
5. dev-agent 增量开发（保留已有代码，只新增/修改）
6. test-agent 测试 + Bug 分类（`test-report.md`）
7. Bug 修复循环（CODE_BUG → dev-agent / DESIGN_BUG → design-agent / REQUIREMENT_BUG → product-agent）
8. 全部通过 → COMPLETE

**状态持久化**：进度保存在 `.agent-auto/state.json`，中断后可随时 `--resume` 继续。

**Agent 记忆**：每个 Agent 有独立记忆文件（`.agent-auto/{agent-name}/memory.md`），跨轮次保持上下文。

#### 变更日志维护
每次需求开发完成、测试验收通过后，**必须**在 `docs/changelog.md` 中记录本次变更：
- 按日期倒序添加新条目（最新的在最前面）
- 包含：变更标题、状态/验收、变更内容、变更文件、触发原因
- 文件末尾有模板可直接复制使用
- Direct AI 和 agent-auto 两种方式均需记录

#### 何时用哪种方式
| 场景 | 方式 |
|------|------|
| Bug 修复 / 单行改动 | Direct AI |
| UI 微调（按钮、颜色、文案） | Direct AI |
| 添加单个 API 端点 | Direct AI |
| 修改已有组件行为 | Direct AI |
| 新功能模块（3+ 文件） | agent-auto |
| 新增整页 CRUD（如管理页面） | agent-auto |
| 跨多文件重构 | agent-auto |
| 新增子系统（通知、支付、权限） | agent-auto |

### How to Add a New Page

1. Create page component in `web/src/pages/NewPage.tsx`
2. Add route in `web/src/App.tsx` inside `<Routes>`:
   ```tsx
   <Route path="/new-page" element={<ProtectedRoute><NewPage /></ProtectedRoute>} />
   ```
3. If it needs state, create store in `web/src/store/newPage.ts`
4. Add API calls in the store using `api.get()` / `api.post()` from `web/src/lib/api.ts`

### How to Add a New API Endpoint

1. Add view function in `backend/apps/generation/views.py` (or relevant app):
   ```python
   @api_view(['GET'])
   @permission_classes([IsAuthenticated])
   def new_endpoint_view(request):
       return Response({'data': ...})
   ```
2. Add URL pattern in `backend/apps/generation/urls.py`:
   ```python
   path('new/endpoint', views.new_endpoint_view, name='new_endpoint'),
   ```
3. Add TypeScript types in `web/src/types/index.ts`
4. Add API call in the relevant Zustand store

### How to Add a New Database Model

1. Define model in `backend/apps/generation/models.py` (or relevant app)
2. Run migrations:
   ```bash
   cd backend
   python manage.py makemigrations
   python manage.py migrate
   ```
3. Register in admin: `backend/apps/generation/admin.py`
4. Create serializer if needed: `backend/apps/generation/serializers.py`

## Environment Variables

| Variable | Description | Required |
|----------|-------------|----------|
| `DATABASE_URL` | MySQL connection string (prod) | Prod only |
| `SECRET_KEY` | Django secret key | Yes |
| `TOS_ACCESS_KEY` | Volcano Engine TOS AccessKeyId | Yes (upload) |
| `TOS_SECRET_KEY` | Volcano Engine TOS SecretAccessKey | Yes (upload) |
| `TOS_BUCKET` | Volcano TOS bucket name (default: `airdrama-media`) | Yes (upload) |
| `TOS_ENDPOINT` | TOS endpoint URL (default: `https://tos-cn-beijing.volces.com`) | Yes (upload) |
| `TOS_REGION` | TOS region (default: `cn-beijing`) | Yes (upload) |
| `TOS_CDN_DOMAIN` | TOS CDN domain for permanent URLs (default: `https://airdrama-media.tos-cn-beijing.volces.com`) | Yes (upload) |
| `ARK_API_KEY` | Volcano Engine ARK API key for Seedance | Yes (video gen) |
| `ARK_BASE_URL` | ARK API base URL (default: `https://ark.cn-beijing.volces.com/api/v3`) | No |
| `ALIYUN_IP_GEO_APPCODE` | Aliyun marketplace IP geolocation API AppCode | Yes (anomaly detection) |
| `FEISHU_APP_SECRET` | Feishu bot app secret for alert notifications | Yes (anomaly alerts) |

## Deployment

- **CI/CD**: Gitea Actions (`.gitea/workflows/deploy.yaml`)
- **Registry**: Huawei Cloud SWR
- **Orchestration**: Kubernetes (`k8s/` directory)
- **Backend URL**: `airflow-studio-api.airlabs.art`
- **Frontend URL**: `airflow-studio.airlabs.art`
- **Database**: Aliyun RDS MySQL (`rm-7xv1uaw910558p1788o.mysql.rds.aliyuncs.com:3306`)

## Testing

- **Unit Tests**: 227 tests in `web/` via Vitest — `cd web && npx vitest run`
- **E2E Tests**: 49 tests via Playwright — `cd web && npx playwright test`
- **Backend**: Django system checks — `cd backend && python manage.py check`
- **Status**: 211 passing, 16 pre-existing path-resolution failures in phase2/phase3 tests
- **Test DB Isolation**: E2E tests use `TESTING=true` env var → backend writes to `test_db.sqlite3` (not `db.sqlite3`). Playwright auto-starts a test backend on port 8000 with this env var. Dev data is never polluted by tests.

## Design Guidelines

- **Color Palette**: `#0a0a0f` (bg), `#111118`, `#16161e`, `#2a2a38`, `#00b8e6` (accent)
- **Typography**: Noto Sans SC (Chinese) + Space Grotesk (UI) + JetBrains Mono (code)
- **Style**: Linear/Vercel dark theme aesthetic
- **Responsive**: `grid-cols-4 max-lg:grid-cols-2`

## Change History

> 完整变更日志见 [`docs/changelog.md`](docs/changelog.md)，以下为摘要。

| Date | Change | Scope |
|------|--------|-------|
| 2025-01 | Phase 1: Core video generation UI | Frontend |
| 2025-02 | Phase 2: Backend + Auth + Admin panel | Full stack |
| 2025-03 | Phase 3: Seconds-based quota, profile page, ECharts dashboard | Full stack |
| 2026-03 | Added CLAUDE.md for AI-assisted incremental development | Documentation |
| 2026-03-13 | Phase 4: TOS upload + Seedance API integration + DB persistence | Full stack |
| 2026-03-13 | Test DB isolation: TESTING=true → test_db.sqlite3, Playwright auto-starts test backend | Backend + Config |
| 2026-03-13 | Removed mock data fallback from DashboardPage — charts show real data only | Frontend |
| 2026-03-13 | LogCenterMiddleware: Django 中间件兜底异常上报（捕获 DRF 之外的导入/中间件/URL 解析错误） | Backend |
| 2026-03-13 | custom_exception_handler: 未处理异常返回 JSON 500 而非 HTML | Backend |
| 2026-03-13 | 前端轮询间隔从 5 秒改为 3 分钟 | Frontend |
| 2026-03-13 | CLAUDE.md 增量开发指南更新为 agent-auto（替换 Autonomous Skill） | Documentation |
| 2026-03-15 | v0.8.0: 音频引用支持 + 视频 TOS 持久化 + 移除硬编码密钥 + 渐进式轮询 | Full stack |
| 2026-03-15 | TOS 桶切换到 airdrama-media (cn-beijing)，K8s Secret 注入 TOS 密钥 | Infra |
| 2026-03-15 | v0.8.1: Seedance API 友好错误提示 (SeedanceAPIError) + 前端 Mock 数据清理 | Full stack |
| 2026-03-16 | v0.8.2: 管理后台 UI 修复 — DatePicker/Select 暗色主题、公告跑马灯、Toast 全局化、失败原因 tooltip | Full stack |
| 2026-03-16 | v0.8.3: 团队详情抽屉→弹窗重构(VideoDetailModal 规范) + 修改秒数池功能 + member_count 修复 | Full stack |
| 2026-03-16 | v0.8.4: 管理员操作审计日志 — AdminAuditLog 模型 + 12 处埋点 + 日志查询页面 | Full stack |
| 2026-03-18 | v0.9.0: 首次登录强制改密 — must_change_password 字段 + ForceChangePasswordModal | Full stack |
| 2026-03-18 | v0.9.0: 并发会话限制 — ActiveSession + SessionJWT + 可配置桌面/移动端会话数 | Full stack |
| 2026-03-18 | v0.9.0: 登录记录 — LoginRecord 模型（IP + User-Agent）为异常检测打基础 | Backend |
| 2026-03-18 | v0.9.0: Token 生命周期缩短 — access 30min, refresh 1天 | Backend |
| 2026-03-18 | v0.9.0: 内容资产页 — 超管/团队管三级折叠式资产浏览（团队→成员→视频） | Full stack |
| 2026-03-18 | v0.9.1: 登录风控第二期 — IP归属地解析 + 5条异常检测规则(R1-R5) + 飞书告警 + 自动封禁 | Full stack |
| 2026-03-18 | v0.9.1: 安全日志页面 — LoginAnomaly 记录列表，按团队/规则/级别/时间筛选 | Frontend |
| 2026-03-18 | v0.9.1: 系统设置页 — 异常检测总开关、R1-R5默认阈值、飞书接收人+测试、告警冷却 | Frontend |
| 2026-03-18 | v0.9.1: 团队管理 — 预期登录城市(必填) + 自动学习 + disabled_by 来源标签 | Full stack |
| 2026-03-18 | v0.9.1: 前端拦截器 — user_disabled/team_disabled 错误码处理，弹窗提示后跳登录 | Frontend |
| 2026-03-19 | fix: LoginRecord 创建时显式传 geo 空字段，修复 MySQL 严格模式 IntegrityError | Backend |

### Phase 4 Details (2026-03-13)

**Backend new files:**
- `backend/utils/tos_client.py` — Volcano Engine TOS upload via official `tos` SDK
- `backend/utils/seedance_client.py` — Seedance/ARK API client (create_task, query_task)

**Backend changes:**
- `backend/config/settings.py` — Added TOS and ARK config
- `backend/apps/generation/models.py` — Added `ark_task_id`, `result_url`, `error_message`, `reference_urls` (JSONField)
- `backend/apps/generation/views.py` — New endpoints: `upload_media_view`, `video_tasks_list_view`, `video_task_detail_view`; Rewritten `video_generate_view` with Seedance integration
- `backend/apps/generation/urls.py` — Added 3 routes: `media/upload`, `video/tasks`, `video/tasks/<uuid>`
- `backend/apps/generation/serializers.py` — Added `references` field to VideoGenerateSerializer
- `backend/requirements.txt` — Added `tos>=2.7`, `requests>=2.31`
- Migration: `0003_generationrecord_ark_task_id_and_more.py`

**Frontend changes:**
- `web/src/types/index.ts` — Added `BackendTask` interface, `tosUrl` to `UploadedFile`, `taskId`/`errorMessage` to `GenerationTask`
- `web/src/lib/api.ts` — Added `mediaApi.upload()`, `videoApi.getTasks()`, `videoApi.getTaskStatus()`; Changed `videoApi.generate()` to JSON
- `web/src/store/generation.ts` — Complete rewrite: async TOS upload + API calls + polling + `loadTasks()` for refresh persistence
- `web/src/components/VideoGenerationPage.tsx` — Added `loadTasks()` on mount
- `web/src/components/GenerationCard.tsx` — Video player for results, error state display

**Flow:** Upload files → TOS → Generate API (Seedance) → DB record → Poll status → Display result
