# Dev Agent Memory

## 2026-03-12 Bug Fix Session

### Fixed Bugs

**BUG-001: GenerationCard model name hardcoded**
- File: `src/components/GenerationCard.tsx:54`
- Root cause: Model display was hardcoded as `Seedance 2.0` instead of using `task.model`
- Fix: Changed to `{task.model === 'seedance_2.0' ? 'Seedance 2.0' : 'Seedance 2.0 Fast'}`

**BUG-002: File upload missing size validation**
- Files: `UniversalUpload.tsx`, `InputBar.tsx` (drag-drop), `KeyframeUpload.tsx`
- Root cause: No file size checks anywhere in the upload pipeline
- PRD requirement: images < 20MB, videos < 100MB
- Fix: Added size validation with toast messages in all 3 upload entry points:
  - `UniversalUpload.handleFileChange` — filters files before `addReferences`
  - `InputBar.handleDrop` — filters after type check, before `addReferences`/`setFirstFrame`
  - `KeyframeUpload.handleFirstChange`/`handleLastChange` — checks before `setFirstFrame`/`setLastFrame`

### Previously Fixed Bugs (by prior session)
- canSubmit selector in Toolbar.tsx — was getting function reference instead of calling it
- Drag-drop accepting audio files in InputBar.tsx — now filters to image/video only

### Notes
- Model type values: `'seedance_2.0'` | `'seedance_2.0_fast'` (defined in `src/types/index.ts`)
- Dead code remains: audio type in `UploadedFile.type`, audio icon in `UniversalUpload`/`GenerationCard` — flagged for cleanup but not addressed (out of scope for bug fix)
- Build: `tsc -b && vite build` — passes cleanly

---

## Bug修复 [2026/3/12 14:40:50]
- **触发**: fix_code_bugs
- **耗时**: 123s
- **花费**: $0.0000
- **退出码**: 0
- **工作摘要**: Both CODE_BUGs have been fixed and the build passes cleanly.

---

## 2026-03-12 Phase 2 Implementation Session

### What was done
Implemented the full Phase 2 features as defined in PRD v2.1:

**BUG-001 (audio dead code) — Fixed**
- Removed `.audioThumb` CSS from `UniversalUpload.module.css` and `GenerationCard.module.css`
- TypeScript audio code was already cleaned in a prior session

**Django Backend (`backend/`)**
- Django project with `config/` settings, URLs, WSGI/ASGI
- `apps/accounts/` — Custom User model (extends AbstractUser with daily_limit, monthly_limit), register/login/me API views, JWT auth
- `apps/generation/` — GenerationRecord + QuotaConfig models, video generate API (with quota checking), admin stats/ranking/quota APIs
- SQLite for local dev (USE_MYSQL env var to switch to Aliyun RDS MySQL)
- Django admin configured for both apps
- Migrations created and applied, superuser: admin / admin123
- Virtual environment in `backend/venv/`

**Frontend Auth & Routing**
- `src/lib/api.ts` — Axios instance with JWT interceptor + auto token refresh
- `src/store/auth.ts` — Zustand auth store (login, register, logout, fetchUserInfo, initialize)
- `src/types/index.ts` — Added User, Quota, AuthTokens, AdminStats, UserRanking types
- `src/pages/LoginPage.tsx` — Login form with validation
- `src/pages/RegisterPage.tsx` — Registration form with all PRD validations
- `src/pages/AdminDashboard.tsx` — Stats cards, bar chart trend, user ranking table with inline quota editing
- `src/components/ProtectedRoute.tsx` — Auth guard with `requireAdmin` prop
- `src/components/UserInfoBar.tsx` — Top bar showing username, daily quota, admin link, logout
- `src/App.tsx` — BrowserRouter with routes: `/login`, `/register`, `/`, `/admin/dashboard`

**Backend Integration**
- `src/store/generation.ts` — addTask now calls `videoApi.generate()` and refreshes quota
- `vite.config.ts` — Proxy `/api` to `http://127.0.0.1:8000`

### Technical decisions
- Used unified `ProtectedRoute` with `requireAdmin` prop instead of separate AdminRoute
- Django 6.0 (latest) used instead of 4.2 LTS — API is compatible
- SQLite as default for dev, MySQL for production (via USE_MYSQL env var)
- Frontend API calls are fire-and-forget — the UI still uses mock progress simulation
- UserInfoBar is `position: fixed` at top of VideoGenerationPage

