Replace scattered buttons with single-step focused wizard:
Step 1 (原始内容): Large textarea + "保存并继续 →"
Step 2 (AI 改写): "开始改写" button → loading → result editor + "保存并继续 →"
Step 3 (提取): "开始提取" button → loading → character table + scene chips + "继续 →"
Step 4 (音色): "AI 自动分配" or manual dropdown per character + voice sample player + "完成,进入分镜 →"
Features:
- Step bar with click navigation (can go back to completed steps)
- Auto-detect current step on page load
- Loading state with centered spinner
- Re-do buttons on each step (重新改写, 重新提取, 重新分配)
- Voice dropdown with 6 OpenAI voices (alloy/echo/fable/onyx/nova/shimmer)
- Inline audio player for voice samples
- updateCharVoice saves immediately via API
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace sparse table with dense card list:
- Each shot is a compact card: #num | tag | desc (2 lines) | dialogue
- Left border highlight on selected card (orange accent)
- Auto-select first shot on load
- Auto-select next shot after delete
Editor panel (380px):
- Shot type dropdown + duration in one row
- Location, description, video prompt, dialogue fields
- blur auto-save
- Delete with auto-select next
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ref<any>(null) → ref(null) — Nuxt treats <any> as HTML tag in
plain <script setup> blocks without lang="ts".
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Full visual overhaul of all pages:
Design system (studio.css):
- Light/dark auto-switch with refined color palette
- Card component with shadow + border + hover states
- Pill-shaped tags with semantic colors
- Shadow-elevated buttons with proper states
- 13px base font, comfortable line height
- Rounded corners (6px/10px), consistent spacing
Layout (default.vue):
- Top navigation bar (48px) instead of narrow sidebar
- Brand + nav links in horizontal bar
- Clean content area below
Pages redesigned:
- index.vue: Card grid for projects, proper modal dialog
- [id]/index.vue: Episode list with badges and chevron arrows
- [episodeNumber].vue: 4-panel workbench with pipeline steps,
split editor, resource cards, production grid, export player
- settings.vue: Centered form layout with toggle switches
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[id].vue as a leaf file conflicts with [id]/episode/[episodeNumber].vue.
Nuxt needs [id] to be a directory with index.vue for nested routes to work.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
nitro.devProxy doesn't work in SPA mode. Use Vite's built-in
proxy which works reliably for /api and /static forwarding.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Apply toSnakeCase/toSnakeCaseArray to all route responses:
- dramas.ts: list items, detail, create response
- episodes.ts: storyboard listing
- storyboards.ts: create response
Frontend reads snake_case fields (shot_type, video_prompt, episode_id, etc.)
but Drizzle returns camelCase. Now all responses are consistent.
Also fix bare import paths to use .js extension.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Light mode default, dark mode via @media (prefers-color-scheme: dark).
All colors use CSS variables, no hardcoded values in components.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Full rewrite with Nuxt 3 + professional dark theme (DaVinci Resolve style):
Tech: Nuxt 3, Vue 3, vue-sonner, lucide-vue-next, pure CSS
Design:
- Deep black background (#0d0d0d)
- Compact 36px header, 52px icon sidebar
- 11px base font, monospace for data
- Minimal chrome, high information density
- Status tags: success/accent/info
Pages:
- / — Project list with inline create dialog
- /drama/[id] — Episode grid
- /drama/[id]/episode/[episodeNumber] — 4-panel workbench (studio layout)
- /settings — AI service config
Workbench 4 panels:
[剧本] Split editor + character voice cards + scene chips
[分镜] Data table with inline editing
[制作] Shot list with image/video/compose status + batch ops
[导出] Video player + download + merge trigger
Composables:
- useApi.ts — All 10 API modules in one file
- useAgent.ts — SSE agent execution with toast progress
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Critical fixes:
1. /dramas/stats route moved before /:id (was permanently shadowed)
2. Removed dead characters query in ffmpeg-compose (wrong FK column)
Security:
3. PUT /dramas/:id — whitelist allowed fields instead of spreading body
Correctness:
4. Middleware order: requestLogger before errorHandler (errors now logged)
5. getActiveConfig: sort by priority descending (deterministic provider selection)
tsc --noEmit: 0 errors
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- agents/index.ts: cast OpenAI provider config as any for compatibility
- routes/agent.ts: access chunk.payload for Mastra stream fields,
fallback to top-level for backwards compat
- routes/images.ts, videos.ts: add .all() to db.select() calls
- routes/characters.ts: remove non-existent imageGenerationStatus
- services/image-generation.ts: rewrite with proper .all() and .run()
- services/video-generation.ts: rewrite with proper .all() and .run()
npx tsc --noEmit: 0 errors
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
chunk.result can be undefined, causing .slice() to fail.
Add null check before stringifying.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Before: tools required LLM to extract episode_id from message text
and pass it as a parameter — unreliable, LLM often got it wrong.
After: agent and tools created per-request with episodeId/dramaId
bound in closures. Tools have empty inputSchema for IDs — they
just use the injected values directly.
Changes:
- All tool files → factory functions (createScriptTools, etc.)
- agents/index.ts → createAgent() factory replaces static agents
- agent route → creates fresh agent per request with IDs injected
- No more [Context: ...] text injection in messages
- Added agent execution logging
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Custom middleware replaces hono/logger:
- Colored output: time, method, path, status, duration
- POST/PUT/PATCH requests print request body (truncated to 500 chars)
- Global error handler catches uncaught exceptions with full stack trace
- Status codes color-coded: 2xx green, 4xx yellow, 5xx red
Also fix: CORS origins updated, static file path to frontend/dist
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Drizzle + better-sqlite3 .returning() causes "not iterable" error.
Replace all 6 occurrences with .run() + select by lastInsertRowid:
- aiConfigs.ts POST
- dramas.ts POST (also add metadata field, fix missing .run())
- scenes.ts POST
- storyboards.ts POST
- image-generation.ts
- video-generation.ts
Also fix .js import extensions and add .run() to all sync db calls.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
DramaList:
- Page centered with max-width 800px
- Create dialog: title, description, episode count, style, video model
- Video model dropdown populated from AI service configs (video type)
- Selected video model stored in drama.metadata.video_model
- Project rows show video model if set
- Delete button on each row with confirmation
- Empty state with icon and create button
- Status labels in Chinese
DramaDetail:
- Center layout to match
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Backend:
- Add toSnakeCase/toSnakeCaseArray transform utility
- aiConfigs route returns snake_case JSON (matches frontend expectations)
- Consistent field names: service_type, api_key, base_url, is_active
Frontend Settings:
- Unified snake_case field access (no more dual format checks)
- Add edit button with pencil icon → opens same dialog with pre-filled values
- Provider selection via dropdown (8 providers)
- Base URL auto-fills from provider default
- Model field supports comma-separated multiple models
- Show base URL in config row
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Chatfire doesn't support OpenAI Responses format.
Switch to compatibility mode + provider.chat() to force
standard /v1/chat/completions endpoint.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Change Button size from 'icon' to 'sm' for edit/delete buttons
- Add @click.stop to prevent event bubbling
- Replace Object.assign with direct property assignment on reactive
- Change add button from <button> to <div> to avoid form nesting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace tab-based accordion UI with a clean, flat layout:
- Four sections: 文本/图片/视频/音频, all visible at once
- Each section shows configured services as compact rows:
provider dot + name + models + URL + key status + toggle + edit/delete
- Add button per section with dashed border
- Edit/Create dialog: provider select, API key, base URL, models (comma-separated)
- Remove ProviderCard dependency, remove service type tabs
- Simpler data flow: no expandable rows, direct CRUD via dialog
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>