### Build verification
- `npm run build` — passes cleanly (tsc + vite)
- `python manage.py check` — no issues
- `python manage.py migrate` — all migrations applied

### How to run
- Frontend: `npm run dev` (port 5173)
- Backend: `cd backend && source venv/bin/activate && python manage.py runserver` (port 8000)
- Vite proxy forwards `/api/*` to backend

### Known limitations
- mysqlclient not installed in venv (requires MySQL client libs); use SQLite for dev
- Backend video generation is just a record keeper — no actual video generation
- Token refresh in axios interceptor redirects to /login on failure

---

## 代码开发 [2026/3/12 15:11:48]
- **触发**: develop_from_prototype
- **耗时**: 798s
- **花费**: $0.0000
- **退出码**: 0
- **工作摘要**: Phase 2 implementation complete (see above)

---

## 2026-03-12 Phase 2 Verification & Cleanup Session

### What was done
- **BUG-001 (audio dead code) — Fully fixed in source code**:
  - `src/types/index.ts`: Changed `'image' | 'video' | 'audio'` → `'image' | 'video'` in both UploadedFile and ReferenceSnapshot
  - `src/store/inputBar.ts`: Removed audio classification branch in `addReferences`
  - `src/components/UniversalUpload.tsx`: Removed AudioIcon component and audio thumbnail branch
  - `src/components/GenerationCard.tsx`: Removed audio thumbnail rendering branch
- **Removed redundant `src/components/auth/` directory** (ProtectedRoute and AdminRoute) — unified ProtectedRoute at `src/components/ProtectedRoute.tsx` with `requireAdmin` prop is used
- **Removed duplicate Q import** in `backend/apps/generation/views.py` (line 163)
- **Downgraded Django to 4.2 LTS** — was 6.0, now 4.2.29 (matches PRD requirement)
- **Verified all endpoints**: register (201), login (200), me with quota (200), video generate with quota tracking (202)
- **Frontend build passes**: `tsc -b && vite build` clean
- **Django check passes**: `python manage.py check` — 0 issues

---

## 代码开发 [2026/3/12 15:18:57]
- **触发**: develop_from_prototype
- **耗时**: 1392s
- **花费**: $0.0000
- **退出码**: 0
- **工作摘要**: All Phase 2 work is complete. Here's a summary: ## Completed ### BUG-001 Fixed (audio dead code) - Removed `'audio'` from type unions in `src/types/index.ts` - Removed audio classification in `src/store/inputBar.ts` - Removed `AudioIcon` and audio rendering branches in `UniversalUpload.tsx` and `GenerationCard.tsx` ### Frontend Phase 2 - **react-router-dom + axios** installed - **Auth store** (`src/store/auth.ts`) — login, register, logout, token refresh, user info - **API service** (`src/lib/ap...

---

## 代码开发 [2026/3/12 15:34:29]
- **触发**: develop_from_prototype
- **耗时**: 195s
- **花费**: $0.0000
- **退出码**: 0
- **工作摘要**: The project is fully implemented with all PRD requirements met. Here's the verification summary: ## Verification Results ### Build & Tests - **Frontend build**: `tsc -b && vite build` passes cleanly (124 modules, 619ms) - **Unit tests**: 151/151 pass (8 test files) - **Django check**: 0 issues ### Phase 1 (Frontend) - All 17 acceptance criteria met - InputBar with dark theme, correct styles (#16161e bg, #2a2a38 border, 20px radius) - Universal/Keyframe mode switching with full linkage - All drop...

---

## 2026-03-12 Bug Fix Session (Round 3)

### Fixed Bugs

**BUG-001: API 拦截器在登录端点 401 响应时错误重定向**
- File: `src/lib/api.ts:22-44`
- Root cause: Axios 401 response interceptor applied to ALL endpoints including `/auth/login`, `/auth/register`, `/auth/token/refresh`. When login credentials were wrong (401), the interceptor would try token refresh and then redirect to `/login`, causing a full page reload that cleared the error state before the user could see the error message.
- Fix: Added `isAuthEndpoint` check — the interceptor now skips token refresh and redirect for auth endpoints (`/auth/login`, `/auth/register`, `/auth/token/refresh`), allowing the 401 error to propagate normally to the calling component which can display the error message.
- Build: `tsc -b && vite build` passes cleanly

---

## Bug修复 [2026/3/12 15:38:53]
- **触发**: fix_code_bugs
- **耗时**: 62s
- **花费**: $0.0000
- **退出码**: 0
- **工作摘要**: **BUG-001 已修复** — `src/lib/api.ts` **问题**: Axios 401 响应拦截器对所有端点无差别处理，包括 `/auth/login` 自身。当用户输入错误密码时，拦截器会尝试 token 刷新，失败后执行 `window.location.href = '/login'` 全页面重定向，导致 React 应用重新加载，错误提示信息被清除。 **修复**: 在 401 拦截器入口添加 `isAuthEndpoint` 检查，排除 `/auth/login`、`/auth/register`、`/auth/token/refresh` 三个认证端点。这些端点的 401 错误会正常 reject 给调用方（LoginPage 等），由组件自行展示错误信息。 构建验证通过，无其他 CODE_BUG。

---

## 2026-03-12 Phase 3 Implementation Session

### What was done
Implemented Phase 3 features as defined in PRD v3.0: **Quota System Refactor + Admin Dashboard Redesign + User Profile Page**

**Backend Changes**
- `accounts/models.py`: Renamed `daily_limit` → `daily_seconds_limit` (default=600), `monthly_limit` → `monthly_seconds_limit` (default=6000)
- `generation/models.py`: Added `seconds_consumed` FloatField to GenerationRecord; QuotaConfig updated with seconds-based defaults + `announcement`/`announcement_enabled` fields
- `accounts/views.py`: me_view returns seconds-based quota using `Sum('seconds_consumed')` instead of `.count()`
- `generation/views.py`: Complete rewrite with all Phase 3 endpoints:
  - `video_generate_view`: Seconds-based quota checking
  - `admin_stats_view`: Seconds consumed today/month, change percentages, 30-day trend, top 10 users
  - `admin_users_list_view`: Paginated with search/status filter, annotated seconds
  - `admin_user_detail_view`, `admin_user_quota_view`, `admin_user_status_view`
  - `admin_records_view`: Paginated with date range and search
  - `admin_settings_view`: GET/PUT for QuotaConfig singleton
  - `profile_overview_view`, `profile_records_view`: User's own data
- `generation/urls.py`: 10 URL patterns for all Phase 3 endpoints
- `generation/serializers.py`: QuotaUpdateSerializer, UserStatusSerializer, SystemSettingsSerializer
- Django admin updated for new field names
- Migrations created and applied

**Frontend Changes**
- Installed `echarts` + `echarts-for-react`
- `src/types/index.ts`: All types updated for seconds-based fields (Quota, AdminStats, AdminUser, AdminUserDetail, AdminRecord, SystemSettings, ProfileOverview, PaginatedResponse)
- `src/lib/api.ts`: Full Phase 3 API client (adminApi with 8 methods, profileApi with 2 methods)
- `src/index.css`: Phase 3 design tokens (sidebar, card, success/danger/warning colors)
- `src/components/UserInfoBar.tsx`: Shows seconds remaining, added "个人中心" button
- `src/store/generation.ts`: Updated for seconds-based API response
- `src/App.tsx`: Phase 3 routing with nested admin routes via Outlet

**New Pages (all with CSS modules)**
- `AdminLayout.tsx` — Sidebar + Outlet layout with collapsible nav, 4 menu items
- `DashboardPage.tsx` — Stats cards, ECharts line chart (30-day trend), ECharts horizontal bar chart (top users), mock data for dev
- `UsersPage.tsx` — User table with search/filter, pagination, quota edit modal, user detail drawer
- `RecordsPage.tsx` — Records table with date range + username search, CSV export (with injection prevention)
- `SettingsPage.tsx` — Quota config form + announcement management with toggle
- `ProfilePage.tsx` — ECharts gauge for daily quota, progress bars, sparkline with 7d/30d toggle, consumption records list

**Removed**
- Old `AdminDashboard.tsx` + `AdminDashboard.module.css` (Phase 2 single-page admin replaced by multi-page)

### Technical decisions
- Used `echarts/core` tree-shaking (import only needed chart types) — `EChartsCoreOption` type, not `EChartsOption`
- AdminLayout uses React Router nested routes with `<Outlet />` for clean code splitting
- ProfilePage has `height: 100vh; overflow-y: auto` to handle scrolling within `#root { overflow: hidden }`
- Mock data generation in DashboardPage when backend returns empty data — ensures charts always display
- CSV export escapes formulas starting with `=`, `+`, `-`, `@` for CSV injection prevention

### Build verification
- `npm run build` — passes cleanly (741 modules, 2.16s)
- `python manage.py check` — 0 issues
- All migrations applied

---

## 代码开发 [2026/3/12 17:26:11]
- **触发**: develop_from_prototype
- **耗时**: 1178s
- **花费**: $0.0000
- **退出码**: 0
- **工作摘要**: Phase 3 implementation is complete. Here's a summary of everything done: **Backend (Django)** - Quota system refactored from call-count to seconds-based (`daily_seconds_limit=600`, `monthly_seconds_limit=6000`) - Added `seconds_consumed` field to GenerationRecord model - QuotaConfig updated with announcement fields - 10 new API endpoints covering admin stats/users/records/settings and user profile - Migrations created and applied, `manage.py check` passes **Frontend (React + TypeScript)** - Inst...

---

## 2026-03-13 Phase 4: TOS 存储 + Seedance API 集成（手动开发）

### What was done
实现了完整的火山引擎 TOS 对象存储 + Seedance 视频生成 API 集成，替换原有的 mock 进度模拟。

**新增文件**
- `backend/utils/tos_client.py` — 火山引擎 TOS (S3 兼容) 文件上传工具，使用 boto3
- `backend/utils/seedance_client.py` — Seedance/ARK API 客户端（创建任务、查询任务、提取视频URL、状态映射）
- `backend/apps/generation/migrations/0003_generationrecord_ark_task_id_and_more.py` — 新增字段迁移

**Backend Changes**
- `requirements.txt`: 新增 `boto3>=1.34`, `requests>=2.31`
- `config/settings.py`: 新增 TOS 配置（AK/SK/Bucket/Endpoint/CDN域名）+ ARK API 配置（API Key/Base URL）
- `apps/generation/models.py`: GenerationRecord 新增 4 个字段:
  - `ark_task_id` CharField — 火山 ARK 任务 ID（如 `cgt-2026xxxx`）
  - `result_url` CharField(1000) — 生成的视频结果 URL
  - `error_message` TextField — 错误信息
  - `reference_urls` JSONField — 参考素材信息（URL、类型、角色、标签）
- `apps/generation/serializers.py`: VideoGenerateSerializer 新增 `references` ListField
- `apps/generation/views.py`: 完全重写，新增 3 个视图:
  - `upload_media_view` POST /media/upload — 上传文件到 TOS，返回公网 URL
  - `video_tasks_list_view` GET /video/tasks — 用户最近任务列表（页面刷新持久化）
  - `video_task_detail_view` GET /video/tasks/<uuid> — 单个任务状态（如仍在处理中则轮询 Seedance API）
  - `video_generate_view` 改写 — 接收 JSON（含 TOS URLs）→ 创建 Seedance 任务 → 存储 ark_task_id
- `apps/generation/urls.py`: 新增 3 条路由

**Frontend Changes**
- `src/types/index.ts`:
  - `UploadedFile` 新增 `tosUrl` 字段
  - `ReferenceSnapshot` 新增 `role` 字段
  - `GenerationTask` 新增 `taskId`（后端 UUID）、`errorMessage` 字段
  - 新增 `BackendTask` 接口（后端返回的任务数据结构）
- `src/lib/api.ts`:
  - 新增 `mediaApi.upload()` — 上传文件到后端 → TOS
  - `videoApi.generate()` 改为发送 JSON（不再是 FormData）
  - 新增 `videoApi.getTasks()` — 获取用户任务列表
  - 新增 `videoApi.getTaskStatus()` — 获取单个任务状态
- `src/store/generation.ts`: **完全重写** — 核心变更:
  - 删除 `simulateProgress()` mock 进度模拟
  - `addTask()` 改为 async: 上传文件 → TOS → 调用生成 API → 轮询状态
  - 新增 `loadTasks()`: 从后端加载任务列表（页面刷新后恢复）
  - 新增 `startPolling()` / `stopPolling()`: 每 5 秒轮询 Seedance 任务状态
  - `regenerate()`: 复用已有 TOS URL 避免重复上传
  - 使用 `Map<string, Timer>` 管理轮询计时器
- `src/components/VideoGenerationPage.tsx`: 挂载时调用 `loadTasks()` 恢复历史任务
- `src/components/GenerationCard.tsx`: 结果区域从 `<img>` 改为 `<video controls>`；新增失败状态显示

### 完整数据流
```
用户上传文件 → POST /media/upload → TOS 存储桶 → 返回公网 URL
用户生成视频 → POST /video/generate (JSON + URLs) → Seedance API 创建任务
前端轮询 → GET /video/tasks/{task_id} → 后端查询 Seedance API → 更新 DB → 返回状态
任务完成 → result_url 存入 DB → 前端显示 <video> 播放器
刷新页面 → GET /video/tasks → 从 DB 加载所有记录 → 恢复展示
```

### TOS 配置
- Bucket: `video-huoshan`
- Region: `cn-guangzhou`
- 外网 Endpoint: `tos-cn-guangzhou.volces.com`
- S3 兼容 Endpoint: `tos-s3-cn-guangzhou.volces.com`
- CDN 域名: `video-huoshan.tos-cn-guangzhou.volces.com`

### Seedance Model ID 映射
- `seedance_2.0` → `doubao-seedance-2-0-260128`
- `seedance_2.0_fast` → `doubao-seedance-2-0-fast-260128`

### 关键技术决策
- TOS 使用 S3 兼容协议（boto3）而非火山原生 SDK — 更通用，依赖更少
- 文件上传和视频生成分两步：先上传到 TOS 拿 URL，再用 URL 调 Seedance API
- 前端轮询而非后端轮询（无需 Celery 等任务队列）
- Seedance 结果 URL 24 小时有效（暂未实现下载到 TOS 持久化）
- 如果 ARK_API_KEY 未设置，生成接口仍创建 DB 记录但跳过 Seedance API 调用

### 需要用户操作
1. 设置 `ARK_API_KEY` 环境变量（Seedance API 密钥）
2. TOS Bucket 设置公共读（Seedance API 需要访问上传的文件）
3. 配置 CORS 允许前端域名

### Build verification
- TypeScript: `tsc --noEmit` 0 errors
- Backend: `python -c "import django; ..."` 全部导入成功
- Migration: 已创建并应用 (0003)

---

## 2026-03-13 手动开发：异常上报 + 轮询优化

### What was done

**1. LogCenterMiddleware — 新增 Django 中间件（`backend/utils/log_center.py`）**
- 问题：线上 `/api/v1/video/tasks` 返回 500，但日志中台未收到任何错误上报
- 原因：DRF 的 `custom_exception_handler` 只能捕获视图内的异常。模块导入失败、中间件报错、URL 解析错误等发生在 DRF 之前，完全绕过 DRF 异常处理
- 解决：新增 `LogCenterMiddleware`，放在 `settings.py` MIDDLEWARE 列表第一位（最外层）
- 异常捕获三层分工（互不重复）：
  - DRF 视图异常 → `custom_exception_handler` 上报（DRF 内部 try/except 拦截，不传播到中间件）
  - 非 DRF 视图异常 → `LogCenterMiddleware.process_exception` 上报
  - 中间件/导入异常 → `LogCenterMiddleware.__call__` try/except 上报

**2. custom_exception_handler 优化（`backend/utils/log_center.py`）**
- 问题：DRF 默认 `exception_handler` 对非 APIException（如数据库错误）返回 `None`，Django 接管后返回 HTML 500 页面，API 客户端无法解析
- 解决：当 `exception_handler` 返回 `None` 时，返回 JSON 格式 `{'error': 'ExceptionType: message'}`，HTTP 500

**3. 前端轮询间隔调整（`web/src/store/generation.ts`）**
- 从 5 秒（`5000`ms）改为 3 分钟（`3 * 60 * 1000`ms）
- 原因：Seedance 视频生成耗时较长（通常 2-5 分钟），5 秒轮询造成大量无效请求

### 文件变更清单
| 文件 | 变更 |
|------|------|
| `backend/utils/log_center.py` | 新增 `LogCenterMiddleware` 类；`custom_exception_handler` 增加 JSON 500 兜底 |
| `backend/config/settings.py` | MIDDLEWARE 列表首位加 `utils.log_center.LogCenterMiddleware` |
| `web/src/store/generation.ts:84` | `setInterval` 从 `5000` 改为 `3 * 60 * 1000` |

### 关键技术决策
- 中间件放在 MIDDLEWARE 第一位：确保能捕获所有内层中间件的异常
- 三层异常捕获无重复上报：DRF 视图异常由 DRF 的 dispatch() 内部 try/except 拦截，不会传播到中间件层
- JSON 500 响应包含具体错误类型和消息：便于前端/运维定位问题
