release: opensource snapshot 2026-02-27 19:25:00
This commit is contained in:
53
.dockerignore
Normal file
53
.dockerignore
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
# 版本控制
|
||||||
|
.git
|
||||||
|
.github
|
||||||
|
|
||||||
|
# 构建产物(builder 阶段重新生成)
|
||||||
|
.next
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
# 测试
|
||||||
|
coverage
|
||||||
|
tests
|
||||||
|
vitest.config.ts
|
||||||
|
docker-compose.test.yml
|
||||||
|
|
||||||
|
# 运行时数据
|
||||||
|
logs
|
||||||
|
data
|
||||||
|
certificates
|
||||||
|
backups
|
||||||
|
uploads
|
||||||
|
|
||||||
|
# IDE 和 AI 工具
|
||||||
|
.vscode
|
||||||
|
.cursor
|
||||||
|
.claude
|
||||||
|
.gemini
|
||||||
|
.agent
|
||||||
|
.shared
|
||||||
|
.artifacts
|
||||||
|
|
||||||
|
# 数据库文件
|
||||||
|
*.db
|
||||||
|
*.db-journal
|
||||||
|
prisma/data
|
||||||
|
|
||||||
|
# 临时文件
|
||||||
|
.DS_Store
|
||||||
|
*.tsbuildinfo
|
||||||
|
.tmp-old-snapshot-*
|
||||||
|
|
||||||
|
# 环境变量
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
.env.test
|
||||||
|
|
||||||
|
# 文档和杂项
|
||||||
|
*.md
|
||||||
|
*.py
|
||||||
|
debug-request.json
|
||||||
|
AGENTS.md
|
||||||
|
docs
|
||||||
|
agent
|
||||||
61
.env.example
Normal file
61
.env.example
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
# ==================== 数据库 ====================
|
||||||
|
# Docker 模式下无需修改,docker-compose.yml 会自动覆盖
|
||||||
|
DATABASE_URL="mysql://root:waoowaoo123@localhost:3306/waoowaoo"
|
||||||
|
|
||||||
|
# ==================== 存储 ====================
|
||||||
|
# local: 本地文件存储(默认,推荐)
|
||||||
|
# cos: 腾讯云COS(需要配置下方COS密钥)
|
||||||
|
STORAGE_TYPE=local
|
||||||
|
|
||||||
|
# 如果使用 COS 存储,请填写以下配置
|
||||||
|
# COS_SECRET_ID=
|
||||||
|
# COS_SECRET_KEY=
|
||||||
|
# COS_BUCKET=
|
||||||
|
# COS_REGION=
|
||||||
|
|
||||||
|
# ==================== 认证 ====================
|
||||||
|
NEXTAUTH_URL=https://localhost
|
||||||
|
NEXTAUTH_SECRET=please-change-this-to-a-random-string
|
||||||
|
|
||||||
|
# ==================== 内部密钥 ====================
|
||||||
|
CRON_SECRET=please-change-this-cron-secret
|
||||||
|
INTERNAL_TASK_TOKEN=please-change-this-task-token
|
||||||
|
API_ENCRYPTION_KEY=please-change-this-encryption-key
|
||||||
|
|
||||||
|
# ==================== Redis ====================
|
||||||
|
# Docker 模式下无需修改,docker-compose.yml 会自动覆盖
|
||||||
|
REDIS_HOST=127.0.0.1
|
||||||
|
REDIS_PORT=6379
|
||||||
|
REDIS_USERNAME=
|
||||||
|
REDIS_PASSWORD=
|
||||||
|
REDIS_TLS=
|
||||||
|
|
||||||
|
# ==================== Worker 配置 ====================
|
||||||
|
WATCHDOG_INTERVAL_MS=30000
|
||||||
|
TASK_HEARTBEAT_TIMEOUT_MS=90000
|
||||||
|
QUEUE_CONCURRENCY_IMAGE=50
|
||||||
|
QUEUE_CONCURRENCY_VIDEO=50
|
||||||
|
QUEUE_CONCURRENCY_VOICE=20
|
||||||
|
QUEUE_CONCURRENCY_TEXT=50
|
||||||
|
|
||||||
|
# ==================== Bull Board (任务管理面板) ====================
|
||||||
|
BULL_BOARD_HOST=0.0.0.0
|
||||||
|
BULL_BOARD_PORT=3010
|
||||||
|
BULL_BOARD_BASE_PATH=/admin/queues
|
||||||
|
BULL_BOARD_USER=
|
||||||
|
BULL_BOARD_PASSWORD=
|
||||||
|
|
||||||
|
# ==================== 日志 ====================
|
||||||
|
LOG_UNIFIED_ENABLED=true
|
||||||
|
LOG_LEVEL=ERROR
|
||||||
|
LOG_FORMAT=json
|
||||||
|
LOG_DEBUG_ENABLED=false
|
||||||
|
LOG_AUDIT_ENABLED=true
|
||||||
|
LOG_SERVICE=waoowaoo
|
||||||
|
LOG_REDACT_KEYS=password,token,apiKey,apikey,authorization,cookie,secret,access_token,refresh_token
|
||||||
|
|
||||||
|
# ==================== 计费 ====================
|
||||||
|
BILLING_MODE=ENFORCE
|
||||||
|
|
||||||
|
# ==================== 流式输出 ====================
|
||||||
|
LLM_STREAM_EPHEMERAL_ENABLED=true
|
||||||
10
.env.test
Normal file
10
.env.test
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
NODE_ENV=test
|
||||||
|
BILLING_MODE=OFF
|
||||||
|
|
||||||
|
# MySQL test database
|
||||||
|
DATABASE_URL=mysql://root:root@127.0.0.1:3307/waoowaoo_test
|
||||||
|
|
||||||
|
# Redis test instance
|
||||||
|
REDIS_HOST=127.0.0.1
|
||||||
|
REDIS_PORT=6380
|
||||||
|
INTERNAL_TASK_TOKEN=waoowaoo-internal-task-2026
|
||||||
10
.eslintrc.json
Normal file
10
.eslintrc.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"extends": "next/core-web-vitals",
|
||||||
|
"rules": {
|
||||||
|
"@typescript-eslint/no-explicit-any": "off",
|
||||||
|
"@typescript-eslint/no-unused-vars": "warn",
|
||||||
|
"@next/next/no-img-element": "warn",
|
||||||
|
"react-hooks/exhaustive-deps": "warn"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
34
.github/pull_request_template.md
vendored
Normal file
34
.github/pull_request_template.md
vendored
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
## Summary
|
||||||
|
|
||||||
|
- What changed:
|
||||||
|
- Why:
|
||||||
|
|
||||||
|
## Architecture Plan Sync (Required)
|
||||||
|
|
||||||
|
- Master plan file: `/Users/earth/Desktop/waoowaoo/docs/architecture-unification-master-plan.md`
|
||||||
|
- [ ] I updated the phase status board (`✅/🔄/⏸/⚠️`) in the same PR.
|
||||||
|
- [ ] I recorded concrete file-level changes under the corresponding phase.
|
||||||
|
- [ ] I kept the plan date/current-context consistent with the code changes in this PR.
|
||||||
|
|
||||||
|
## Phase Status Delta (Required)
|
||||||
|
|
||||||
|
| Phase | Before | After | Evidence (file path) |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| e.g. Phase 1.2 | 🔄 | ✅ | `src/lib/query/task-target-overlay.ts` |
|
||||||
|
|
||||||
|
## Guardrails Checklist (Required)
|
||||||
|
|
||||||
|
- [ ] No compatibility layer introduced.
|
||||||
|
- [ ] No silent fallback introduced.
|
||||||
|
- [ ] No hidden default or fake data introduced.
|
||||||
|
- [ ] Errors remain explicit and traceable.
|
||||||
|
|
||||||
|
## Validation
|
||||||
|
|
||||||
|
- Commands run:
|
||||||
|
- Manual verification:
|
||||||
|
|
||||||
|
## Risks
|
||||||
|
|
||||||
|
- Known impact/risk:
|
||||||
|
- Follow-up tasks:
|
||||||
74
.gitignore
vendored
Normal file
74
.gitignore
vendored
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.*
|
||||||
|
.yarn/*
|
||||||
|
!.yarn/patches
|
||||||
|
!.yarn/plugins
|
||||||
|
!.yarn/releases
|
||||||
|
!.yarn/versions
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# next.js
|
||||||
|
/.next/
|
||||||
|
/out/
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
*.pem
|
||||||
|
|
||||||
|
# debug
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# vercel
|
||||||
|
.vercel
|
||||||
|
|
||||||
|
# IDE and AI tools
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
.claude/
|
||||||
|
.cursor/
|
||||||
|
.gemini/
|
||||||
|
.artifacts/
|
||||||
|
.agent/
|
||||||
|
.shared/
|
||||||
|
|
||||||
|
# typescript
|
||||||
|
*.tsbuildinfo
|
||||||
|
next-env.d.ts
|
||||||
|
|
||||||
|
/src/generated/prisma
|
||||||
|
|
||||||
|
# logs
|
||||||
|
/logs
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# environment variables
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
docker-logs/
|
||||||
|
|
||||||
|
# database
|
||||||
|
*.db
|
||||||
|
*.db-journal
|
||||||
|
prisma/data/
|
||||||
|
|
||||||
|
# uploads (user data)
|
||||||
|
uploads/
|
||||||
|
/data/
|
||||||
|
|
||||||
|
certificates
|
||||||
|
|
||||||
|
# local temporary snapshots for old-version verification
|
||||||
|
.tmp-old-snapshot-*/
|
||||||
1
.vscode/settings.json
vendored
Normal file
1
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
57
AGENTS.md
Normal file
57
AGENTS.md
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
# AGENTS.md
|
||||||
|
|
||||||
|
## 适用范围
|
||||||
|
- 本规范适用于本仓库的所有目录与文件。
|
||||||
|
- 若下级目录存在新的 `AGENTS.md`,下级规范仅可补充,不可弱化本文件的强约束。
|
||||||
|
|
||||||
|
## 项目目标与编码原则
|
||||||
|
- 项目定位为全新系统,`统一` 与 `简洁` 是最高优先级。
|
||||||
|
- 禁止以“兼容旧代码/旧行为”为理由引入冗余分支、兼容层、双轨逻辑或临时补丁。
|
||||||
|
- 新功能与重构应优先服务于一致性、可维护性、可读性,而非历史包袱。
|
||||||
|
- 禁止使用任何any类型,必须明确类型
|
||||||
|
## 文件与模块化要求
|
||||||
|
- 大文件必须拆分为清晰模块,按职责边界组织。
|
||||||
|
- 单个文件若同时承担多类职责(如 UI、状态、数据请求、转换逻辑混杂)必须拆分。
|
||||||
|
- 公共能力应抽离为可复用模块,避免复制粘贴。
|
||||||
|
- 命名必须体现职责,目录结构应支持快速定位与阅读。
|
||||||
|
|
||||||
|
## 数据安全与高风险操作
|
||||||
|
- 任何可能导致数据 `删除`、`丢失`、`覆盖`、`结构变更`、`不可逆修改` 的操作,执行前必须获得用户明确同意。
|
||||||
|
- 未获得明确同意时,仅允许进行只读分析、方案设计与风险说明,不得落地执行。
|
||||||
|
- 涉及数据库、文件批量改写、迁移脚本、清理脚本、覆盖写入等场景,一律按高风险处理。
|
||||||
|
- 但可以运行测试、构建等无害的操作。
|
||||||
|
- 可以执行测试,构建等没有毁灭性的命令
|
||||||
|
|
||||||
|
|
||||||
|
## 思维与决策方法
|
||||||
|
- 所有方案必须采用第一性原理:先明确目标、约束与事实,再推导实现路径。
|
||||||
|
- 禁止基于“惯例如此”或“历史如此”直接做决策;必须说明核心假设与取舍依据。
|
||||||
|
- 实现应追求最小必要复杂度,避免无效抽象与过度设计。
|
||||||
|
|
||||||
|
## 命令与 Git 操作限制
|
||||||
|
- 禁止执行除 `Git 只读查询` 以外的任何命令。
|
||||||
|
- 允许的 Git 只读操作仅包括状态与历史查询,例如:`git status`、`git log`、`git diff`、`git show`、`git branch`(只读用法)。
|
||||||
|
- 任何会改变 Git 状态或历史的操作必须先获得用户明确同意,包括但不限于:`commit`、`push`、`pull`、`merge`、`rebase`、`cherry-pick`、`reset`、`checkout`(修改性用法)、创建/删除分支、打标签。
|
||||||
|
- 在未获同意前,不得进行代码改写、暂存、提交、同步、回滚或历史重写。
|
||||||
|
- 可以执行测试构建 build lint等测试命令
|
||||||
|
|
||||||
|
## 不掩盖任何问题
|
||||||
|
- 不要做任何不必要的回退逻辑,特别是有可能隐藏问题的,除非用户允许,否则禁止做,如发现一个模型不可用时自动跳转到新的模型,或代码失效时,报错时直接略过,或者没有的时候提供默认值等错误操作,或制造假数据等。
|
||||||
|
-系统执行必须遵循显式失败与零隐式回退原则:严禁静默跳过错误、隐式配置兜底或自动模型降级,确保所有非预期行为原地崩溃并如实上报。
|
||||||
|
|
||||||
|
|
||||||
|
## 敢于合理质疑用户 了解用户真实需求
|
||||||
|
- 提问以了解我真正需要什么(不仅仅是我说什么)。
|
||||||
|
- 用户可能不够了解代码 对技术的理解可能不如你
|
||||||
|
- 用户和你说的作为参考 而不是绝对值 如果某些事情说不通,请挑战我的假设。
|
||||||
|
|
||||||
|
## 测试规范
|
||||||
|
|
||||||
|
详细规范见 [`agent/testing.md`](agent/testing.md),以下为强制核心约束:
|
||||||
|
|
||||||
|
- 新增功能或修改功能逻辑必须进行测试,新增功能必须增加测试,如修改文件导致需修改测试文件务必一起进行修改,确保测试百分百跟随
|
||||||
|
- 改 worker 逻辑 / 修 bug / 加 route 或 task type → 必须写或更新测试
|
||||||
|
- 修 bug 必须同步新增回归测试,`it()` 名称体现该 bug 场景
|
||||||
|
- 断言必须检查具体值(DB 写入字段值、函数入参、返回值),禁止只用 `toHaveBeenCalled()`
|
||||||
|
- 禁止"自给自答":mock 返回 X 再断言 X,没有经过任何业务逻辑
|
||||||
|
- 未通过 `npm run test:regression` 不得宣称功能完成
|
||||||
58
Dockerfile
Normal file
58
Dockerfile
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
# ==================== Stage 1: Dependencies ====================
|
||||||
|
FROM node:20-alpine AS deps
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY package.json package-lock.json ./
|
||||||
|
RUN npm ci
|
||||||
|
|
||||||
|
# ==================== Stage 2: Build ====================
|
||||||
|
FROM node:20-alpine AS builder
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY --from=deps /app/node_modules ./node_modules
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Prisma generate + Next.js build
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# ==================== Stage 3: Production ====================
|
||||||
|
FROM node:20-alpine AS runner
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
|
||||||
|
RUN apk add --no-cache tini
|
||||||
|
|
||||||
|
# node_modules(含 devDeps,因为 npm run start 需要 concurrently + tsx)
|
||||||
|
COPY --from=builder /app/node_modules ./node_modules
|
||||||
|
COPY --from=builder /app/package.json ./package.json
|
||||||
|
|
||||||
|
# Next.js 构建产物
|
||||||
|
COPY --from=builder /app/.next ./.next
|
||||||
|
COPY --from=builder /app/public ./public
|
||||||
|
|
||||||
|
# Prisma schema(db push 需要)
|
||||||
|
COPY --from=builder /app/prisma ./prisma
|
||||||
|
|
||||||
|
# Worker 和 Watchdog 源码(tsx 运行 TypeScript)
|
||||||
|
COPY --from=builder /app/src ./src
|
||||||
|
COPY --from=builder /app/scripts ./scripts
|
||||||
|
COPY --from=builder /app/lib ./lib
|
||||||
|
|
||||||
|
# 定价和配置标准
|
||||||
|
COPY --from=builder /app/standards ./standards
|
||||||
|
|
||||||
|
# 国际化 + 配置文件
|
||||||
|
COPY --from=builder /app/messages ./messages
|
||||||
|
COPY --from=builder /app/tsconfig.json ./tsconfig.json
|
||||||
|
COPY --from=builder /app/next.config.ts ./next.config.ts
|
||||||
|
COPY --from=builder /app/middleware.ts ./middleware.ts
|
||||||
|
COPY --from=builder /app/postcss.config.mjs ./postcss.config.mjs
|
||||||
|
|
||||||
|
# 本地存储数据目录 + 空 .env(tsx --env-file=.env 需要文件存在,实际 env 由 docker-compose 注入)
|
||||||
|
RUN mkdir -p /app/data/uploads /app/logs && touch /app/.env
|
||||||
|
|
||||||
|
EXPOSE 3000 3010
|
||||||
|
|
||||||
|
ENTRYPOINT ["/sbin/tini", "--"]
|
||||||
|
CMD ["npm", "run", "start"]
|
||||||
119
README.md
Normal file
119
README.md
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
<p align="center">
|
||||||
|
<img src="public/banner.png" alt="waoowaoo" width="600">
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="#-quick-start">English</a> | <a href="#-快速开始">中文</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
# waoowaoo AI 影视 Studio
|
||||||
|
|
||||||
|
> ⚠️ **测试版声明**:本项目目前处于测试初期阶段,由于暂时只有我一个人开发,存在部分 bug 和不完善之处。我们正在快速迭代更新中,欢迎进群反馈问题和需求!
|
||||||
|
>
|
||||||
|
> ⚠️ **Beta Notice**: This project is in early beta. It's currently solo-developed, so bugs and rough edges exist. We're iterating fast — feel free to open an Issue!
|
||||||
|
|
||||||
|
一款基于 AI 技术的短剧/漫画视频制作工具,支持从小说文本自动生成分镜、角色、场景,并制作成完整视频。
|
||||||
|
|
||||||
|
An AI-powered tool for creating short drama / comic videos — automatically generates storyboards, characters, and scenes from novel text, then assembles them into complete videos.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✨ 功能特性 / Features
|
||||||
|
|
||||||
|
| | 中文 | English |
|
||||||
|
|---|---|---|
|
||||||
|
| 🎬 | AI 剧本分析 - 自动解析小说,提取角色、场景、剧情 | AI Script Analysis - parse novels, extract characters, scenes & plot |
|
||||||
|
| 🎨 | 角色 & 场景生成 - AI 生成一致性人物和场景图片 | Character & Scene Generation - consistent AI-generated images |
|
||||||
|
| 📽️ | 分镜视频制作 - 自动生成分镜头并合成视频 | Storyboard Video - auto-generate shots and compose videos |
|
||||||
|
| 🎙️ | AI 配音 - 多角色语音合成 | AI Voiceover - multi-character voice synthesis |
|
||||||
|
| 🌐 | 多语言支持 - 中文 / 英文界面,右上角一键切换 | Bilingual UI - Chinese / English, switch in the top-right corner |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 快速开始
|
||||||
|
|
||||||
|
**前提条件**:安装 [Docker Desktop](https://docs.docker.com/get-docker/)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/saturndec/waoowaoo.git
|
||||||
|
cd waoowaoo
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
访问 [http://localhost:13000](http://localhost:13000) 开始使用!
|
||||||
|
|
||||||
|
> 首次启动会自动完成数据库初始化,无需任何额外配置。
|
||||||
|
|
||||||
|
> ⚠️ **如果遇到网页卡顿**:HTTP 模式下浏览器可能限制并发连接。可安装 [Caddy](https://caddyserver.com/docs/install) 启用 HTTPS:
|
||||||
|
> ```bash
|
||||||
|
> caddy run --config Caddyfile
|
||||||
|
> ```
|
||||||
|
> 然后访问 [https://localhost:1443](https://localhost:1443)
|
||||||
|
|
||||||
|
### 🔄 更新到最新版本
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git fetch origin && git reset --hard origin/main
|
||||||
|
docker compose down && docker compose up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Quick Start
|
||||||
|
|
||||||
|
**Prerequisites**: Install [Docker Desktop](https://docs.docker.com/get-docker/)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/saturndec/waoowaoo.git
|
||||||
|
cd waoowaoo
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
Visit [http://localhost:13000](http://localhost:13000) to get started!
|
||||||
|
|
||||||
|
> The database is initialized automatically on first launch — no extra configuration needed.
|
||||||
|
|
||||||
|
> ⚠️ **If you experience lag**: HTTP mode may limit browser connections. Install [Caddy](https://caddyserver.com/docs/install) for HTTPS:
|
||||||
|
> ```bash
|
||||||
|
> caddy run --config Caddyfile
|
||||||
|
> ```
|
||||||
|
> Then visit [https://localhost:1443](https://localhost:1443)
|
||||||
|
|
||||||
|
### 🔄 Updating to the Latest Version
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git fetch origin && git reset --hard origin/main
|
||||||
|
docker compose down && docker compose up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 API 配置 / API Configuration
|
||||||
|
|
||||||
|
启动后进入**设置中心**配置 AI 服务的 API Key,内置配置教程。
|
||||||
|
|
||||||
|
After launching, go to **Settings** to configure your AI service API keys. A built-in guide is provided.
|
||||||
|
|
||||||
|
> 💡 **推荐 / Recommended**: Tested with ByteDance Volcano Engine (Seedance, Seedream) and Google AI Studio (Banana). Text models currently require OpenRouter API.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📦 技术栈 / Tech Stack
|
||||||
|
|
||||||
|
- **Framework**: Next.js 15 + React 19
|
||||||
|
- **Database**: MySQL + Prisma ORM
|
||||||
|
- **Queue**: Redis + BullMQ
|
||||||
|
- **Styling**: Tailwind CSS v4
|
||||||
|
- **Auth**: NextAuth.js
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🤝 反馈 / Feedback
|
||||||
|
|
||||||
|
暂不接受 Pull Request,如有问题或建议,欢迎提交 [Issue](https://github.com/saturndec/waoowaoo/issues)!
|
||||||
|
|
||||||
|
Pull Requests are not accepted at this time. For bugs or suggestions, please open an [Issue](https://github.com/saturndec/waoowaoo/issues).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Made with ❤️ by waoowaoo team**
|
||||||
623
SYSTEM_BEHAVIOR_LEVEL_TEST_MASTER_PLAN.md
Normal file
623
SYSTEM_BEHAVIOR_LEVEL_TEST_MASTER_PLAN.md
Normal file
@@ -0,0 +1,623 @@
|
|||||||
|
你必须按照目前的md文件详细执行我们的代码修改计划,且必须时刻关注,维护本次md文档,确保该文档能始终保持最新,和我们代码库保持完全一致,除非用户要求,否则默认禁止打补丁,禁止兼容层,我们需要的是简洁干净可扩展的系统,我们这个系统目前没有人用,可以一次性全量,彻底,不留遗留的修改,并且需要一次性完成所有,禁止停下,禁止自己停止任务,一次性完成所有内容。
|
||||||
|
|
||||||
|
# 全系统真实行为级测试替换执行主计划
|
||||||
|
版本: v1.0
|
||||||
|
仓库: /Users/earth/Desktop/waoowaoo
|
||||||
|
最后更新: 2026-02-25
|
||||||
|
定位: 用真实“行为结果断言”替换结构级/字符串级测试,覆盖全系统功能回归链路
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1: 项目目标
|
||||||
|
|
||||||
|
### 1.1 为什么要做
|
||||||
|
当前系统历史回归集中在“链路行为错了但结构没变”的问题:
|
||||||
|
- 编辑角色/场景后字段未正确回写。
|
||||||
|
- 上传参考图后没有按参考图生成。
|
||||||
|
- 三视图后缀、locale、meta、referenceImages 在 route -> task -> worker 过程中丢失。
|
||||||
|
- 前端状态看起来正常,但真实任务状态或写库结果错误。
|
||||||
|
|
||||||
|
现有部分测试仍是结构级(例如检查源码里是否包含 `apiHandler`、`submitTask`、`maybeSubmitLLMTask`,或者仅检查 `TASK_TYPE -> queue` 映射),这类测试无法拦截真实业务回归。
|
||||||
|
|
||||||
|
### 1.2 需要达到的目标
|
||||||
|
把测试体系升级为“行为级为主、结构级为辅”:
|
||||||
|
- 每个关键功能都必须有“输入 -> 执行 -> 输出/副作用”的断言。
|
||||||
|
- 断言必须检查具体值(写入字段值、payload 值、response 值),不接受只断言“被调用了”。
|
||||||
|
- route、task type、worker handler 三层都要有行为级覆盖矩阵。
|
||||||
|
- 外部 API 全 fake,不走真实高成本调用。
|
||||||
|
|
||||||
|
### 1.3 本次扫描结论(基于当前仓库)
|
||||||
|
- API 路由文件覆盖面: `src/app/api/**/route.ts`(全量 catalog 已维护)。
|
||||||
|
- Worker 文件覆盖面: `src/lib/workers/handlers/*.ts` + `src/lib/workers/*.worker.ts`。
|
||||||
|
- `tests/**/*.test.ts` 实际数量: `71`。
|
||||||
|
- `src/lib/workers/handlers/*.ts` 文件数量: `43`(含 helper/shared/re-export 文件)。
|
||||||
|
- `handlers` 目录中 `export async function handle...` 入口函数数量: `26`(这是 worker 行为测试的主覆盖对象)。
|
||||||
|
- 计数口径说明:
|
||||||
|
- helper/shared/prompt-utils 文件不计入“handler 入口数”。
|
||||||
|
- 仅 re-export 的别名文件(如 `modify-asset-image-task-handler.ts`、`image-task-handlers.ts`)不单独计入口径。
|
||||||
|
- 已有结构级测试(需替换/下沉,已替换项会在阶段状态中标记):
|
||||||
|
- `tests/integration/api/contract/direct-submit-routes.test.ts`
|
||||||
|
- `tests/integration/api/contract/llm-observe-routes.test.ts`
|
||||||
|
- `tests/integration/api/contract/crud-routes.test.ts`
|
||||||
|
- `tests/integration/api/contract/task-infra-routes.test.ts`
|
||||||
|
- `tests/integration/chain/{text,image,video,voice}.chain.test.ts`
|
||||||
|
- `tests/unit/worker/video-worker.test.ts`(已替换为行为断言)
|
||||||
|
- `tests/unit/worker/voice-worker.test.ts`(已替换为行为断言)
|
||||||
|
- `tests/unit/optimistic/sse-invalidation.test.ts`(已替换为行为断言)
|
||||||
|
- `tests/unit/optimistic/task-target-state-map.test.ts`(已替换为行为断言)
|
||||||
|
- 已落地的行为级样板(保留并扩展):
|
||||||
|
- `tests/unit/worker/reference-to-character.test.ts`
|
||||||
|
- `tests/unit/worker/asset-hub-image-suffix.test.ts`
|
||||||
|
- `tests/unit/worker/modify-image-reference-description.test.ts`
|
||||||
|
- `tests/integration/api/specific/characters-post-reference-forwarding.test.ts`
|
||||||
|
- `tests/contracts/requirements-matrix.test.ts`
|
||||||
|
|
||||||
|
### 1.4 修改前后的预计区别
|
||||||
|
修改前:
|
||||||
|
- 大量“永远绿灯”风险:结构级测试通过但真实业务错误。
|
||||||
|
- 关键回归(参考图链路、提示词后缀、写回字段)无法稳定拦截。
|
||||||
|
|
||||||
|
修改后:
|
||||||
|
- 结构级测试只做守卫,不作为回归主防线。
|
||||||
|
- 行为级测试覆盖 route 入参、task payload、worker 分支、DB 写回、返回值契约。
|
||||||
|
- 新增或修改功能时,必须补行为级用例,否则 guard 失败。
|
||||||
|
|
||||||
|
### 1.5 规模预估
|
||||||
|
- 预计新增/重写测试文件: 45-70 个
|
||||||
|
- 预计修改文件: 25-40 个
|
||||||
|
- 预计新增代码: 9,000-16,000 行(以测试与守卫脚本为主)
|
||||||
|
- 预计执行阶段: 8 个阶段
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2: 阶段+具体代码修改地方以及需要修改的内容
|
||||||
|
|
||||||
|
### 状态图例
|
||||||
|
✅ 已完成
|
||||||
|
🔄 正在执行
|
||||||
|
⏸ 待执行
|
||||||
|
⚠️ 问题
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 阶段1: 基线与约束固化
|
||||||
|
|
||||||
|
✅ Phase 1.1: 盘点路由、task type、worker 入口并建立 catalog。
|
||||||
|
修改位置:
|
||||||
|
- `/Users/earth/Desktop/waoowaoo/tests/contracts/route-catalog.ts`
|
||||||
|
- `/Users/earth/Desktop/waoowaoo/tests/contracts/task-type-catalog.ts`
|
||||||
|
|
||||||
|
✅ Phase 1.2: requirements matrix 存在性校验落地,阻断“文档写了但文件不存在”。
|
||||||
|
修改位置:
|
||||||
|
- `/Users/earth/Desktop/waoowaoo/tests/contracts/requirements-matrix.ts`
|
||||||
|
- `/Users/earth/Desktop/waoowaoo/tests/contracts/requirements-matrix.test.ts`
|
||||||
|
|
||||||
|
✅ Phase 1.3: 定义“行为级测试判定标准”并加入守卫。
|
||||||
|
要改内容:
|
||||||
|
- 新增 `/Users/earth/Desktop/waoowaoo/tests/contracts/behavior-test-standard.md`
|
||||||
|
- 新增 `/Users/earth/Desktop/waoowaoo/scripts/guards/test-behavior-quality-guard.mjs`
|
||||||
|
硬性规则:
|
||||||
|
- 禁止只断言 `toHaveBeenCalled()`
|
||||||
|
- 必须断言具体 payload/data 字段值或返回值
|
||||||
|
- 禁止在 contract/chain 目录内读取源码文本做契约主断言
|
||||||
|
|
||||||
|
✅ Phase 1.3.a: 后端 Worker 单元测试硬规范已写入本主计划(本文件第 3 章)。
|
||||||
|
当前状态:
|
||||||
|
- 规范文本已固化
|
||||||
|
- 自动化守卫脚本已落地(Phase 1.3 完成)
|
||||||
|
|
||||||
|
⚠️ Phase 1.4: 历史结构级测试较多,改造期间可能出现“同名文件语义变化”导致误解。
|
||||||
|
处理策略:
|
||||||
|
- 每次改造完成后,在本文件执行日志记录“此文件已由结构级改为行为级”。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 阶段2: API 契约从结构级替换为行为级
|
||||||
|
|
||||||
|
依赖关系:
|
||||||
|
- Phase 2 可先行推进(route 行为契约)。
|
||||||
|
- Phase 3 与 Phase 4 依赖 Phase 2 的 route 输入输出基线稳定。
|
||||||
|
|
||||||
|
✅ Phase 2.1: 重写 direct-submit contract 为真实调用断言。
|
||||||
|
重写文件:
|
||||||
|
- `/Users/earth/Desktop/waoowaoo/tests/integration/api/contract/direct-submit-routes.test.ts`
|
||||||
|
必须断言:
|
||||||
|
- 未登录 401
|
||||||
|
- 参数缺失 400(错误码一致)
|
||||||
|
- 正常请求返回 `{ taskId, async: true }`
|
||||||
|
- `submitTask` 入参包含 `type/targetType/targetId/payload/locale`
|
||||||
|
|
||||||
|
✅ Phase 2.2: 重写 llm-observe contract 为真实调用断言。
|
||||||
|
重写文件:
|
||||||
|
- `/Users/earth/Desktop/waoowaoo/tests/integration/api/contract/llm-observe-routes.test.ts`
|
||||||
|
必须断言:
|
||||||
|
- `maybeSubmitLLMTask` 入参正确透传
|
||||||
|
- `displayMode/flow/meta` 不丢失
|
||||||
|
- 越权请求被拒绝
|
||||||
|
|
||||||
|
✅ Phase 2.3: 重写 crud contract 为真实行为断言(已补齐 asset-hub + novel-promotion 写回断言)。
|
||||||
|
重写文件:
|
||||||
|
- `/Users/earth/Desktop/waoowaoo/tests/integration/api/contract/crud-routes.test.ts`
|
||||||
|
必须断言:
|
||||||
|
- PATCH 后数据库字段值确实变化
|
||||||
|
- DELETE 后实体不存在
|
||||||
|
- 无权限用户无法操作他人资源
|
||||||
|
|
||||||
|
✅ Phase 2.4: 重写 task-infra contract 为真实行为断言(已补 SSE 终态事件序列断言)。
|
||||||
|
重写文件:
|
||||||
|
- `/Users/earth/Desktop/waoowaoo/tests/integration/api/contract/task-infra-routes.test.ts`
|
||||||
|
必须断言:
|
||||||
|
- dismiss 后任务状态变化
|
||||||
|
- task-target-state 与任务终态一致
|
||||||
|
- SSE 事件序列含终态事件
|
||||||
|
|
||||||
|
⏸ Phase 2.5: 扩展 route specific 测试,补关键历史回归点。
|
||||||
|
新增/扩展:
|
||||||
|
- `/Users/earth/Desktop/waoowaoo/tests/integration/api/specific/reference-to-character-api.test.ts`
|
||||||
|
- `/Users/earth/Desktop/waoowaoo/tests/integration/api/specific/characters-post-reference-forwarding.test.ts`(已完成,继续扩展)
|
||||||
|
- `/Users/earth/Desktop/waoowaoo/tests/integration/api/specific/characters-post.test.ts`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 阶段3: Worker 决策测试全量行为化
|
||||||
|
|
||||||
|
依赖关系:
|
||||||
|
- Phase 3 依赖 Phase 2(route 契约稳定后再固化 worker 结果断言)。
|
||||||
|
|
||||||
|
✅ Phase 3.1: 关键历史 bug 已有行为级样板落地。
|
||||||
|
已完成文件:
|
||||||
|
- `/Users/earth/Desktop/waoowaoo/tests/unit/worker/reference-to-character.test.ts`
|
||||||
|
- `/Users/earth/Desktop/waoowaoo/tests/unit/worker/asset-hub-image-suffix.test.ts`
|
||||||
|
- `/Users/earth/Desktop/waoowaoo/tests/unit/worker/modify-image-reference-description.test.ts`
|
||||||
|
|
||||||
|
✅ Phase 3.2: 把“失败快照类”worker 测试升级为“结果断言类”。
|
||||||
|
优先重写:
|
||||||
|
- `/Users/earth/Desktop/waoowaoo/tests/unit/worker/image-task-handlers-core.test.ts`
|
||||||
|
- `/Users/earth/Desktop/waoowaoo/tests/unit/worker/script-to-storyboard.test.ts`
|
||||||
|
- `/Users/earth/Desktop/waoowaoo/tests/unit/worker/episode-split.test.ts`
|
||||||
|
必须断言:
|
||||||
|
- 具体生成参数(referenceImages/aspectRatio/resolution)
|
||||||
|
- 具体写库字段值(description/imageUrl/imageUrls/selectedIndex)
|
||||||
|
- 关键分支(character/location/storyboard)均触发
|
||||||
|
|
||||||
|
✅ Phase 3.3: 新增核心 handler 行为测试文件(按模块拆分,已全部落地)。
|
||||||
|
新增文件:
|
||||||
|
- `/Users/earth/Desktop/waoowaoo/tests/unit/worker/character-image-task-handler.test.ts`
|
||||||
|
- `/Users/earth/Desktop/waoowaoo/tests/unit/worker/location-image-task-handler.test.ts`
|
||||||
|
- `/Users/earth/Desktop/waoowaoo/tests/unit/worker/panel-image-task-handler.test.ts`
|
||||||
|
- `/Users/earth/Desktop/waoowaoo/tests/unit/worker/panel-variant-task-handler.test.ts`
|
||||||
|
- `/Users/earth/Desktop/waoowaoo/tests/unit/worker/story-to-script.test.ts`
|
||||||
|
- `/Users/earth/Desktop/waoowaoo/tests/unit/worker/screenplay-convert.test.ts`
|
||||||
|
- `/Users/earth/Desktop/waoowaoo/tests/unit/worker/voice-design.test.ts`
|
||||||
|
- `/Users/earth/Desktop/waoowaoo/tests/unit/worker/voice-analyze.test.ts`
|
||||||
|
- `/Users/earth/Desktop/waoowaoo/tests/unit/worker/analyze-novel.test.ts`
|
||||||
|
- `/Users/earth/Desktop/waoowaoo/tests/unit/worker/analyze-global.test.ts`
|
||||||
|
- `/Users/earth/Desktop/waoowaoo/tests/unit/worker/character-profile.test.ts`
|
||||||
|
- `/Users/earth/Desktop/waoowaoo/tests/unit/worker/clips-build.test.ts`
|
||||||
|
- `/Users/earth/Desktop/waoowaoo/tests/unit/worker/asset-hub-ai-design.test.ts`
|
||||||
|
- `/Users/earth/Desktop/waoowaoo/tests/unit/worker/asset-hub-ai-modify.test.ts`
|
||||||
|
- `/Users/earth/Desktop/waoowaoo/tests/unit/worker/llm-proxy.test.ts`
|
||||||
|
- `/Users/earth/Desktop/waoowaoo/tests/unit/worker/shot-ai-tasks.test.ts`
|
||||||
|
- `/Users/earth/Desktop/waoowaoo/tests/unit/worker/shot-ai-variants.test.ts`
|
||||||
|
- `/Users/earth/Desktop/waoowaoo/tests/unit/worker/shot-ai-prompt-appearance.test.ts`
|
||||||
|
- `/Users/earth/Desktop/waoowaoo/tests/unit/worker/shot-ai-prompt-location.test.ts`
|
||||||
|
- `/Users/earth/Desktop/waoowaoo/tests/unit/worker/shot-ai-prompt-shot.test.ts`
|
||||||
|
当前进度:
|
||||||
|
- 已完成: `character-image-task-handler`、`location-image-task-handler`、`panel-image-task-handler`、`panel-variant-task-handler`、`story-to-script`、`screenplay-convert`、`voice-design`、`voice-analyze`、`analyze-novel`、`analyze-global`、`character-profile`、`clips-build`、`asset-hub-ai-design`、`asset-hub-ai-modify`、`llm-proxy`、`shot-ai-tasks`、`shot-ai-variants`、`shot-ai-prompt-appearance`、`shot-ai-prompt-location`、`shot-ai-prompt-shot`
|
||||||
|
- 待完成: 无(Phase 3.3 范围内)
|
||||||
|
|
||||||
|
⚠️ Phase 3.3.a: 边界说明(避免误算)。
|
||||||
|
不纳入“handler 入口测试清单”的文件:
|
||||||
|
- `llm-stream.ts`(stream context/callback helper)
|
||||||
|
- `modify-asset-image-task-handler.ts`(re-export 别名)
|
||||||
|
- `image-task-handlers.ts`(re-export 聚合)
|
||||||
|
|
||||||
|
✅ Phase 3.4: worker 入口层行为测试替换 routing-only 断言。
|
||||||
|
重写文件:
|
||||||
|
- `/Users/earth/Desktop/waoowaoo/tests/unit/worker/video-worker.test.ts`
|
||||||
|
- `/Users/earth/Desktop/waoowaoo/tests/unit/worker/voice-worker.test.ts`
|
||||||
|
必须断言:
|
||||||
|
- 任务类型分发到正确 handler
|
||||||
|
- handler 结果被正确回传与封装
|
||||||
|
- 失败分支日志与错误码一致
|
||||||
|
|
||||||
|
⚠️ Phase 3.5: 避免“mock 自己返回答案”造成假安全。
|
||||||
|
硬要求:
|
||||||
|
- 每个测试至少 1 个断言检查具体字段值(不是调用次数)
|
||||||
|
- 对 DB update/create 入参做 `objectContaining(data: ...)` 断言
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 阶段4: Chain 测试从队列映射升级为端到端行为链路
|
||||||
|
|
||||||
|
依赖关系:
|
||||||
|
- Phase 4 依赖 Phase 2 + Phase 3(先稳定 route 和 handler 行为,再做链路端到端)。
|
||||||
|
|
||||||
|
✅ Phase 4.1: 重写 image chain(enqueue + worker 消费 + 持久化写回断言已落地)。
|
||||||
|
重写文件:
|
||||||
|
- `/Users/earth/Desktop/waoowaoo/tests/integration/chain/image.chain.test.ts`
|
||||||
|
覆盖链路:
|
||||||
|
- route -> submitTask -> queue -> image worker -> DB 回写
|
||||||
|
示例断言:
|
||||||
|
- 任务状态从 queued -> processing -> completed
|
||||||
|
- 目标实体 imageUrl/imageUrls 有值且结构正确
|
||||||
|
|
||||||
|
✅ Phase 4.2: 重写 text chain(enqueue + worker 消费 + 结果级边界断言已落地)。
|
||||||
|
重写文件:
|
||||||
|
- `/Users/earth/Desktop/waoowaoo/tests/integration/chain/text.chain.test.ts`
|
||||||
|
覆盖链路:
|
||||||
|
- analyze/story/script/reference-to-character 全链路关键节点
|
||||||
|
|
||||||
|
✅ Phase 4.3: 重写 video chain(enqueue + video worker 消费 + lip-sync 持久化断言已落地)。
|
||||||
|
重写文件:
|
||||||
|
- `/Users/earth/Desktop/waoowaoo/tests/integration/chain/video.chain.test.ts`
|
||||||
|
覆盖链路:
|
||||||
|
- generate-video/lip-sync 任务执行结果与状态持久化
|
||||||
|
|
||||||
|
✅ Phase 4.4: 重写 voice chain(enqueue + voice worker 消费 + 关键参数透传断言已落地)。
|
||||||
|
重写文件:
|
||||||
|
- `/Users/earth/Desktop/waoowaoo/tests/integration/chain/voice.chain.test.ts`
|
||||||
|
覆盖链路:
|
||||||
|
- voice-design/voice-generate 的实体写回与任务状态
|
||||||
|
|
||||||
|
⏸ Phase 4.5: 固化外部 fake 层,保证零真实外网请求。
|
||||||
|
使用/扩展:
|
||||||
|
- `/Users/earth/Desktop/waoowaoo/tests/helpers/fakes/llm.ts`
|
||||||
|
- `/Users/earth/Desktop/waoowaoo/tests/helpers/fakes/media.ts`
|
||||||
|
- `/Users/earth/Desktop/waoowaoo/tests/helpers/fakes/providers.ts`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 阶段5: 前端状态回归测试行为化
|
||||||
|
|
||||||
|
✅ Phase 5.1: 替换源码字符串检查为 hook 真实行为测试。
|
||||||
|
重写文件:
|
||||||
|
- `/Users/earth/Desktop/waoowaoo/tests/unit/optimistic/sse-invalidation.test.ts`
|
||||||
|
- `/Users/earth/Desktop/waoowaoo/tests/unit/optimistic/task-target-state-map.test.ts`
|
||||||
|
必须断言:
|
||||||
|
- 给定事件序列时 query invalidation 实际触发条件正确
|
||||||
|
- target state map 在 queued/processing/completed/failed 下输出正确
|
||||||
|
|
||||||
|
✅ Phase 5.2: 现有 optimistic mutation 行为测试保留并扩展。
|
||||||
|
文件:
|
||||||
|
- `/Users/earth/Desktop/waoowaoo/tests/unit/optimistic/asset-hub-mutations.test.ts`
|
||||||
|
- `/Users/earth/Desktop/waoowaoo/tests/unit/optimistic/project-asset-mutations.test.ts`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 阶段6: 覆盖矩阵升级为“行为测试矩阵”
|
||||||
|
|
||||||
|
✅ Phase 6.1: 新增 route 行为覆盖矩阵。
|
||||||
|
新增:
|
||||||
|
- `/Users/earth/Desktop/waoowaoo/tests/contracts/route-behavior-matrix.ts`
|
||||||
|
要求:
|
||||||
|
- 117 个 route 每个都映射到至少 1 条行为级 caseId + test 文件
|
||||||
|
|
||||||
|
✅ Phase 6.2: 新增 task type 行为覆盖矩阵。
|
||||||
|
新增:
|
||||||
|
- `/Users/earth/Desktop/waoowaoo/tests/contracts/tasktype-behavior-matrix.ts`
|
||||||
|
要求:
|
||||||
|
- 37 个 TASK_TYPE 每个都映射 worker 行为测试 + chain 行为测试
|
||||||
|
|
||||||
|
✅ Phase 6.3: 新增矩阵守卫脚本。
|
||||||
|
新增:
|
||||||
|
- `/Users/earth/Desktop/waoowaoo/scripts/guards/test-behavior-route-coverage-guard.mjs`
|
||||||
|
- `/Users/earth/Desktop/waoowaoo/scripts/guards/test-behavior-tasktype-coverage-guard.mjs`
|
||||||
|
|
||||||
|
⚠️ Phase 6.4: 矩阵维护成本高。
|
||||||
|
策略:
|
||||||
|
- 优先通过脚本自动校验文件存在与 caseId 唯一性
|
||||||
|
- 每次新增 route/tasktype 必须更新矩阵,否则 CI 失败
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 阶段7: CI 门禁与执行策略
|
||||||
|
|
||||||
|
✅ Phase 7.1: 新增行为级门禁命令。
|
||||||
|
修改:
|
||||||
|
- `/Users/earth/Desktop/waoowaoo/package.json`
|
||||||
|
新增脚本:
|
||||||
|
- `test:behavior:unit`
|
||||||
|
- `test:behavior:api`
|
||||||
|
- `test:behavior:chain`
|
||||||
|
- `test:behavior:guards`
|
||||||
|
- `test:behavior:full`
|
||||||
|
|
||||||
|
⏸ Phase 7.2: PR workflow 强制执行行为级全量门禁。
|
||||||
|
修改:
|
||||||
|
- `/Users/earth/Desktop/waoowaoo/.github/workflows/test-regression-pr.yml`
|
||||||
|
|
||||||
|
✅ Phase 7.3: 失败诊断脚本已接入(保留)。
|
||||||
|
文件:
|
||||||
|
- `/Users/earth/Desktop/waoowaoo/scripts/test-regression-runner.sh`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 阶段8: 收口与冻结
|
||||||
|
|
||||||
|
⏸ Phase 8.1: 删除/降级旧结构级测试(仅保留轻量守卫,不计入行为覆盖率)。
|
||||||
|
目标:
|
||||||
|
- contract/chain 中不再有“只读源码字符串”的主断言
|
||||||
|
|
||||||
|
⏸ Phase 8.2: 建立“新增功能必须附行为测试”的提交流程。
|
||||||
|
落地:
|
||||||
|
- PR 模板加检查项
|
||||||
|
- guard 失败提示明确指出缺失 case
|
||||||
|
|
||||||
|
✅ Phase 8.3: 冻结基线并发布“行为级测试开发规范”。
|
||||||
|
新增:
|
||||||
|
- `/Users/earth/Desktop/waoowaoo/docs/testing/behavior-test-guideline.md`
|
||||||
|
|
||||||
|
⚠️ Phase 8.4: 不可达目标声明。
|
||||||
|
说明:
|
||||||
|
- “100% 无 bug”不可证明;可达目标是“100% 关键功能链路行为覆盖 + 关键字段结果断言 + 变更自动门禁”。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 阶段9: Billing 与并发测试纳入总蓝图
|
||||||
|
|
||||||
|
🔄 Phase 9.1: billing 现有测试纳入“行为级总体覆盖说明”,避免遗漏域。
|
||||||
|
覆盖现状:
|
||||||
|
- `tests/unit/billing/*.test.ts`
|
||||||
|
- `tests/integration/billing/*.integration.test.ts`
|
||||||
|
- `tests/concurrency/billing/ledger.concurrency.test.ts`
|
||||||
|
|
||||||
|
⏸ Phase 9.2: 明确 billing worker/ledger 行为级断言增强点。
|
||||||
|
新增/重写方向:
|
||||||
|
- 计费写账一致性(usage->ledger)字段级断言
|
||||||
|
- 异常重试/幂等行为断言
|
||||||
|
- 并发写入冲突场景断言
|
||||||
|
|
||||||
|
⏸ Phase 9.3: 将 billing 与 concurrency 纳入 `test:behavior:full` 报告维度。
|
||||||
|
要求:
|
||||||
|
- 输出 billing/concurrency 独立通过率
|
||||||
|
- 与 route/worker/chain 覆盖率同级展示
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3: 后端 Worker 单元测试硬规范(强制)
|
||||||
|
|
||||||
|
### 3.1 必须覆盖的测试类型
|
||||||
|
每个 worker handler 必须至少包含三类用例:
|
||||||
|
1. 失败路径:参数缺失/格式错误时,抛出正确错误信息。
|
||||||
|
2. 成功路径:正常输入时,副作用结果正确(数据库写入/关键调用参数/返回值)。
|
||||||
|
3. 关键分支:`if/else` 分支每条至少 1 个用例。
|
||||||
|
|
||||||
|
### 3.2 Mock 规范
|
||||||
|
必须 Mock:
|
||||||
|
1. `prisma` 等数据库访问。
|
||||||
|
2. LLM/图像生成/视觉分析等 AI 调用。
|
||||||
|
3. COS/上传等文件存储。
|
||||||
|
4. 外部 HTTP 请求。
|
||||||
|
5. 一切需要网络的依赖。
|
||||||
|
|
||||||
|
不能 Mock:
|
||||||
|
1. 待测业务逻辑函数本身。
|
||||||
|
2. 项目内业务常量(例如 `CHARACTER_PROMPT_SUFFIX`),必须直接 import 使用。
|
||||||
|
|
||||||
|
### 3.3 断言规范(最高优先级)
|
||||||
|
每个 `it()` 必须断言“结果”,不能只断言“过程”。
|
||||||
|
|
||||||
|
必须断言:
|
||||||
|
1. 数据库 `update/create` 的具体字段值(如 `description`、`imageUrl`、`imageUrls`)。
|
||||||
|
2. AI/生成函数收到的核心参数(如 `prompt` 必含内容)。
|
||||||
|
3. 图像生成相关关键参数(如 `referenceImages`、`aspectRatio`、`resolution`)。
|
||||||
|
|
||||||
|
弱断言限制:
|
||||||
|
1. `toHaveBeenCalled()` 不能作为唯一主断言。
|
||||||
|
2. `toHaveBeenCalledTimes(N)` 仅在“次数本身有业务意义”时使用。
|
||||||
|
|
||||||
|
### 3.4 测试数据规范
|
||||||
|
1. 数据必须能触发目标分支(例如“有参考图/无参考图”分别建用例)。
|
||||||
|
2. 关键业务字段必须使用有语义的固定值。
|
||||||
|
3. 无关透传字段可用占位值(如 `task-1`)。
|
||||||
|
|
||||||
|
禁止模式:
|
||||||
|
1. “自己给答案自己验证”:mock 返回值与断言目标完全同源。
|
||||||
|
2. 正确做法:mock AI 返回值,断言该值被写入到 `prisma.update({ data })` 的具体字段。
|
||||||
|
|
||||||
|
### 3.5 it() 结构模板(强制推荐)
|
||||||
|
```ts
|
||||||
|
it('[条件] -> [预期结果]', async () => {
|
||||||
|
// 1. 准备 mock(仅覆盖本场景差异)
|
||||||
|
// 2. 构造 job/payload(只给本场景关键字段)
|
||||||
|
// 3. 执行 handler
|
||||||
|
// 4. 断言:
|
||||||
|
// a. DB data 字段
|
||||||
|
// b. 核心调用参数(prompt/referenceImages/aspectRatio)
|
||||||
|
// c. 返回值关键字段(如 success)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.6 命名规范
|
||||||
|
统一格式:`[条件] -> [预期结果]`
|
||||||
|
示例:
|
||||||
|
1. `没有 extraImageUrls -> 不调用分析,description 不更新`
|
||||||
|
2. `有 extraImageUrls -> AI 分析结果写入 description`
|
||||||
|
3. `AI 调用失败 -> 主流程成功且 description 不被污染`
|
||||||
|
4. `缺少必填参数 -> 抛出包含字段名的错误信息`
|
||||||
|
|
||||||
|
### 3.7 一条 bug 一条测试(强制)
|
||||||
|
1. 每修复一个 bug,必须新增至少一条对应回归测试。
|
||||||
|
2. 测试名必须可追溯该 bug 场景(例如“防止 XXX 回归”)。
|
||||||
|
3. 未补测试不得标记该 bug 任务完成。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 执行日志(必须持续追加)
|
||||||
|
格式:
|
||||||
|
- [YYYY-MM-DD HH:mm] 状态变更: <Phase/任务> <旧状态> -> <新状态>
|
||||||
|
- [YYYY-MM-DD HH:mm] 修改文件: <绝对路径列表>
|
||||||
|
- [YYYY-MM-DD HH:mm] 运行命令: <命令>
|
||||||
|
- [YYYY-MM-DD HH:mm] 结果: <通过/失败 + 摘要>
|
||||||
|
- [YYYY-MM-DD HH:mm] 问题: <若有>
|
||||||
|
|
||||||
|
- [2026-02-25 21:59] 状态变更: Phase 3.1 ⏸ -> ✅
|
||||||
|
- [2026-02-25 21:59] 修改文件: /Users/earth/Desktop/waoowaoo/tests/unit/worker/reference-to-character.test.ts, /Users/earth/Desktop/waoowaoo/tests/unit/worker/asset-hub-image-suffix.test.ts, /Users/earth/Desktop/waoowaoo/tests/unit/worker/modify-image-reference-description.test.ts, /Users/earth/Desktop/waoowaoo/src/lib/workers/handlers/reference-to-character.ts
|
||||||
|
- [2026-02-25 21:59] 运行命令: BILLING_TEST_BOOTSTRAP=0 npx vitest run tests/unit/worker/reference-to-character.test.ts tests/unit/worker/asset-hub-image-suffix.test.ts tests/unit/worker/modify-image-reference-description.test.ts
|
||||||
|
- [2026-02-25 21:59] 结果: 关键历史回归点(后缀失效/参考图描述不更新)已行为级可测
|
||||||
|
- [2026-02-25 21:59] 问题: 无
|
||||||
|
|
||||||
|
- [2026-02-25 22:00] 状态变更: Phase 1.2 ⏸ -> ✅
|
||||||
|
- [2026-02-25 22:00] 修改文件: /Users/earth/Desktop/waoowaoo/tests/contracts/requirements-matrix.ts, /Users/earth/Desktop/waoowaoo/tests/contracts/requirements-matrix.test.ts
|
||||||
|
- [2026-02-25 22:00] 运行命令: BILLING_TEST_BOOTSTRAP=0 npx vitest run tests/contracts/requirements-matrix.test.ts
|
||||||
|
- [2026-02-25 22:00] 结果: 阻断不存在测试路径引用(已修复 `crud-asset-hub-routes.test.ts` 错误引用)
|
||||||
|
- [2026-02-25 22:00] 问题: 无
|
||||||
|
|
||||||
|
- [2026-02-25 22:10] 状态变更: Phase 1.3.a ⏸ -> ✅
|
||||||
|
- [2026-02-25 22:10] 修改文件: /Users/earth/Desktop/waoowaoo/SYSTEM_BEHAVIOR_LEVEL_TEST_MASTER_PLAN.md
|
||||||
|
- [2026-02-25 22:10] 运行命令: 文档更新(无测试执行)
|
||||||
|
- [2026-02-25 22:10] 结果: 已将后端 Worker 单元测试硬规范(覆盖/Mock/断言/命名/一 bug 一测试)固化为主计划强制章节
|
||||||
|
- [2026-02-25 22:10] 问题: 自动化守卫脚本仍待实现(Phase 1.3)
|
||||||
|
|
||||||
|
- [2026-02-25 22:20] 状态变更: 文档校正(扫描计数与范围修正)
|
||||||
|
- [2026-02-25 22:20] 修改文件: /Users/earth/Desktop/waoowaoo/SYSTEM_BEHAVIOR_LEVEL_TEST_MASTER_PLAN.md
|
||||||
|
- [2026-02-25 22:20] 运行命令: rg --files/rg -n 扫描 tests 与 handlers
|
||||||
|
- [2026-02-25 22:20] 结果: 已修正 test 文件数=51、handlers 文件数=43、handler 入口数=26;补齐 Phase 3.3 遗漏 handler;新增 Phase 依赖关系与 Phase 9(billing/concurrency)
|
||||||
|
- [2026-02-25 22:20] 问题: Phase 1.3 自动守卫脚本尚未实现
|
||||||
|
|
||||||
|
- [2026-02-25 23:05] 状态变更: Phase 2.1 🔄 -> ✅, Phase 2.2 ⏸ -> ✅
|
||||||
|
- [2026-02-25 23:05] 修改文件: /Users/earth/Desktop/waoowaoo/tests/integration/api/contract/direct-submit-routes.test.ts, /Users/earth/Desktop/waoowaoo/tests/integration/api/contract/llm-observe-routes.test.ts
|
||||||
|
- [2026-02-25 23:05] 运行命令: BILLING_TEST_BOOTSTRAP=0 npx vitest run tests/integration/api/contract/direct-submit-routes.test.ts tests/integration/api/contract/llm-observe-routes.test.ts
|
||||||
|
- [2026-02-25 23:05] 结果: 两类 contract 测试已由结构级改为行为级并通过,覆盖 16 个 direct-submit routes 与 22 个 llm-observe routes
|
||||||
|
- [2026-02-25 23:05] 问题: 无
|
||||||
|
|
||||||
|
- [2026-02-25 23:06] 状态变更: Phase 2.3 ⏸ -> 🔄, Phase 2.4 ⏸ -> 🔄
|
||||||
|
- [2026-02-25 23:06] 修改文件: /Users/earth/Desktop/waoowaoo/tests/integration/api/contract/crud-routes.test.ts, /Users/earth/Desktop/waoowaoo/tests/integration/api/contract/task-infra-routes.test.ts
|
||||||
|
- [2026-02-25 23:06] 运行命令: BILLING_TEST_BOOTSTRAP=0 npx vitest run tests/integration/api/contract/crud-routes.test.ts tests/integration/api/contract/task-infra-routes.test.ts
|
||||||
|
- [2026-02-25 23:06] 结果: 已替换为真实 route 调用断言;crud 完成鉴权行为覆盖,task-infra 完成鉴权/参数/核心成功路径,后续补 DB 写回与 SSE 终态序列
|
||||||
|
- [2026-02-25 23:06] 问题: 无
|
||||||
|
|
||||||
|
- [2026-02-25 23:06] 状态变更: Phase 3.2 🔄 -> ✅, Phase 3.4 ⏸ -> ✅
|
||||||
|
- [2026-02-25 23:06] 修改文件: /Users/earth/Desktop/waoowaoo/tests/unit/worker/image-task-handlers-core.test.ts, /Users/earth/Desktop/waoowaoo/tests/unit/worker/episode-split.test.ts, /Users/earth/Desktop/waoowaoo/tests/unit/worker/script-to-storyboard.test.ts, /Users/earth/Desktop/waoowaoo/tests/unit/worker/video-worker.test.ts, /Users/earth/Desktop/waoowaoo/tests/unit/worker/voice-worker.test.ts
|
||||||
|
- [2026-02-25 23:06] 运行命令: BILLING_TEST_BOOTSTRAP=0 npx vitest run tests/unit/worker/script-to-storyboard.test.ts tests/unit/worker/video-worker.test.ts tests/unit/worker/voice-worker.test.ts tests/unit/worker/image-task-handlers-core.test.ts tests/unit/worker/episode-split.test.ts
|
||||||
|
- [2026-02-25 23:06] 结果: worker 测试已升级为结果级断言,覆盖失败路径、成功路径、关键分支与关键写库字段
|
||||||
|
- [2026-02-25 23:06] 问题: 无
|
||||||
|
|
||||||
|
- [2026-02-25 23:07] 状态变更: Phase 4.2 ⏸ -> 🔄, Phase 4.3 ⏸ -> 🔄, Phase 4.4 ⏸ -> 🔄
|
||||||
|
- [2026-02-25 23:07] 修改文件: /Users/earth/Desktop/waoowaoo/tests/integration/chain/image.chain.test.ts, /Users/earth/Desktop/waoowaoo/tests/integration/chain/text.chain.test.ts, /Users/earth/Desktop/waoowaoo/tests/integration/chain/video.chain.test.ts, /Users/earth/Desktop/waoowaoo/tests/integration/chain/voice.chain.test.ts
|
||||||
|
- [2026-02-25 23:07] 运行命令: BILLING_TEST_BOOTSTRAP=0 npx vitest run tests/integration/chain/image.chain.test.ts tests/integration/chain/text.chain.test.ts tests/integration/chain/video.chain.test.ts tests/integration/chain/voice.chain.test.ts
|
||||||
|
- [2026-02-25 23:07] 结果: chain 测试已由映射断言升级为 addTaskJob enqueue 行为断言(校验 queue 选择 + jobId/priority)
|
||||||
|
- [2026-02-25 23:07] 问题: route->worker->DB 端到端链路仍待补
|
||||||
|
|
||||||
|
- [2026-02-25 23:08] 运行命令: BILLING_TEST_BOOTSTRAP=0 npx vitest run tests/integration/api/contract tests/integration/chain tests/unit/worker
|
||||||
|
- [2026-02-25 23:08] 结果: 16 个测试文件全部通过,117/117 测试通过
|
||||||
|
|
||||||
|
- [2026-02-25 23:09] 修改文件: /Users/earth/Desktop/waoowaoo/tests/integration/api/contract/crud-routes.test.ts
|
||||||
|
- [2026-02-25 23:09] 运行命令: BILLING_TEST_BOOTSTRAP=0 npx vitest run tests/integration/api/contract/crud-routes.test.ts
|
||||||
|
- [2026-02-25 23:09] 结果: 新增 CRUD 结果级断言(PATCH 写入字段值、DELETE 删除调用与越权 403),从“仅鉴权检查”升级为“含写库行为检查”
|
||||||
|
- [2026-02-25 23:09] 问题: novel-promotion 侧 CRUD 的字段级断言仍待扩展
|
||||||
|
|
||||||
|
- [2026-02-25 23:09] 修改文件: /Users/earth/Desktop/waoowaoo/tests/integration/api/contract/task-infra-routes.test.ts
|
||||||
|
- [2026-02-25 23:09] 运行命令: BILLING_TEST_BOOTSTRAP=0 npx vitest run tests/integration/api/contract/task-infra-routes.test.ts
|
||||||
|
- [2026-02-25 23:09] 结果: 新增 SSE replay 成功路径断言(`text/event-stream`、`last-event-id` 回放、channel 订阅行为)
|
||||||
|
- [2026-02-25 23:09] 问题: SSE 终态事件的 completed/failed 序列断言仍待补
|
||||||
|
|
||||||
|
- [2026-02-25 23:10] 运行命令: BILLING_TEST_BOOTSTRAP=0 npx vitest run tests/integration/api/contract tests/integration/chain tests/unit/worker
|
||||||
|
- [2026-02-25 23:10] 结果: 16 个测试文件全部通过,120/120 测试通过
|
||||||
|
|
||||||
|
- [2026-02-25 23:11] 状态变更: Phase 1.3 🔄 -> ✅
|
||||||
|
- [2026-02-25 23:11] 修改文件: /Users/earth/Desktop/waoowaoo/tests/contracts/behavior-test-standard.md, /Users/earth/Desktop/waoowaoo/scripts/guards/test-behavior-quality-guard.mjs, /Users/earth/Desktop/waoowaoo/package.json
|
||||||
|
- [2026-02-25 23:11] 运行命令: node scripts/guards/test-behavior-quality-guard.mjs && npm run check:test-coverage-guards
|
||||||
|
- [2026-02-25 23:11] 结果: 行为级质量守卫已接入(拦截源码字符串契约 + 弱断言),并纳入 `check:test-coverage-guards`
|
||||||
|
- [2026-02-25 23:11] 问题: 无
|
||||||
|
|
||||||
|
- [2026-02-25 23:12] 修改文件: /Users/earth/Desktop/waoowaoo/tests/integration/api/contract/direct-submit-routes.test.ts, /Users/earth/Desktop/waoowaoo/tests/integration/api/contract/llm-observe-routes.test.ts
|
||||||
|
- [2026-02-25 23:12] 运行命令: BILLING_TEST_BOOTSTRAP=0 npx vitest run tests/integration/api/contract/direct-submit-routes.test.ts tests/integration/api/contract/llm-observe-routes.test.ts
|
||||||
|
- [2026-02-25 23:12] 结果: 两个 contract 测试新增 `toHaveBeenCalledWith(objectContaining(...))` 强断言,通过行为质量守卫
|
||||||
|
- [2026-02-25 23:12] 问题: 无
|
||||||
|
|
||||||
|
- [2026-02-25 23:13] 状态变更: Phase 5.1 ⏸ -> ✅
|
||||||
|
- [2026-02-25 23:13] 修改文件: /Users/earth/Desktop/waoowaoo/tests/unit/optimistic/sse-invalidation.test.ts, /Users/earth/Desktop/waoowaoo/tests/unit/optimistic/task-target-state-map.test.ts
|
||||||
|
- [2026-02-25 23:13] 运行命令: BILLING_TEST_BOOTSTRAP=0 npx vitest run tests/unit/optimistic/sse-invalidation.test.ts tests/unit/optimistic/task-target-state-map.test.ts
|
||||||
|
- [2026-02-25 23:13] 结果: 两个 optimistic 结构级测试已替换为行为级(SSE 终态 invalidation 与 target-state overlay 合并规则)
|
||||||
|
- [2026-02-25 23:13] 问题: 无
|
||||||
|
|
||||||
|
- [2026-02-25 23:16] 状态变更: Phase 3.3 ⏸ -> 🔄
|
||||||
|
- [2026-02-25 23:16] 修改文件: /Users/earth/Desktop/waoowaoo/tests/unit/worker/shot-ai-tasks.test.ts, /Users/earth/Desktop/waoowaoo/tests/unit/worker/voice-design.test.ts, /Users/earth/Desktop/waoowaoo/tests/unit/worker/asset-hub-ai-design.test.ts, /Users/earth/Desktop/waoowaoo/tests/unit/worker/asset-hub-ai-modify.test.ts, /Users/earth/Desktop/waoowaoo/tests/unit/worker/shot-ai-prompt-appearance.test.ts, /Users/earth/Desktop/waoowaoo/tests/unit/worker/shot-ai-prompt-location.test.ts, /Users/earth/Desktop/waoowaoo/tests/unit/worker/shot-ai-prompt-shot.test.ts, /Users/earth/Desktop/waoowaoo/tests/unit/worker/shot-ai-variants.test.ts, /Users/earth/Desktop/waoowaoo/tests/unit/worker/llm-proxy.test.ts
|
||||||
|
- [2026-02-25 23:16] 运行命令: BILLING_TEST_BOOTSTRAP=0 npx vitest run tests/unit/worker/shot-ai-tasks.test.ts tests/unit/worker/voice-design.test.ts tests/unit/worker/asset-hub-ai-design.test.ts tests/unit/worker/asset-hub-ai-modify.test.ts tests/unit/worker/shot-ai-prompt-appearance.test.ts tests/unit/worker/shot-ai-prompt-location.test.ts tests/unit/worker/shot-ai-prompt-shot.test.ts tests/unit/worker/shot-ai-variants.test.ts tests/unit/worker/llm-proxy.test.ts
|
||||||
|
- [2026-02-25 23:16] 结果: 新增 9 个 worker 行为测试文件(20 条用例+5 条用例),覆盖 shot-ai 分发、prompt 修改链路、asset-hub ai 设计/修改、voice-design、llm-proxy 显式失败
|
||||||
|
- [2026-02-25 23:16] 问题: 无
|
||||||
|
|
||||||
|
- [2026-02-25 23:16] 运行命令: BILLING_TEST_BOOTSTRAP=0 npx vitest run tests/unit/worker
|
||||||
|
- [2026-02-25 23:16] 结果: worker 套件通过,17 文件 / 48 测试通过
|
||||||
|
|
||||||
|
- [2026-02-25 23:17] 运行命令: BILLING_TEST_BOOTSTRAP=0 npx vitest run tests/unit/optimistic tests/unit/worker tests/integration/api/contract tests/integration/chain
|
||||||
|
- [2026-02-25 23:17] 结果: 全回归分组通过,31 文件 / 155 测试通过
|
||||||
|
|
||||||
|
- [2026-02-25 23:25] 修改文件: /Users/earth/Desktop/waoowaoo/tests/unit/worker/story-to-script.test.ts, /Users/earth/Desktop/waoowaoo/tests/unit/worker/screenplay-convert.test.ts, /Users/earth/Desktop/waoowaoo/tests/unit/worker/analyze-novel.test.ts, /Users/earth/Desktop/waoowaoo/tests/unit/worker/analyze-global.test.ts, /Users/earth/Desktop/waoowaoo/tests/unit/worker/voice-analyze.test.ts, /Users/earth/Desktop/waoowaoo/tests/unit/worker/clips-build.test.ts, /Users/earth/Desktop/waoowaoo/tests/unit/worker/character-profile.test.ts, /Users/earth/Desktop/waoowaoo/tests/unit/worker/character-image-task-handler.test.ts, /Users/earth/Desktop/waoowaoo/tests/unit/worker/location-image-task-handler.test.ts, /Users/earth/Desktop/waoowaoo/tests/unit/worker/panel-image-task-handler.test.ts, /Users/earth/Desktop/waoowaoo/tests/unit/worker/panel-variant-task-handler.test.ts
|
||||||
|
- [2026-02-25 23:25] 运行命令: BILLING_TEST_BOOTSTRAP=0 npx vitest run tests/unit/worker/story-to-script.test.ts tests/unit/worker/screenplay-convert.test.ts tests/unit/worker/analyze-novel.test.ts tests/unit/worker/analyze-global.test.ts tests/unit/worker/voice-analyze.test.ts tests/unit/worker/clips-build.test.ts tests/unit/worker/character-profile.test.ts tests/unit/worker/character-image-task-handler.test.ts tests/unit/worker/location-image-task-handler.test.ts tests/unit/worker/panel-image-task-handler.test.ts tests/unit/worker/panel-variant-task-handler.test.ts
|
||||||
|
- [2026-02-25 23:25] 结果: 新增 11 个 worker handler 行为测试文件,覆盖剩余未落地入口(文本链路 + 图片链路),失败路径/成功路径/关键分支断言全部落地
|
||||||
|
- [2026-02-25 23:25] 问题: 首轮运行出现 5 个断言问题(重试分支 mock 泄漏与断言过窄),已在同轮修复
|
||||||
|
|
||||||
|
- [2026-02-25 23:26] 运行命令: BILLING_TEST_BOOTSTRAP=0 npx vitest run tests/unit/worker
|
||||||
|
- [2026-02-25 23:26] 结果: worker 套件通过,28 文件 / 76 测试通过
|
||||||
|
|
||||||
|
- [2026-02-25 23:26] 运行命令: BILLING_TEST_BOOTSTRAP=0 npx vitest run tests/unit/optimistic tests/unit/worker tests/integration/api/contract tests/integration/chain
|
||||||
|
- [2026-02-25 23:26] 结果: 全回归分组通过,42 文件 / 183 测试通过
|
||||||
|
|
||||||
|
- [2026-02-25 23:27] 状态变更: Phase 3.3 🔄 -> ✅
|
||||||
|
- [2026-02-25 23:27] 运行命令: npm run check:test-coverage-guards
|
||||||
|
- [2026-02-25 23:27] 结果: 覆盖守卫通过(behavior quality / route=117 / taskType=37)
|
||||||
|
- [2026-02-25 23:27] 问题: 无
|
||||||
|
|
||||||
|
- [2026-02-25 23:27] 运行命令: rg \"export async function handle\" src/lib/workers/handlers -l + tests/unit/worker import 对账
|
||||||
|
- [2026-02-25 23:27] 结果: 26/26 handler 入口均存在对应 worker 行为测试文件引用
|
||||||
|
- [2026-02-25 23:27] 问题: 无
|
||||||
|
|
||||||
|
- [2026-02-25 23:46] 修改文件: /Users/earth/Desktop/waoowaoo/tests/integration/api/contract/crud-routes.test.ts, /Users/earth/Desktop/waoowaoo/tests/integration/api/contract/task-infra-routes.test.ts
|
||||||
|
- [2026-02-25 23:46] 运行命令: BILLING_TEST_BOOTSTRAP=0 npx vitest run tests/integration/api/contract/crud-routes.test.ts tests/integration/api/contract/task-infra-routes.test.ts
|
||||||
|
- [2026-02-25 23:46] 结果: CRUD 合同新增 novel-promotion 写回断言(select-character-image / select-location-image / clips PATCH),task-infra 新增 SSE channel 终态事件序列断言(processing -> completed)
|
||||||
|
- [2026-02-25 23:46] 问题: 无
|
||||||
|
|
||||||
|
- [2026-02-25 23:46] 修改文件: /Users/earth/Desktop/waoowaoo/tests/integration/chain/image.chain.test.ts, /Users/earth/Desktop/waoowaoo/tests/integration/chain/text.chain.test.ts, /Users/earth/Desktop/waoowaoo/tests/integration/chain/video.chain.test.ts, /Users/earth/Desktop/waoowaoo/tests/integration/chain/voice.chain.test.ts
|
||||||
|
- [2026-02-25 23:46] 运行命令: BILLING_TEST_BOOTSTRAP=0 npx vitest run tests/integration/chain/image.chain.test.ts tests/integration/chain/text.chain.test.ts tests/integration/chain/video.chain.test.ts tests/integration/chain/voice.chain.test.ts
|
||||||
|
- [2026-02-25 23:46] 结果: 4 个 chain 文件由“仅 queue 映射”升级为“queue payload -> worker 消费 -> 结果/写回断言”
|
||||||
|
- [2026-02-25 23:46] 问题: 无
|
||||||
|
|
||||||
|
- [2026-02-25 23:47] 修改文件: /Users/earth/Desktop/waoowaoo/tests/contracts/route-behavior-matrix.ts, /Users/earth/Desktop/waoowaoo/tests/contracts/tasktype-behavior-matrix.ts, /Users/earth/Desktop/waoowaoo/scripts/guards/test-behavior-route-coverage-guard.mjs, /Users/earth/Desktop/waoowaoo/scripts/guards/test-behavior-tasktype-coverage-guard.mjs, /Users/earth/Desktop/waoowaoo/package.json, /Users/earth/Desktop/waoowaoo/tests/contracts/task-type-catalog.ts, /Users/earth/Desktop/waoowaoo/docs/testing/behavior-test-guideline.md
|
||||||
|
- [2026-02-25 23:47] 运行命令: BILLING_TEST_BOOTSTRAP=0 npx vitest run tests/unit/optimistic tests/unit/worker tests/integration/api/contract tests/integration/chain && npm run check:test-coverage-guards
|
||||||
|
- [2026-02-25 23:47] 结果: 分组回归通过(42 文件 / 191 测试),覆盖门禁通过(behavior quality + route 117 + taskType 37 + behavior matrices)
|
||||||
|
- [2026-02-25 23:47] 问题: 无
|
||||||
|
|
||||||
|
- [2026-02-25 23:51] 运行命令: npm run test:behavior:full
|
||||||
|
- [2026-02-25 23:51] 结果: 行为级全链路命令通过(guards + unit + api + chain);unit=39 文件/107 测试,api=4 文件/93 测试,chain=4 文件/12 测试
|
||||||
|
- [2026-02-25 23:51] 问题: unit 辅助测试阶段出现本地 Redis 连接拒绝日志(127.0.0.1:6380)但不影响用例通过,后续可按需优化为静默 mock
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4: 验证策略
|
||||||
|
|
||||||
|
### 4.1 可量化验收目标(全部必须达成)
|
||||||
|
1. Route 行为覆盖率: `117/117`(每个 route 至少 1 个行为级用例)。
|
||||||
|
2. TASK_TYPE 行为覆盖率: `37/37`(每个 task type 至少 1 个 worker 行为用例 + 1 个 chain 行为用例)。
|
||||||
|
3. 结构级 contract/chain 主断言占比: `0%`(不得再以源码字符串匹配作为主断言)。
|
||||||
|
4. 关键回归场景覆盖: `100%`(参考图链路、后缀链路、编辑写回链路、task state 链路)。
|
||||||
|
5. 外部真实调用次数: `0`(测试环境必须全 fake)。
|
||||||
|
6. PR 门禁: `100%` 执行 `test:behavior:full`,任一缺失即失败。
|
||||||
|
7. Worker 用例规范符合率: `100%`(每个 worker 测试文件均满足 3.1~3.7 规则)。
|
||||||
|
8. Billing + Concurrency 维度通过率: `100%`(纳入统一验收报告)。
|
||||||
|
|
||||||
|
### 4.2 核心验证命令
|
||||||
|
- `npm run test:guards`
|
||||||
|
- `cross-env BILLING_TEST_BOOTSTRAP=0 vitest run tests/unit/worker`
|
||||||
|
- `cross-env BILLING_TEST_BOOTSTRAP=0 vitest run tests/unit/helpers`
|
||||||
|
- `cross-env BILLING_TEST_BOOTSTRAP=1 vitest run tests/integration/api`
|
||||||
|
- `cross-env BILLING_TEST_BOOTSTRAP=1 vitest run tests/integration/chain`
|
||||||
|
- `npm run test:pr`
|
||||||
|
|
||||||
|
### 4.3 用例质量验证(防假绿灯)
|
||||||
|
每个新增行为测试必须至少满足两条:
|
||||||
|
1. 断言具体业务字段值(例如 `description/imageUrls/locale/meta/referenceImages`)。
|
||||||
|
2. 覆盖至少一个历史回归分支。
|
||||||
|
3. 覆盖一个失败分支(权限/参数/模型未配置)。
|
||||||
|
4. 不使用“mock 自己返回结果并直接断言调用次数”的空测试模式。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5: 备注
|
||||||
|
|
||||||
|
1. 本文档是“行为级测试替换计划”,与 `SYSTEM_REGRESSION_COVERAGE_MASTER_PLAN.md` 并行存在;冲突时以“行为级优先”原则执行。
|
||||||
|
2. 本计划默认不引入兼容层与静默回退,错误必须显式暴露。
|
||||||
|
3. 新接手模型必须先阅读本文件,再执行代码修改;执行后必须回写执行日志。
|
||||||
|
4. 如果出现“测试通过但线上仍回归”,优先审计断言是否为结果级而不是调用级。
|
||||||
590
SYSTEM_REGRESSION_COVERAGE_MASTER_PLAN.md
Normal file
590
SYSTEM_REGRESSION_COVERAGE_MASTER_PLAN.md
Normal file
@@ -0,0 +1,590 @@
|
|||||||
|
你必须按照目前的md文件详细执行我们的代码修改计划,且必须时刻关注,维护本次md文档,确保该文档能始终保持最新,和我们代码库保持完全一致,除非用户要求,否则默认禁止打补丁,禁止兼容层,我们需要的是简洁干净可扩展的系统,我们这个系统目前没有人用,可以一次性全量,彻底,不留遗留的修改,并且需要一次性完成所有,禁止停下,禁止自己停止任务,一次性完成所有内容。
|
||||||
|
|
||||||
|
# 全系统回归测试执行主计划
|
||||||
|
版本: v1.0
|
||||||
|
仓库: /Users/earth/Desktop/waoowaoo
|
||||||
|
最后更新: 2026-02-25
|
||||||
|
责任模式: 单一主计划文档驱动(本文件是唯一事实来源)
|
||||||
|
|
||||||
|
## 0. 文档维护协议(强制)
|
||||||
|
1. 每次开始任何代码工作前,必须先更新“2:阶段+具体任务”的状态。
|
||||||
|
2. 每次完成一个任务,必须同步更新为 ✅ 并记录变更文件与命令。
|
||||||
|
3. 任何发现的阻塞必须写入 ⚠️ 问题区,不允许口头跳过。
|
||||||
|
4. 不允许引入兼容层、双轨逻辑、静默回退、假成功。
|
||||||
|
5. 新增功能或改动若未补测试,禁止标记为完成。
|
||||||
|
6. 新模型/新窗口接手时,只允许依据本文件继续执行,不依赖历史聊天上下文。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1: 项目目标
|
||||||
|
|
||||||
|
### 1.1 为什么要做
|
||||||
|
系统存在“功能经常回归”的问题,核心症状是跨层链路被重构破坏而未被及时发现,例如:
|
||||||
|
- 编辑角色/场景后未正确持久化。
|
||||||
|
- 上传参考图成功但生成阶段未正确使用参考图参数。
|
||||||
|
- 路由参数、任务 payload、worker 决策之间发生字段漂移。
|
||||||
|
- 任务状态和前端感知状态(target state/SSE)出现不一致。
|
||||||
|
|
||||||
|
### 1.2 目标定义
|
||||||
|
建立覆盖全系统的、可持续维护的自动化测试体系,确保:
|
||||||
|
- 所有关键功能都有自动化回归防线。
|
||||||
|
- 所有任务链路变更都会被测试阻断。
|
||||||
|
- 每个 PR 都执行全量门禁(已确定策略)。
|
||||||
|
|
||||||
|
### 1.3 当前上下文快照(仓库事实)
|
||||||
|
- API 路由总数: 117
|
||||||
|
- maybeSubmitLLMTask 路由: 22
|
||||||
|
- 直接 submitTask 路由: 16
|
||||||
|
- TASK_TYPE 数量: 37
|
||||||
|
- worker handlers 数量: 43
|
||||||
|
- 现有测试主要在 billing 域:
|
||||||
|
- unit: 18
|
||||||
|
- integration: 5
|
||||||
|
- concurrency: 1
|
||||||
|
|
||||||
|
### 1.4 修改前 vs 修改后(预期差异)
|
||||||
|
修改前:
|
||||||
|
- 测试集中于 billing,系统级回归无法被稳定阻断。
|
||||||
|
- 缺少全域 route 契约覆盖与任务类型覆盖矩阵。
|
||||||
|
- 缺少 route -> queue -> worker 的全链路契约测试。
|
||||||
|
|
||||||
|
修改后:
|
||||||
|
- 建立“契约驱动沙漏模型”全系统测试架构。
|
||||||
|
- 建立 route/task-type/requirement 覆盖矩阵与守卫脚本。
|
||||||
|
- 每次 PR 全量执行并门禁。
|
||||||
|
- 外部 API 统一 fake,避免高成本与不稳定性。
|
||||||
|
|
||||||
|
### 1.5 规模预估(用于排期)
|
||||||
|
- 预计新增文件: 55-80
|
||||||
|
- 预计修改文件: 20-35
|
||||||
|
- 预计新增代码: 8,000-14,000 行(以测试与测试基建为主)
|
||||||
|
- 预计总阶段: 8 阶段
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2: 阶段+具体代码修改地方以及需要修改的内容
|
||||||
|
|
||||||
|
### 2.0 状态图例
|
||||||
|
🟩✅ 已完成
|
||||||
|
🟦🔄 正在执行
|
||||||
|
🟨⏸ 待执行
|
||||||
|
🟥⚠️ 问题/阻塞
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 阶段1: 基线收敛与测试基建
|
||||||
|
|
||||||
|
🟩✅ Phase 1.1: 完成仓库现状盘点(route/taskType/worker 数量与入口路径)。
|
||||||
|
🟩✅ Phase 1.2: 完成测试策略决策锁定(全域门禁 + 全 fake + 每次 PR 全量)。
|
||||||
|
🟩✅ Phase 1.3: 建立主计划文档并作为唯一执行入口。
|
||||||
|
🟩✅ Phase 1.4: 扩展 tests/helpers/db-reset.ts 为 resetSystemState(),覆盖任务域+资产域+novel-promotion 域。
|
||||||
|
🟩✅ Phase 1.5: 新增 tests/helpers/request.ts(统一 NextRequest 构造)。
|
||||||
|
🟩✅ Phase 1.6: 新增 tests/helpers/auth.ts(mock requireUserAuth/requireProjectAuth/requireProjectAuthLight)。
|
||||||
|
🟩✅ Phase 1.7: 新增 tests/helpers/fixtures.ts(用户、项目、角色、场景、分镜、任务测试数据工厂)。
|
||||||
|
🟩✅ Phase 1.8: 当前 global-setup/global-teardown 仅围绕 BILLING_TEST_BOOTSTRAP,需升级为 system test bootstrap 约定。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 阶段2: 覆盖矩阵与守卫(防止漏测)
|
||||||
|
|
||||||
|
🟩✅ Phase 2.1: 新增 tests/contracts/route-catalog.ts,登记 117 个 route。
|
||||||
|
🟩✅ Phase 2.2: 新增 tests/contracts/task-type-catalog.ts,登记 37 个 TASK_TYPE。
|
||||||
|
🟩✅ Phase 2.3: 新增 tests/contracts/requirements-matrix.ts,建立需求 -> 测试用例映射。
|
||||||
|
🟩✅ Phase 2.4: 新增 scripts/guards/test-route-coverage-guard.mjs,强制 route 必有契约测试登记。
|
||||||
|
🟩✅ Phase 2.5: 新增 scripts/guards/test-tasktype-coverage-guard.mjs,强制 TASK_TYPE 必有测试映射。
|
||||||
|
🟩✅ Phase 2.6: 在 package.json 增加 check:test-coverage-guards 并纳入 test:pr。
|
||||||
|
🟥⚠️ Phase 2.7: 若 route 变化频繁,catalog 维护成本会上升,需要自动生成校验脚本降低维护负担。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 阶段3: L1 纯单元测试(高频回归逻辑锁定)
|
||||||
|
|
||||||
|
🟩✅ Phase 3.1: 新增 tests/unit/helpers/route-task-helpers.test.ts。
|
||||||
|
修改点: src/lib/llm-observe/route-task.ts
|
||||||
|
覆盖点: parseSyncFlag / shouldRunSyncTask / resolveDisplayMode / resolvePositiveInteger。
|
||||||
|
|
||||||
|
🟩✅ Phase 3.2: 新增 tests/unit/helpers/task-submitter-helpers.test.ts。
|
||||||
|
修改点: src/lib/task/submitter.ts
|
||||||
|
覆盖点: normalizeTaskPayload 的 flowId/flowStageIndex/flowStageTotal/meta 回退逻辑。
|
||||||
|
|
||||||
|
🟩✅ Phase 3.3: 新增 tests/unit/helpers/reference-to-character-helpers.test.ts。
|
||||||
|
修改点: src/lib/workers/handlers/reference-to-character-helpers.ts
|
||||||
|
覆盖点: parseReferenceImages / readString / readBoolean / 上限截断与空值过滤。
|
||||||
|
|
||||||
|
🟩✅ Phase 3.4: 新增 tests/unit/helpers/task-state-service.test.ts。
|
||||||
|
修改点: src/lib/task/state-service.ts
|
||||||
|
覆盖点: phase 决策、intent 归一化、错误归一化、progress 边界。
|
||||||
|
|
||||||
|
🟩✅ Phase 3.5: 需要确保不使用 any,必要时先补充内部类型导出。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 阶段4: L2 API 契约集成测试(全系统主防线之一)
|
||||||
|
|
||||||
|
🟩✅ Phase 4.1: 新增 tests/integration/api/helpers/call-route.ts。
|
||||||
|
目标: 统一 route 调用入口,减少重复模板代码。
|
||||||
|
|
||||||
|
🟩✅ Phase 4.2: 新增 tests/integration/api/contract/llm-observe-routes.test.ts。
|
||||||
|
覆盖范围: 22 个 maybeSubmitLLMTask 路由。
|
||||||
|
共同断言: 未登录/越权/参数错误/成功返回 taskId + async。
|
||||||
|
|
||||||
|
🟩✅ Phase 4.3: 新增 tests/integration/api/contract/direct-submit-routes.test.ts。
|
||||||
|
覆盖范围: 16 个直接 submitTask 路由。
|
||||||
|
共同断言: payload 入队契约、billing/locale/flow meta 关键字段存在。
|
||||||
|
|
||||||
|
🟩✅ Phase 4.4: 新增 tests/integration/api/contract/crud-routes.test.ts。
|
||||||
|
覆盖范围: asset-hub + novel-promotion CRUD 路由。
|
||||||
|
共同断言: DB 真值变化、字段映射不漂移、权限拦截。
|
||||||
|
|
||||||
|
🟩✅ Phase 4.6: 新增 tests/integration/api/contract/task-infra-routes.test.ts。
|
||||||
|
覆盖范围: /api/tasks, /api/tasks/[taskId], /api/tasks/dismiss, /api/task-target-states, /api/sse。
|
||||||
|
共同断言: 状态读取、取消、dismiss、target-state 结果结构。
|
||||||
|
|
||||||
|
🟥⚠️ Phase 4.7: 117 route 全覆盖耗时高,若单进程过慢需按组拆分命令并并行 CI job(不降覆盖)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 阶段5: L3 Worker 决策单元测试(全系统主防线之二)
|
||||||
|
|
||||||
|
🟩✅ Phase 5.1: 新增 tests/unit/worker/reference-to-character.test.ts。
|
||||||
|
覆盖: extractOnly / customDescription / useReferenceImages / backgroundJob 分支。
|
||||||
|
|
||||||
|
🟩✅ Phase 5.2: 新增 tests/unit/worker/image-task-handlers-core.test.ts。
|
||||||
|
覆盖: referenceImages 注入、resolution/aspectRatio 选择、目标实体分支(character/location/storyboard)。
|
||||||
|
|
||||||
|
🟩✅ Phase 5.3: 新增 tests/unit/worker/script-to-storyboard.test.ts。
|
||||||
|
覆盖: step orchestration、JSON parse 失败路径、voice line 匹配合法性校验。
|
||||||
|
|
||||||
|
🟩✅ Phase 5.4: 新增 tests/unit/worker/episode-split.test.ts。
|
||||||
|
覆盖: 分集数量边界、错误输入显式失败、输出结构一致性。
|
||||||
|
|
||||||
|
🟩✅ Phase 5.5: 新增 tests/unit/worker/video-worker.test.ts 与 voice-worker.test.ts。
|
||||||
|
覆盖: 必填 payload 校验、外部轮询超时、持久化字段更新。
|
||||||
|
|
||||||
|
🟥⚠️ Phase 5.6: 若 mock 粒度过粗会掩盖问题,必须在断言中校验“被调用参数内容”而非仅校验调用次数。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 阶段6: L4 全链路契约测试(route -> queue -> worker)
|
||||||
|
|
||||||
|
🟩✅ Phase 6.1: 新增 tests/integration/chain/text.chain.test.ts。
|
||||||
|
场景: ai-create-character、reference-to-character 全链路。
|
||||||
|
|
||||||
|
🟩✅ Phase 6.2: 新增 tests/integration/chain/image.chain.test.ts。
|
||||||
|
场景: generate-image、modify-image 全链路。
|
||||||
|
|
||||||
|
🟩✅ Phase 6.3: 新增 tests/integration/chain/video.chain.test.ts。
|
||||||
|
场景: generate-video、lip-sync 全链路。
|
||||||
|
|
||||||
|
🟩✅ Phase 6.4: 新增 tests/integration/chain/voice.chain.test.ts。
|
||||||
|
场景: voice-design、voice-generate 全链路。
|
||||||
|
|
||||||
|
🟩✅ Phase 6.5: 新增 tests/helpers/fakes/llm.ts、tests/helpers/fakes/media.ts、tests/helpers/fakes/providers.ts。
|
||||||
|
要求: 外部调用全部 fake,禁止真实外网消耗。
|
||||||
|
|
||||||
|
🟩✅ Phase 6.6: 需要在测试环境加“网络闸门”,防止误打真实外部 API。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 阶段7: 前端状态回归测试(轻量,不做重 E2E)
|
||||||
|
|
||||||
|
🟩✅ Phase 7.1: 扩展 tests/unit/optimistic/asset-hub-mutations.test.ts。
|
||||||
|
覆盖: 并发操作回滚冲突、缓存一致性。
|
||||||
|
|
||||||
|
🟩✅ Phase 7.2: 新增 tests/unit/optimistic/task-target-state-map.test.ts。
|
||||||
|
覆盖: queued/processing/completed/failed 对 UI 状态映射。
|
||||||
|
|
||||||
|
🟩✅ Phase 7.3: 新增 tests/unit/optimistic/sse-invalidation.test.ts。
|
||||||
|
覆盖: 仅终态触发 target-state 刷新,不允许轮询回退。
|
||||||
|
|
||||||
|
🟥⚠️ Phase 7.4: 不引入高成本浏览器 E2E,避免与“全 fake、低成本”策略冲突。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 阶段8: CI 门禁与回归收口
|
||||||
|
|
||||||
|
🟩✅ Phase 8.1: 更新 package.json,新增命令。
|
||||||
|
建议命令:
|
||||||
|
- test:guards
|
||||||
|
- test:unit:all
|
||||||
|
- test:integration:api
|
||||||
|
- test:integration:chain
|
||||||
|
- test:pr
|
||||||
|
- test:regression
|
||||||
|
|
||||||
|
🟩✅ Phase 8.2: 调整 CI,每次 PR 执行 test:pr(全量门禁)。
|
||||||
|
🟩✅ Phase 8.3: 回归失败输出标准化(失败文件、失败断言、首次引入 commit)。
|
||||||
|
🟩✅ Phase 8.4: 设置完成判定条件,满足后冻结基线。
|
||||||
|
|
||||||
|
🟥⚠️ Phase 8.5: 全量 PR 门禁会拉长反馈时间,需要预留 CI 资源并做缓存优化。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 执行日志(每次执行后必须追加)
|
||||||
|
格式:
|
||||||
|
- [YYYY-MM-DD HH:mm] 状态变更: <任务ID> <旧状态> -> <新状态>
|
||||||
|
- [YYYY-MM-DD HH:mm] 修改文件: <绝对路径列表>
|
||||||
|
- [YYYY-MM-DD HH:mm] 运行命令: <命令>
|
||||||
|
- [YYYY-MM-DD HH:mm] 结果: <通过/失败 + 摘要>
|
||||||
|
- [YYYY-MM-DD HH:mm] 问题: <若有>
|
||||||
|
|
||||||
|
- [2026-02-25 10:00] 状态变更: Phase 1.3/1.4/1.5/1.6/1.7 🔄/⏸ -> ✅
|
||||||
|
- [2026-02-25 10:00] 修改文件: /Users/earth/Desktop/waoowaoo/tests/helpers/db-reset.ts, /Users/earth/Desktop/waoowaoo/tests/helpers/request.ts, /Users/earth/Desktop/waoowaoo/tests/helpers/auth.ts, /Users/earth/Desktop/waoowaoo/tests/helpers/fixtures.ts
|
||||||
|
- [2026-02-25 10:00] 运行命令: git show/git ls-tree(只读盘点)
|
||||||
|
- [2026-02-25 10:00] 结果: 已完成测试基础 helper 与系统重置扩展
|
||||||
|
- [2026-02-25 10:00] 问题: Phase 1.8 仍需推进全系统 bootstrap 统一
|
||||||
|
|
||||||
|
- [2026-02-25 10:05] 状态变更: Phase 2.1/2.2/2.3/2.4/2.5/2.6 ⏸ -> ✅
|
||||||
|
- [2026-02-25 10:05] 修改文件: /Users/earth/Desktop/waoowaoo/tests/contracts/route-catalog.ts, /Users/earth/Desktop/waoowaoo/tests/contracts/task-type-catalog.ts, /Users/earth/Desktop/waoowaoo/tests/contracts/requirements-matrix.ts, /Users/earth/Desktop/waoowaoo/scripts/guards/test-route-coverage-guard.mjs, /Users/earth/Desktop/waoowaoo/scripts/guards/test-tasktype-coverage-guard.mjs, /Users/earth/Desktop/waoowaoo/package.json
|
||||||
|
- [2026-02-25 10:05] 运行命令: git show/git ls-tree(只读盘点)
|
||||||
|
- [2026-02-25 10:05] 结果: 覆盖矩阵与守卫脚本落地,新增 test:pr/test:regression 入口
|
||||||
|
- [2026-02-25 10:05] 问题: 需在 CI workflow 文件接入 test:pr(Phase 8.2)
|
||||||
|
|
||||||
|
- [2026-02-25 10:10] 状态变更: Phase 3.1/3.2/3.3/3.4/3.5 ⏸/⚠️ -> ✅
|
||||||
|
- [2026-02-25 10:10] 修改文件: /Users/earth/Desktop/waoowaoo/src/lib/llm-observe/route-task.ts, /Users/earth/Desktop/waoowaoo/src/lib/task/submitter.ts, /Users/earth/Desktop/waoowaoo/src/lib/task/state-service.ts, /Users/earth/Desktop/waoowaoo/tests/unit/helpers/*.test.ts
|
||||||
|
- [2026-02-25 10:10] 运行命令: 待执行测试命令验证
|
||||||
|
- [2026-02-25 10:10] 结果: 关键 pure helper 单测已落地,核心函数可测性增强
|
||||||
|
- [2026-02-25 10:10] 问题: 无
|
||||||
|
|
||||||
|
- [2026-02-25 10:15] 状态变更: Phase 4.1/4.2/4.3/4.4/4.5/4.6 ⏸ -> ✅
|
||||||
|
- [2026-02-25 10:15] 修改文件: /Users/earth/Desktop/waoowaoo/tests/integration/api/helpers/call-route.ts, /Users/earth/Desktop/waoowaoo/tests/integration/api/contract/*.test.ts, /Users/earth/Desktop/waoowaoo/tests/integration/api/specific/*.test.ts
|
||||||
|
- [2026-02-25 10:15] 运行命令: 待执行测试命令验证
|
||||||
|
- [2026-02-25 10:15] 结果: API 契约分组测试模板已落地并接入 catalog
|
||||||
|
- [2026-02-25 10:15] 问题: 动态 DB 真值契约仍需持续加深
|
||||||
|
|
||||||
|
- [2026-02-25 10:20] 状态变更: Phase 5.1/5.2/5.3/5.4/5.5, Phase 6.1/6.2/6.3/6.4/6.5/6.6, Phase 7.2/7.3, Phase 8.1 ⏸ -> ✅
|
||||||
|
- [2026-02-25 10:20] 修改文件: /Users/earth/Desktop/waoowaoo/tests/unit/worker/*.test.ts, /Users/earth/Desktop/waoowaoo/tests/integration/chain/*.test.ts, /Users/earth/Desktop/waoowaoo/tests/helpers/fakes/*.ts, /Users/earth/Desktop/waoowaoo/tests/setup/env.ts, /Users/earth/Desktop/waoowaoo/tests/unit/optimistic/task-target-state-map.test.ts, /Users/earth/Desktop/waoowaoo/tests/unit/optimistic/sse-invalidation.test.ts
|
||||||
|
- [2026-02-25 10:20] 运行命令: 待执行测试命令验证
|
||||||
|
- [2026-02-25 10:20] 结果: Worker/Chain/Optimistic 第一批回归防线与网络闸门已落地
|
||||||
|
- [2026-02-25 10:20] 问题: Phase 7.1 与 Phase 8.2/8.3/8.4 仍需推进
|
||||||
|
|
||||||
|
- [2026-02-25 10:25] 状态变更: Phase 1.8 ⚠️ -> ✅, Phase 8.2 ⏸ -> ✅
|
||||||
|
- [2026-02-25 10:25] 修改文件: /Users/earth/Desktop/waoowaoo/tests/setup/global-setup.ts, /Users/earth/Desktop/waoowaoo/tests/setup/global-teardown.ts, /Users/earth/Desktop/waoowaoo/.github/workflows/test-regression-pr.yml
|
||||||
|
- [2026-02-25 10:25] 运行命令: npm run test:pr
|
||||||
|
- [2026-02-25 10:25] 结果: test:guards/test:unit:all/test:billing:integration/test:integration:api/test:integration:chain 全部通过
|
||||||
|
- [2026-02-25 10:25] 问题: 单测过程仍会出现 Redis 连接拒绝日志噪音(不影响通过)
|
||||||
|
|
||||||
|
- [2026-02-25 10:30] 状态变更: Phase 8.3/8.4 ⏸ -> ✅
|
||||||
|
- [2026-02-25 10:30] 修改文件: /Users/earth/Desktop/waoowaoo/scripts/test-regression-runner.sh, /Users/earth/Desktop/waoowaoo/package.json
|
||||||
|
- [2026-02-25 10:30] 运行命令: npm run test:guards
|
||||||
|
- [2026-02-25 10:30] 结果: 回归失败统一诊断脚本已接入 test:pr,guard 通过
|
||||||
|
- [2026-02-25 10:30] 问题: 无
|
||||||
|
|
||||||
|
- [2026-02-25 10:40] 状态变更: 回归门禁验收执行
|
||||||
|
- [2026-02-25 10:40] 修改文件: /Users/earth/Desktop/waoowaoo/SYSTEM_REGRESSION_COVERAGE_MASTER_PLAN.md
|
||||||
|
- [2026-02-25 10:40] 运行命令: npm run test:pr
|
||||||
|
- [2026-02-25 10:40] 结果: 全链路门禁通过(test:guards、test:unit:all、test:billing:integration、test:integration:api、test:integration:chain)
|
||||||
|
- [2026-02-25 10:40] 问题: 测试日志中仍有 Redis 连接拒绝噪音(不影响通过)
|
||||||
|
|
||||||
|
- [2026-02-25 22:00] 状态变更: Phase 5.1 结果断言增强 + 回归缺口修复
|
||||||
|
- [2026-02-25 22:00] 修改文件: /Users/earth/Desktop/waoowaoo/src/lib/workers/handlers/reference-to-character.ts, /Users/earth/Desktop/waoowaoo/tests/unit/worker/reference-to-character.test.ts, /Users/earth/Desktop/waoowaoo/tests/contracts/requirements-matrix.ts, /Users/earth/Desktop/waoowaoo/tests/contracts/requirements-matrix.test.ts, /Users/earth/Desktop/waoowaoo/SYSTEM_REGRESSION_COVERAGE_MASTER_PLAN.md
|
||||||
|
- [2026-02-25 22:00] 运行命令: BILLING_TEST_BOOTSTRAP=0 npx vitest run tests/unit/worker/reference-to-character.test.ts tests/unit/worker/asset-hub-image-suffix.test.ts tests/unit/worker/modify-image-reference-description.test.ts tests/integration/api/specific/characters-post-reference-forwarding.test.ts tests/contracts/requirements-matrix.test.ts && BILLING_TEST_BOOTSTRAP=0 npx vitest run tests/unit/worker
|
||||||
|
- [2026-02-25 22:00] 结果: 关键回归链路测试通过;新增 requirements matrix 完整性断言可阻断不存在测试文件引用;worker 全套通过
|
||||||
|
- [2026-02-25 22:00] 问题: worker 单测日志仍有 Redis ECONNREFUSED 噪音(断言通过,不影响结果)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4: 验证策略
|
||||||
|
|
||||||
|
### 4.1 可量化验收指标(必须全部达成)
|
||||||
|
1. route 契约覆盖率 = 117/117(100%)。
|
||||||
|
2. TASK_TYPE 覆盖率 = 37/37(100%)。
|
||||||
|
3. 4 类队列链路测试均存在且通过(text/image/video/voice)。
|
||||||
|
4. 每个 PR 全量门禁执行并通过。
|
||||||
|
5. 无真实外网调用(测试日志与网络闸门双重确认)。
|
||||||
|
6. 关键高频回归场景(编辑类、参考图类、任务状态类)均有自动化用例。
|
||||||
|
7. 新增/修改 route 或 TASK_TYPE 时,若未补测试,guard 必须失败。
|
||||||
|
|
||||||
|
### 4.2 命令级验证
|
||||||
|
- `npm run test:guards`
|
||||||
|
- `cross-env BILLING_TEST_BOOTSTRAP=0 vitest run tests/unit`
|
||||||
|
- `cross-env BILLING_TEST_BOOTSTRAP=1 vitest run tests/integration/api`
|
||||||
|
- `cross-env BILLING_TEST_BOOTSTRAP=1 vitest run tests/integration/chain`
|
||||||
|
- `npm run test:pr`
|
||||||
|
|
||||||
|
### 4.3 结果记录
|
||||||
|
每轮执行后在执行日志追加:
|
||||||
|
- 总用例数
|
||||||
|
- 失败数
|
||||||
|
- 失败模块
|
||||||
|
- 修复后重跑结果
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5: 备注(可选但建议)
|
||||||
|
|
||||||
|
1. 本计划不依赖历史对话,可由任意新模型直接接手。
|
||||||
|
2. 若用户策略改变(例如允许少量 live canary),必须先更新本文件“策略锁定项”,再动代码。
|
||||||
|
3. 若发现范围蔓延,优先维护“矩阵完整性”而不是临时加测。
|
||||||
|
4. 任何“为了通过测试而加回退”的方案一律禁止。
|
||||||
|
5. 所有测试代码保持强类型,不允许 any。
|
||||||
|
你必须按照目前的md文件详细执行我们的代码修改计划,且必须时刻关注,维护本次md文档,确保该文档能始终保持最新,和我们代码库保持完全一致,除非用户要求,否则默认禁止打补丁,禁止兼容层,我们需要的是简洁干净可扩展的系统,我们这个系统目前没有人用,可以一次性全量,彻底,不留遗留的修改,并且需要一次性完成所有,禁止停下,禁止自己停止任务,一次性完成所有内容。
|
||||||
|
|
||||||
|
# 全系统回归测试执行主计划
|
||||||
|
版本: v1.0
|
||||||
|
仓库: /Users/earth/Desktop/waoowaoo
|
||||||
|
最后更新: 2026-02-25
|
||||||
|
责任模式: 单一主计划文档驱动(本文件是唯一事实来源)
|
||||||
|
|
||||||
|
## 0. 文档维护协议(强制)
|
||||||
|
1. 每次开始任何代码工作前,必须先更新“2:阶段+具体任务”的状态。
|
||||||
|
2. 每次完成一个任务,必须同步更新为 ✅ 并记录变更文件与命令。
|
||||||
|
3. 任何发现的阻塞必须写入 ⚠️ 问题区,不允许口头跳过。
|
||||||
|
4. 不允许引入兼容层、双轨逻辑、静默回退、假成功。
|
||||||
|
5. 新增功能或改动若未补测试,禁止标记为完成。
|
||||||
|
6. 新模型/新窗口接手时,只允许依据本文件继续执行,不依赖历史聊天上下文。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1: 项目目标
|
||||||
|
|
||||||
|
### 1.1 为什么要做
|
||||||
|
系统存在“功能经常回归”的问题,核心症状是跨层链路被重构破坏而未被及时发现,例如:
|
||||||
|
- 编辑角色/场景后未正确持久化。
|
||||||
|
- 上传参考图成功但生成阶段未正确使用参考图参数。
|
||||||
|
- 路由参数、任务 payload、worker 决策之间发生字段漂移。
|
||||||
|
- 任务状态和前端感知状态(target state/SSE)出现不一致。
|
||||||
|
|
||||||
|
### 1.2 目标定义
|
||||||
|
建立覆盖全系统的、可持续维护的自动化测试体系,确保:
|
||||||
|
- 所有关键功能都有自动化回归防线。
|
||||||
|
- 所有任务链路变更都会被测试阻断。
|
||||||
|
- 每个 PR 都执行全量门禁(已确定策略)。
|
||||||
|
|
||||||
|
### 1.3 当前上下文快照(仓库事实)
|
||||||
|
- API 路由总数: 117
|
||||||
|
- maybeSubmitLLMTask 路由: 22
|
||||||
|
- 直接 submitTask 路由: 16
|
||||||
|
- TASK_TYPE 数量: 49
|
||||||
|
- worker handlers 数量: 43
|
||||||
|
- 现有测试主要在 billing 域:
|
||||||
|
- unit: 18
|
||||||
|
- integration: 5
|
||||||
|
- concurrency: 1
|
||||||
|
|
||||||
|
### 1.4 修改前 vs 修改后(预期差异)
|
||||||
|
修改前:
|
||||||
|
- 测试集中于 billing,系统级回归无法被稳定阻断。
|
||||||
|
- 缺少全域 route 契约覆盖与任务类型覆盖矩阵。
|
||||||
|
- 缺少 route -> queue -> worker 的全链路契约测试。
|
||||||
|
|
||||||
|
修改后:
|
||||||
|
- 建立“契约驱动沙漏模型”全系统测试架构。
|
||||||
|
- 建立 route/task-type/requirement 覆盖矩阵与守卫脚本。
|
||||||
|
- 每次 PR 全量执行并门禁。
|
||||||
|
- 外部 API 统一 fake,避免高成本与不稳定性。
|
||||||
|
|
||||||
|
### 1.5 规模预估(用于排期)
|
||||||
|
- 预计新增文件: 55-80
|
||||||
|
- 预计修改文件: 20-35
|
||||||
|
- 预计新增代码: 8,000-14,000 行(以测试与测试基建为主)
|
||||||
|
- 预计总阶段: 8 阶段
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2: 阶段+具体代码修改地方以及需要修改的内容
|
||||||
|
|
||||||
|
### 2.0 状态图例
|
||||||
|
🟩✅ 已完成
|
||||||
|
🟦🔄 正在执行
|
||||||
|
🟨⏸ 待执行
|
||||||
|
🟥⚠️ 问题/阻塞
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 阶段1: 基线收敛与测试基建
|
||||||
|
|
||||||
|
🟩✅ Phase 1.1: 完成仓库现状盘点(route/taskType/worker 数量与入口路径)。
|
||||||
|
🟩✅ Phase 1.2: 完成测试策略决策锁定(全域门禁 + 全 fake + 每次 PR 全量)。
|
||||||
|
🟦🔄 Phase 1.3: 建立主计划文档并作为唯一执行入口。
|
||||||
|
🟨⏸ Phase 1.4: 扩展 tests/helpers/db-reset.ts 为 resetSystemState(),覆盖任务域+资产域+novel-promotion 域。
|
||||||
|
🟨⏸ Phase 1.5: 新增 tests/helpers/request.ts(统一 NextRequest 构造)。
|
||||||
|
🟨⏸ Phase 1.6: 新增 tests/helpers/auth.ts(mock requireUserAuth/requireProjectAuth/requireProjectAuthLight)。
|
||||||
|
🟨⏸ Phase 1.7: 新增 tests/helpers/fixtures.ts(用户、项目、角色、场景、分镜、任务测试数据工厂)。
|
||||||
|
🟥⚠️ Phase 1.8: 当前 global-setup/global-teardown 仅围绕 BILLING_TEST_BOOTSTRAP,需升级为 system test bootstrap 约定。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 阶段2: 覆盖矩阵与守卫(防止漏测)
|
||||||
|
|
||||||
|
🟨⏸ Phase 2.1: 新增 tests/contracts/route-catalog.ts,登记 117 个 route。
|
||||||
|
🟨⏸ Phase 2.2: 新增 tests/contracts/task-type-catalog.ts,登记 49 个 TASK_TYPE。
|
||||||
|
🟨⏸ Phase 2.3: 新增 tests/contracts/requirements-matrix.ts,建立需求 -> 测试用例映射。
|
||||||
|
🟨⏸ Phase 2.4: 新增 scripts/guards/test-route-coverage-guard.mjs,强制 route 必有契约测试登记。
|
||||||
|
🟨⏸ Phase 2.5: 新增 scripts/guards/test-tasktype-coverage-guard.mjs,强制 TASK_TYPE 必有测试映射。
|
||||||
|
🟨⏸ Phase 2.6: 在 package.json 增加 check:test-coverage-guards 并纳入 test:pr。
|
||||||
|
🟥⚠️ Phase 2.7: 若 route 变化频繁,catalog 维护成本会上升,需要自动生成校验脚本降低维护负担。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 阶段3: L1 纯单元测试(高频回归逻辑锁定)
|
||||||
|
|
||||||
|
🟨⏸ Phase 3.1: 新增 tests/unit/helpers/route-task-helpers.test.ts。
|
||||||
|
修改点: src/lib/llm-observe/route-task.ts
|
||||||
|
覆盖点: parseSyncFlag / shouldRunSyncTask / resolveDisplayMode / resolvePositiveInteger。
|
||||||
|
|
||||||
|
🟨⏸ Phase 3.2: 新增 tests/unit/helpers/task-submitter-helpers.test.ts。
|
||||||
|
修改点: src/lib/task/submitter.ts
|
||||||
|
覆盖点: normalizeTaskPayload 的 flowId/flowStageIndex/flowStageTotal/meta 回退逻辑。
|
||||||
|
|
||||||
|
🟨⏸ Phase 3.3: 新增 tests/unit/helpers/reference-to-character-helpers.test.ts。
|
||||||
|
修改点: src/lib/workers/handlers/reference-to-character-helpers.ts
|
||||||
|
覆盖点: parseReferenceImages / readString / readBoolean / 上限截断与空值过滤。
|
||||||
|
|
||||||
|
🟨⏸ Phase 3.4: 新增 tests/unit/helpers/task-state-service.test.ts。
|
||||||
|
修改点: src/lib/task/state-service.ts
|
||||||
|
覆盖点: phase 决策、intent 归一化、错误归一化、progress 边界。
|
||||||
|
|
||||||
|
🟥⚠️ Phase 3.5: 需要确保不使用 any,必要时先补充内部类型导出。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 阶段4: L2 API 契约集成测试(全系统主防线之一)
|
||||||
|
|
||||||
|
🟨⏸ Phase 4.1: 新增 tests/integration/api/helpers/call-route.ts。
|
||||||
|
目标: 统一 route 调用入口,减少重复模板代码。
|
||||||
|
|
||||||
|
🟨⏸ Phase 4.2: 新增 tests/integration/api/contract/llm-observe-routes.test.ts。
|
||||||
|
覆盖范围: 22 个 maybeSubmitLLMTask 路由。
|
||||||
|
共同断言: 未登录/越权/参数错误/成功返回 taskId + async。
|
||||||
|
|
||||||
|
🟨⏸ Phase 4.3: 新增 tests/integration/api/contract/direct-submit-routes.test.ts。
|
||||||
|
覆盖范围: 16 个直接 submitTask 路由。
|
||||||
|
共同断言: payload 入队契约、billing/locale/flow meta 关键字段存在。
|
||||||
|
|
||||||
|
🟨⏸ Phase 4.4: 新增 tests/integration/api/contract/crud-asset-hub-routes.test.ts。
|
||||||
|
覆盖范围: asset-hub CRUD 路由。
|
||||||
|
共同断言: DB 真值变化、字段映射不漂移、权限拦截。
|
||||||
|
|
||||||
|
🟨⏸ Phase 4.5: 新增 tests/integration/api/contract/crud-novel-promotion-routes.test.ts。
|
||||||
|
覆盖范围: novel-promotion 非任务化 CRUD 路由。
|
||||||
|
共同断言: project 权限、数据一致性、错误码一致性。
|
||||||
|
|
||||||
|
🟨⏸ Phase 4.6: 新增 tests/integration/api/contract/task-infra-routes.test.ts。
|
||||||
|
覆盖范围: /api/tasks, /api/tasks/[taskId], /api/tasks/dismiss, /api/task-target-states, /api/sse。
|
||||||
|
共同断言: 状态读取、取消、dismiss、target-state 结果结构。
|
||||||
|
|
||||||
|
🟥⚠️ Phase 4.7: 117 route 全覆盖耗时高,若单进程过慢需按组拆分命令并并行 CI job(不降覆盖)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 阶段5: L3 Worker 决策单元测试(全系统主防线之二)
|
||||||
|
|
||||||
|
🟨⏸ Phase 5.1: 新增 tests/unit/worker/reference-to-character.test.ts。
|
||||||
|
覆盖: extractOnly / customDescription / useReferenceImages / backgroundJob 分支。
|
||||||
|
|
||||||
|
🟨⏸ Phase 5.2: 新增 tests/unit/worker/image-task-handlers-core.test.ts。
|
||||||
|
覆盖: referenceImages 注入、resolution/aspectRatio 选择、目标实体分支(character/location/storyboard)。
|
||||||
|
|
||||||
|
🟨⏸ Phase 5.3: 新增 tests/unit/worker/script-to-storyboard.test.ts。
|
||||||
|
覆盖: step orchestration、JSON parse 失败路径、voice line 匹配合法性校验。
|
||||||
|
|
||||||
|
🟨⏸ Phase 5.4: 新增 tests/unit/worker/episode-split.test.ts。
|
||||||
|
覆盖: 分集数量边界、错误输入显式失败、输出结构一致性。
|
||||||
|
|
||||||
|
🟨⏸ Phase 5.5: 新增 tests/unit/worker/video-worker.test.ts 与 voice-worker.test.ts。
|
||||||
|
覆盖: 必填 payload 校验、外部轮询超时、持久化字段更新。
|
||||||
|
|
||||||
|
🟥⚠️ Phase 5.6: 若 mock 粒度过粗会掩盖问题,必须在断言中校验“被调用参数内容”而非仅校验调用次数。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 阶段6: L4 全链路契约测试(route -> queue -> worker)
|
||||||
|
|
||||||
|
🟨⏸ Phase 6.1: 新增 tests/integration/chain/text.chain.test.ts。
|
||||||
|
场景: ai-create-character、reference-to-character 全链路。
|
||||||
|
|
||||||
|
🟨⏸ Phase 6.2: 新增 tests/integration/chain/image.chain.test.ts。
|
||||||
|
场景: generate-image、modify-image 全链路。
|
||||||
|
|
||||||
|
🟨⏸ Phase 6.3: 新增 tests/integration/chain/video.chain.test.ts。
|
||||||
|
场景: generate-video、lip-sync 全链路。
|
||||||
|
|
||||||
|
🟨⏸ Phase 6.4: 新增 tests/integration/chain/voice.chain.test.ts。
|
||||||
|
场景: voice-design、voice-generate 全链路。
|
||||||
|
|
||||||
|
🟨⏸ Phase 6.5: 新增 tests/helpers/fakes/llm.ts、tests/helpers/fakes/media.ts、tests/helpers/fakes/providers.ts。
|
||||||
|
要求: 外部调用全部 fake,禁止真实外网消耗。
|
||||||
|
|
||||||
|
🟥⚠️ Phase 6.6: 需要在测试环境加“网络闸门”,防止误打真实外部 API。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 阶段7: 前端状态回归测试(轻量,不做重 E2E)
|
||||||
|
|
||||||
|
🟨⏸ Phase 7.1: 扩展 tests/unit/optimistic/asset-hub-mutations.test.ts。
|
||||||
|
覆盖: 并发操作回滚冲突、缓存一致性。
|
||||||
|
|
||||||
|
🟨⏸ Phase 7.2: 新增 tests/unit/optimistic/task-target-state-map.test.ts。
|
||||||
|
覆盖: queued/processing/completed/failed 对 UI 状态映射。
|
||||||
|
|
||||||
|
🟨⏸ Phase 7.3: 新增 tests/unit/optimistic/sse-invalidation.test.ts。
|
||||||
|
覆盖: 仅终态触发 target-state 刷新,不允许轮询回退。
|
||||||
|
|
||||||
|
🟥⚠️ Phase 7.4: 不引入高成本浏览器 E2E,避免与“全 fake、低成本”策略冲突。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 阶段8: CI 门禁与回归收口
|
||||||
|
|
||||||
|
🟨⏸ Phase 8.1: 更新 package.json,新增命令。
|
||||||
|
建议命令:
|
||||||
|
- test:guards
|
||||||
|
- test:unit:all
|
||||||
|
- test:integration:api
|
||||||
|
- test:integration:chain
|
||||||
|
- test:pr
|
||||||
|
- test:regression
|
||||||
|
|
||||||
|
🟨⏸ Phase 8.2: 调整 CI,每次 PR 执行 test:pr(全量门禁)。
|
||||||
|
🟨⏸ Phase 8.3: 回归失败输出标准化(失败文件、失败断言、首次引入 commit)。
|
||||||
|
🟨⏸ Phase 8.4: 设置完成判定条件,满足后冻结基线。
|
||||||
|
|
||||||
|
🟥⚠️ Phase 8.5: 全量 PR 门禁会拉长反馈时间,需要预留 CI 资源并做缓存优化。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 执行日志(每次执行后必须追加)
|
||||||
|
格式:
|
||||||
|
- [YYYY-MM-DD HH:mm] 状态变更: <任务ID> <旧状态> -> <新状态>
|
||||||
|
- [YYYY-MM-DD HH:mm] 修改文件: <绝对路径列表>
|
||||||
|
- [YYYY-MM-DD HH:mm] 运行命令: <命令>
|
||||||
|
- [YYYY-MM-DD HH:mm] 结果: <通过/失败 + 摘要>
|
||||||
|
- [YYYY-MM-DD HH:mm] 问题: <若有>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4: 验证策略
|
||||||
|
|
||||||
|
### 4.1 可量化验收指标(必须全部达成)
|
||||||
|
1. route 契约覆盖率 = 117/117(100%)。
|
||||||
|
2. TASK_TYPE 覆盖率 = 49/49(100%)。
|
||||||
|
3. 4 类队列链路测试均存在且通过(text/image/video/voice)。
|
||||||
|
4. 每个 PR 全量门禁执行并通过。
|
||||||
|
5. 无真实外网调用(测试日志与网络闸门双重确认)。
|
||||||
|
6. 关键高频回归场景(编辑类、参考图类、任务状态类)均有自动化用例。
|
||||||
|
7. 新增/修改 route 或 TASK_TYPE 时,若未补测试,guard 必须失败。
|
||||||
|
|
||||||
|
### 4.2 命令级验证
|
||||||
|
- `npm run test:guards`
|
||||||
|
- `cross-env BILLING_TEST_BOOTSTRAP=0 vitest run tests/unit`
|
||||||
|
- `cross-env BILLING_TEST_BOOTSTRAP=1 vitest run tests/integration/api`
|
||||||
|
- `cross-env BILLING_TEST_BOOTSTRAP=1 vitest run tests/integration/chain`
|
||||||
|
- `npm run test:pr`
|
||||||
|
|
||||||
|
### 4.3 结果记录
|
||||||
|
每轮执行后在执行日志追加:
|
||||||
|
- 总用例数
|
||||||
|
- 失败数
|
||||||
|
- 失败模块
|
||||||
|
- 修复后重跑结果
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5: 备注(可选但建议)
|
||||||
|
|
||||||
|
1. 本计划不依赖历史对话,可由任意新模型直接接手。
|
||||||
|
2. 若用户策略改变(例如允许少量 live canary),必须先更新本文件“策略锁定项”,再动代码。
|
||||||
|
3. 若发现范围蔓延,优先维护“矩阵完整性”而不是临时加测。
|
||||||
|
4. 任何“为了通过测试而加回退”的方案一律禁止。
|
||||||
|
5. 所有测试代码保持强类型,不允许 any。
|
||||||
147
agent/testing.md
Normal file
147
agent/testing.md
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
# 测试编写详细规范
|
||||||
|
|
||||||
|
## 1. 何时必须写或更新测试
|
||||||
|
|
||||||
|
| 触发场景 | 要求 |
|
||||||
|
|---|---|
|
||||||
|
| 修改 worker handler 逻辑 | 必须有对应行为测试 |
|
||||||
|
| 修复 bug | 必须新增回归测试,`it()` 名称体现该 bug 场景 |
|
||||||
|
| 新增 API route 或 task type | 必须更新 `tests/contracts/` 矩阵 |
|
||||||
|
| 修改 prompt 后缀、referenceImages 注入、DB 写回字段 | 必须有行为断言覆盖 |
|
||||||
|
|
||||||
|
未通过 `npm run test:regression` 不得宣称功能完成。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 断言必须是行为级(检查具体值)
|
||||||
|
|
||||||
|
**正确写法**:
|
||||||
|
```ts
|
||||||
|
// 断言 DB 写入了具体字段值
|
||||||
|
const updateData = prismaMock.globalCharacterAppearance.update.mock.calls.at(-1)?.[0].data
|
||||||
|
expect(updateData.description).toBe('AI_EXTRACTED_DESCRIPTION')
|
||||||
|
|
||||||
|
// 断言生图函数收到了正确参数
|
||||||
|
const { prompt, options } = readGenerateCall(0)
|
||||||
|
expect(prompt).toContain(CHARACTER_PROMPT_SUFFIX)
|
||||||
|
expect(options.referenceImages).toEqual(['https://ref.example/a.png'])
|
||||||
|
|
||||||
|
// 断言返回值
|
||||||
|
expect(result).toEqual({ success: true, count: 2 })
|
||||||
|
```
|
||||||
|
|
||||||
|
**禁止写法**(不能作为唯一主断言):
|
||||||
|
```ts
|
||||||
|
expect(fn).toHaveBeenCalled() // 只知道"调用了",不知道"传了什么"
|
||||||
|
expect(fn).toHaveBeenCalledTimes(1) // 次数本身无业务意义时无效
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Mock 规范
|
||||||
|
|
||||||
|
**必须 Mock**:
|
||||||
|
- `prisma`(所有数据库操作)
|
||||||
|
- LLM / chatCompletionWithVision / generateImage
|
||||||
|
- COS / uploadToCOS / getSignedUrl
|
||||||
|
- 外部 HTTP(fetchWithTimeoutAndRetry 等)
|
||||||
|
|
||||||
|
**禁止 Mock**:
|
||||||
|
- 你要测试的业务逻辑函数本身
|
||||||
|
- 项目内部常量(如 `CHARACTER_PROMPT_SUFFIX`),直接 import 使用
|
||||||
|
|
||||||
|
**禁止"自给自答"**:
|
||||||
|
```ts
|
||||||
|
// 错误:mock 返回 X,马上断言 X,没有经过任何业务逻辑
|
||||||
|
mockLLM.mockReturnValue('result')
|
||||||
|
expect(await mockLLM()).toBe('result') // 废测试
|
||||||
|
|
||||||
|
// 正确:mock AI 返回 X,断言业务代码把 X 写进了数据库
|
||||||
|
llmMock.getCompletionContent.mockReturnValue('高挑女性')
|
||||||
|
await handleTask(job)
|
||||||
|
expect(prismaMock.update.mock.calls.at(-1)[0].data.description).toBe('高挑女性')
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 测试数据规范
|
||||||
|
|
||||||
|
- **影响分支的字段**须分开写 `it()`,例如:
|
||||||
|
- `有 extraImageUrls` 和 `无 extraImageUrls` 分别写一个用例
|
||||||
|
- `isBackgroundJob: true` 和 `false` 分别写
|
||||||
|
- **纯透传字段**(`taskId`、`userId` 等代码不处理)可用占位值 `'task-1'`
|
||||||
|
- **每个 `it()` 命名格式**:`[条件] -> [预期结果]`
|
||||||
|
|
||||||
|
**命名示例**:
|
||||||
|
```
|
||||||
|
有参考图 -> AI 分析结果写入 description
|
||||||
|
无参考图 -> 不触发 AI,description 不变
|
||||||
|
AI 调用失败 -> 主流程成功,description 不被污染
|
||||||
|
缺少必填参数 -> 抛出包含字段名的错误
|
||||||
|
批量确认 2 个角色 -> 逐个处理,count 返回 2
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 每个测试文件的结构
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// 1. vi.hoisted 定义所有 mock(必须在 import 之前)
|
||||||
|
const prismaMock = vi.hoisted(() => ({ ... }))
|
||||||
|
const llmMock = vi.hoisted(() => ({ ... }))
|
||||||
|
|
||||||
|
// 2. vi.mock 注册(在 import 之前)
|
||||||
|
vi.mock('@/lib/prisma', () => ({ prisma: prismaMock }))
|
||||||
|
vi.mock('@/lib/llm-client', () => llmMock)
|
||||||
|
|
||||||
|
// 3. import 真实业务代码(在 mock 注册之后)
|
||||||
|
import { handleXxxTask } from '@/lib/workers/handlers/xxx'
|
||||||
|
|
||||||
|
// 4. describe + beforeEach 重置 mock
|
||||||
|
describe('worker xxx behavior', () => {
|
||||||
|
beforeEach(() => { vi.clearAllMocks() })
|
||||||
|
|
||||||
|
it('[条件] -> [结果]', async () => {
|
||||||
|
// 准备:覆盖这次场景需要的特殊 mock 返回值
|
||||||
|
// 构造:buildJob(payload, taskType)
|
||||||
|
// 执行:await handleXxxTask(job)
|
||||||
|
// 断言:检查具体值
|
||||||
|
})
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 运行命令
|
||||||
|
|
||||||
|
| 场景 | 命令 |
|
||||||
|
|---|---|
|
||||||
|
| 改了 worker 逻辑 | `BILLING_TEST_BOOTSTRAP=0 npx vitest run tests/unit/worker` |
|
||||||
|
| 改了某个具体文件 | `BILLING_TEST_BOOTSTRAP=0 npx vitest run tests/unit/worker/xxx.test.ts` |
|
||||||
|
| 改了 API 路由 | `BILLING_TEST_BOOTSTRAP=0 npx vitest run tests/integration/api` |
|
||||||
|
| 改了 helpers / 常量 | `BILLING_TEST_BOOTSTRAP=0 npx vitest run tests/unit/helpers` |
|
||||||
|
| 提交前完整验证 | `npm run test:regression` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 目录说明
|
||||||
|
|
||||||
|
| 目录 | 用途 |
|
||||||
|
|---|---|
|
||||||
|
| `tests/unit/worker/` | worker handler 行为测试(主要回归防线) |
|
||||||
|
| `tests/unit/helpers/` | 纯函数 / 工具函数测试 |
|
||||||
|
| `tests/unit/optimistic/` | 前端状态 hook 行为测试 |
|
||||||
|
| `tests/integration/api/contract/` | API 路由契约(401/400/200 + payload 断言) |
|
||||||
|
| `tests/integration/chain/` | queue → worker → 结果完整链路 |
|
||||||
|
| `tests/contracts/` | 矩阵与守卫(route/tasktype/requirements) |
|
||||||
|
| `tests/helpers/fakes/` | 通用 mock 工具(llm、media、providers) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 验证测试有效性(防假绿灯)
|
||||||
|
|
||||||
|
写完测试后,用以下方式确认测试没有虚假通过:
|
||||||
|
|
||||||
|
1. 临时注释掉你刚写的业务逻辑,测试应该变红
|
||||||
|
2. 还原业务逻辑,测试应该变绿
|
||||||
|
3. 如果注释后测试还是绿,说明断言没有覆盖到真实业务路径
|
||||||
19
caddyfile
Normal file
19
caddyfile
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# HTTPS 反向代理(在主机上运行,非 Docker 内)
|
||||||
|
# 启动方式: caddy run --config Caddyfile
|
||||||
|
#
|
||||||
|
# 用法:
|
||||||
|
# 1. docker compose up -d (启动 App + MySQL + Redis)
|
||||||
|
# 2. caddy run --config Caddyfile (启动 HTTPS 代理)
|
||||||
|
# 3. 打开 https://localhost:4443 或 https://your-ip:4443
|
||||||
|
#
|
||||||
|
# 修改下方 IP 为你的局域网 IP(ifconfig en0 查看)
|
||||||
|
|
||||||
|
localhost:1443, https://192.168.31.218:1443 {
|
||||||
|
handle /admin/queues* {
|
||||||
|
reverse_proxy localhost:13010
|
||||||
|
}
|
||||||
|
handle {
|
||||||
|
reverse_proxy localhost:13000
|
||||||
|
}
|
||||||
|
tls internal
|
||||||
|
}
|
||||||
9
debug-request.json
Normal file
9
debug-request.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"model": "doubao-seedream-4-0-250828",
|
||||||
|
"prompt": "Lily and Olivia in 医院病房_日夜, medium shot, dramatic lighting, American comic style",
|
||||||
|
"sequential_image_generation": "disabled",
|
||||||
|
"response_format": "url",
|
||||||
|
"size": "1080x1920",
|
||||||
|
"stream": false,
|
||||||
|
"watermark": false
|
||||||
|
}
|
||||||
32
docker-compose.test.yml
Normal file
32
docker-compose.test.yml
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
services:
|
||||||
|
mysql:
|
||||||
|
image: mysql:8.0
|
||||||
|
container_name: waoowaoo-test-mysql
|
||||||
|
environment:
|
||||||
|
MYSQL_ROOT_PASSWORD: root
|
||||||
|
MYSQL_DATABASE: waoowaoo_test
|
||||||
|
MYSQL_ROOT_HOST: "%"
|
||||||
|
ports:
|
||||||
|
- "3307:3306"
|
||||||
|
command:
|
||||||
|
- "--default-authentication-plugin=mysql_native_password"
|
||||||
|
- "--sql_mode=STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION"
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "mysqladmin ping -h 127.0.0.1 -uroot -proot"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 30
|
||||||
|
start_period: 15s
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis:7-alpine
|
||||||
|
container_name: waoowaoo-test-redis
|
||||||
|
ports:
|
||||||
|
- "6380:6379"
|
||||||
|
command: ["redis-server", "--appendonly", "no"]
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "redis-cli", "ping"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 30
|
||||||
|
start_period: 5s
|
||||||
121
docker-compose.yml
Normal file
121
docker-compose.yml
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
services:
|
||||||
|
# ==================== MySQL ====================
|
||||||
|
mysql:
|
||||||
|
image: mysql:8.0
|
||||||
|
container_name: waoowaoo-mysql
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
MYSQL_ROOT_PASSWORD: waoowaoo123
|
||||||
|
MYSQL_DATABASE: waoowaoo
|
||||||
|
MYSQL_ROOT_HOST: "%"
|
||||||
|
ports:
|
||||||
|
- "13306:3306"
|
||||||
|
volumes:
|
||||||
|
- mysql_data:/var/lib/mysql
|
||||||
|
command:
|
||||||
|
- "--default-authentication-plugin=mysql_native_password"
|
||||||
|
- "--sql_mode=STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION"
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "mysqladmin ping -h 127.0.0.1 -uroot -pwaoowaoo123"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 30
|
||||||
|
start_period: 15s
|
||||||
|
|
||||||
|
# ==================== Redis ====================
|
||||||
|
redis:
|
||||||
|
image: redis:7-alpine
|
||||||
|
container_name: waoowaoo-redis
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "16379:6379"
|
||||||
|
volumes:
|
||||||
|
- redis_data:/data
|
||||||
|
command: ["redis-server", "--appendonly", "yes"]
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "redis-cli", "ping"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 30
|
||||||
|
start_period: 5s
|
||||||
|
|
||||||
|
# ==================== App (Next.js + Workers) ====================
|
||||||
|
app:
|
||||||
|
build: .
|
||||||
|
container_name: waoowaoo-app
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
# 数据库(指向容器内部 MySQL,用服务名 mysql 而非 localhost)
|
||||||
|
DATABASE_URL: "mysql://root:waoowaoo123@mysql:3306/waoowaoo"
|
||||||
|
# Redis(指向容器内部 Redis,用服务名 redis)
|
||||||
|
REDIS_HOST: redis
|
||||||
|
REDIS_PORT: "6379"
|
||||||
|
REDIS_USERNAME: ""
|
||||||
|
REDIS_PASSWORD: ""
|
||||||
|
REDIS_TLS: ""
|
||||||
|
# 存储:默认本地存储
|
||||||
|
STORAGE_TYPE: local
|
||||||
|
# 认证
|
||||||
|
NEXTAUTH_URL: "http://localhost:3000"
|
||||||
|
NEXTAUTH_SECRET: "waoowaoo-default-secret-2026"
|
||||||
|
# 内部密钥
|
||||||
|
CRON_SECRET: "waoowaoo-docker-cron-secret"
|
||||||
|
INTERNAL_TASK_TOKEN: "waoowaoo-docker-task-token"
|
||||||
|
API_ENCRYPTION_KEY: "waoowaoo-opensource-fixed-key-2026"
|
||||||
|
# Worker 配置
|
||||||
|
WATCHDOG_INTERVAL_MS: "30000"
|
||||||
|
TASK_HEARTBEAT_TIMEOUT_MS: "90000"
|
||||||
|
QUEUE_CONCURRENCY_IMAGE: "50"
|
||||||
|
QUEUE_CONCURRENCY_VIDEO: "50"
|
||||||
|
QUEUE_CONCURRENCY_VOICE: "20"
|
||||||
|
QUEUE_CONCURRENCY_TEXT: "50"
|
||||||
|
# Bull Board
|
||||||
|
BULL_BOARD_HOST: "0.0.0.0"
|
||||||
|
BULL_BOARD_PORT: "3010"
|
||||||
|
BULL_BOARD_BASE_PATH: "/admin/queues"
|
||||||
|
BULL_BOARD_USER: ""
|
||||||
|
BULL_BOARD_PASSWORD: ""
|
||||||
|
# 日志
|
||||||
|
LOG_UNIFIED_ENABLED: "true"
|
||||||
|
LOG_LEVEL: "INFO"
|
||||||
|
LOG_FORMAT: "json"
|
||||||
|
LOG_DEBUG_ENABLED: "false"
|
||||||
|
LOG_AUDIT_ENABLED: "true"
|
||||||
|
LOG_SERVICE: "waoowaoo"
|
||||||
|
LOG_REDACT_KEYS: "password,token,apiKey,apikey,authorization,cookie,secret,access_token,refresh_token"
|
||||||
|
# 计费
|
||||||
|
BILLING_MODE: "SHADOW"
|
||||||
|
# 流式
|
||||||
|
LLM_STREAM_EPHEMERAL_ENABLED: "true"
|
||||||
|
ports:
|
||||||
|
- "13000:3000"
|
||||||
|
- "13010:3010"
|
||||||
|
volumes:
|
||||||
|
- ./data:/app/data
|
||||||
|
- ./docker-logs:/app/logs
|
||||||
|
depends_on:
|
||||||
|
mysql:
|
||||||
|
condition: service_healthy
|
||||||
|
redis:
|
||||||
|
condition: service_healthy
|
||||||
|
command: >
|
||||||
|
sh -c "
|
||||||
|
npx prisma db push --skip-generate &&
|
||||||
|
(sleep 5 && echo '' &&
|
||||||
|
echo '╔══════════════════════════════════════════════════╗' &&
|
||||||
|
echo '║ waoowaoo is ready! ║' &&
|
||||||
|
echo '║ ║' &&
|
||||||
|
echo '║ HTTP: http://localhost:13000 ║' &&
|
||||||
|
echo '║ ║' &&
|
||||||
|
echo '║ For HTTPS, run Caddy on host: ║' &&
|
||||||
|
echo '║ caddy run --config Caddyfile ║' &&
|
||||||
|
echo '║ Then open: https://localhost:1443 ║' &&
|
||||||
|
echo '╚══════════════════════════════════════════════════╝' &&
|
||||||
|
echo '') &
|
||||||
|
npm run start
|
||||||
|
"
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
mysql_data:
|
||||||
|
redis_data:
|
||||||
|
|
||||||
38
docs/testing/behavior-test-guideline.md
Normal file
38
docs/testing/behavior-test-guideline.md
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# Behavior Test Guideline
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
- Treat tests as product contracts: input -> execution -> result.
|
||||||
|
- Block regressions on real outcomes (payload, DB write fields, returned contract, lifecycle events).
|
||||||
|
|
||||||
|
## Mandatory Rules
|
||||||
|
- Do not use `toHaveBeenCalled()` as the only assertion.
|
||||||
|
- Every test must assert at least one concrete business value.
|
||||||
|
- Every worker handler must include:
|
||||||
|
- failure path
|
||||||
|
- success path
|
||||||
|
- key branch path
|
||||||
|
- One bug fix must add at least one regression test.
|
||||||
|
|
||||||
|
## Mock Rules
|
||||||
|
- Must mock: database, AI providers, storage, external HTTP.
|
||||||
|
- Must not mock: the function under test, business constants.
|
||||||
|
- Avoid self-fulfilling mocks (`mock return X` then only assert returned X).
|
||||||
|
|
||||||
|
## Required Layers
|
||||||
|
- `tests/unit/helpers`: payload parsing and helper decisions.
|
||||||
|
- `tests/integration/api/contract`: route-level contracts and auth/validation.
|
||||||
|
- `tests/unit/worker`: worker branch + persistence assertions.
|
||||||
|
- `tests/integration/chain`: queue payload handoff and worker consumption behavior.
|
||||||
|
- `tests/unit/optimistic`: SSE and target-state UI consistency.
|
||||||
|
|
||||||
|
## Execution Commands
|
||||||
|
- `npm run test:behavior:unit`
|
||||||
|
- `npm run test:behavior:api`
|
||||||
|
- `npm run test:behavior:chain`
|
||||||
|
- `npm run test:behavior:guards`
|
||||||
|
- `npm run test:behavior:full`
|
||||||
|
|
||||||
|
## Merge Gate
|
||||||
|
- Behavior guards must pass.
|
||||||
|
- New route/task type must be reflected in catalogs/matrices.
|
||||||
|
- Regression changes without behavior tests are not complete.
|
||||||
52
eslint.config.mjs
Normal file
52
eslint.config.mjs
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import { dirname } from "path";
|
||||||
|
import { fileURLToPath } from "url";
|
||||||
|
import { FlatCompat } from "@eslint/eslintrc";
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = dirname(__filename);
|
||||||
|
|
||||||
|
const compat = new FlatCompat({
|
||||||
|
baseDirectory: __dirname,
|
||||||
|
});
|
||||||
|
|
||||||
|
const eslintConfig = [
|
||||||
|
...compat.extends("next/core-web-vitals", "next/typescript"),
|
||||||
|
{
|
||||||
|
ignores: [
|
||||||
|
"node_modules/**",
|
||||||
|
".agent/**",
|
||||||
|
".next/**",
|
||||||
|
"out/**",
|
||||||
|
"build/**",
|
||||||
|
"coverage/**",
|
||||||
|
"next-env.d.ts",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ["src/**/*.{ts,tsx}"],
|
||||||
|
ignores: ["src/components/ui/icons/**"],
|
||||||
|
rules: {
|
||||||
|
"no-restricted-imports": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
paths: [
|
||||||
|
{
|
||||||
|
name: "lucide-react",
|
||||||
|
message: "Import icons through '@/components/ui/icons' only.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"no-restricted-syntax": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
selector: "JSXOpeningElement[name.name='svg']",
|
||||||
|
message:
|
||||||
|
"Use AppIcon or icons module components instead of inline <svg>.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default eslintConfig;
|
||||||
91
extract_chinese.py
Normal file
91
extract_chinese.py
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
提取React/TypeScript代码中的硬编码中文字符串
|
||||||
|
"""
|
||||||
|
import re
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
import json
|
||||||
|
|
||||||
|
def extract_chinese_strings(file_path):
|
||||||
|
"""提取文件中的中文字符串"""
|
||||||
|
try:
|
||||||
|
with open(file_path, 'r', encoding='utf-8') as f:
|
||||||
|
content = f.read()
|
||||||
|
except:
|
||||||
|
return []
|
||||||
|
|
||||||
|
results = []
|
||||||
|
|
||||||
|
# 匹配JSX/TSX中的中文字符串
|
||||||
|
# 1. {' 中文 '} 或 {"中文"}
|
||||||
|
pattern1 = r'\{\s*[\'"]([^\'"\{\}]*[\u4e00-\u9fff]+[^\'"\{\}]*)[\'\"]\s*\}'
|
||||||
|
# 2. >中文<
|
||||||
|
pattern2 = r'\>([^<\>]*[\u4e00-\u9fff]+[^<\>]*)\<'
|
||||||
|
# 3. placeholder="中文" 等属性
|
||||||
|
pattern3 = r'(?:placeholder|title|alt|value|defaultValue|confirmText|cancelText|message)\s*=\s*[\'"]([^\'\"]*[\u4e00-\u9fff]+[^\'\"]*)[\'"]'
|
||||||
|
# 4. 字符串默认值 = '中文'
|
||||||
|
pattern4 = r'=\s*[\'"]([^\'\"]*[\u4e00-\u9fff]+[^\'\"]*)[\'"]'
|
||||||
|
|
||||||
|
for pattern in [pattern1, pattern2, pattern3, pattern4]:
|
||||||
|
matches = re.finditer(pattern, content)
|
||||||
|
for match in matches:
|
||||||
|
chinese_text = match.group(1).strip()
|
||||||
|
if chinese_text and len(chinese_text) > 0:
|
||||||
|
# 跳过注释
|
||||||
|
line_num = content[:match.start()].count('\n') + 1
|
||||||
|
line = content.split('\n')[line_num - 1]
|
||||||
|
if '//' in line and line.index('//') < line.find(chinese_text):
|
||||||
|
continue
|
||||||
|
results.append({
|
||||||
|
'text': chinese_text,
|
||||||
|
'line': line_num,
|
||||||
|
'category': 'unknown'
|
||||||
|
})
|
||||||
|
|
||||||
|
# 去重
|
||||||
|
seen = set()
|
||||||
|
unique_results = []
|
||||||
|
for r in results:
|
||||||
|
key = f"{r['text']}_{r['line']}"
|
||||||
|
if key not in seen:
|
||||||
|
seen.add(key)
|
||||||
|
unique_results.append(r)
|
||||||
|
|
||||||
|
return unique_results
|
||||||
|
|
||||||
|
def scan_directory(base_path,exclude_patterns=['test-ui']):
|
||||||
|
"""扫描目录中的所有TSX/TS文件"""
|
||||||
|
all_findings = {}
|
||||||
|
|
||||||
|
for root, dirs, files in os.walk(base_path):
|
||||||
|
# 排除特定目录
|
||||||
|
dirs[:] = [d for d in dirs if d not in exclude_patterns and not d.startswith('.')]
|
||||||
|
|
||||||
|
for file in files:
|
||||||
|
if file.endswith(('.tsx', '.ts')):
|
||||||
|
file_path = os.path.join(root, file)
|
||||||
|
relative_path = os.path.relpath(file_path, base_path)
|
||||||
|
|
||||||
|
findings = extract_chinese_strings(file_path)
|
||||||
|
if findings:
|
||||||
|
all_findings[relative_path] = findings
|
||||||
|
|
||||||
|
return all_findings
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
base_dir = 'src'
|
||||||
|
results = scan_directory(base_dir)
|
||||||
|
|
||||||
|
# 输出结果
|
||||||
|
total = 0
|
||||||
|
for file_path, findings in sorted(results.items()):
|
||||||
|
if findings:
|
||||||
|
print(f"\n## {file_path} ({len(findings)} strings)")
|
||||||
|
for finding in findings[:10]: # 只显示前10个
|
||||||
|
print(f" Line {finding['line']}: {finding['text'][:60]}")
|
||||||
|
total += len(findings)
|
||||||
|
if len(findings) > 10:
|
||||||
|
print(f" ... and {len(findings) - 10} more")
|
||||||
|
|
||||||
|
print(f"\n\n总计: {len(results)} 个文件, {total} 处硬编码中文")
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
# Character Image To Description Prompt
|
||||||
|
|
||||||
|
Analyze the provided character image and write one detailed English visual description for image generation.
|
||||||
|
|
||||||
|
## Required content
|
||||||
|
Include all of the following:
|
||||||
|
1. Gender and approximate age range
|
||||||
|
2. Hair style and hair color
|
||||||
|
3. Face shape and facial features
|
||||||
|
4. Body build and silhouette
|
||||||
|
5. Clothing style and clothing details
|
||||||
|
6. Accessories and signature details
|
||||||
|
7. Overall style keywords
|
||||||
|
|
||||||
|
## Missing-part completion
|
||||||
|
If the image only shows part of the body, infer the missing parts consistently.
|
||||||
|
- Missing lower body: infer matching pants/skirt style
|
||||||
|
- Missing shoes: infer shoes that fit the outfit
|
||||||
|
- Missing accessory details: infer a few reasonable accessories
|
||||||
|
|
||||||
|
## Forbidden content
|
||||||
|
Do not mention:
|
||||||
|
- Skin color
|
||||||
|
- Eye color
|
||||||
|
- Facial expression
|
||||||
|
- Pose or action
|
||||||
|
- Background
|
||||||
|
|
||||||
|
## Output format
|
||||||
|
Return one plain English paragraph only (about 120-220 words).
|
||||||
|
Do not return markdown, bullets, titles, or JSON.
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
# 图片反推角色描述提示词
|
||||||
|
|
||||||
|
请分析这张角色图片,生成一段详细的角色外貌描述(用于 AI 图片生成)。
|
||||||
|
|
||||||
|
## 输出要求
|
||||||
|
|
||||||
|
生成一段完整的角色视觉描述,包含以下要素:
|
||||||
|
|
||||||
|
1. 性别和年龄段(如:约二十五岁的男性)
|
||||||
|
2. 发型发色(如:黑色短发、微卷的棕色长发)
|
||||||
|
3. 脸型五官特征(如:剑眉星目、高鼻梁、薄唇)
|
||||||
|
4. 体态身材(如:身形修长、体格健壮)
|
||||||
|
5. 服装风格(如:深蓝色西装、白色衬衫、皮质腰带)
|
||||||
|
6. 配饰特征(如:左手戴银色手表、胸前别金色胸针)
|
||||||
|
7. 整体气质关键词(如:精英气质、禁欲系、高冷、温柔暖男)
|
||||||
|
|
||||||
|
## 缺失内容补齐规则
|
||||||
|
|
||||||
|
如果参考图只展示了部分身体(如上半身、头像),请根据已有信息合理推断并补全:
|
||||||
|
- **缺少下半身**:根据上衣风格推断裤装/裙装类型(如西装上衣配深蓝色西裤、休闲上衣配牛仔裤)
|
||||||
|
- **缺少鞋子**:根据整体穿搭风格推断鞋款(如正装配皮鞋、休闲装配运动鞋或帆布鞋)
|
||||||
|
- **缺少配饰细节**:根据角色气质合理添加配饰(如商务风配手表、休闲风配手环)
|
||||||
|
|
||||||
|
## 禁止描写
|
||||||
|
|
||||||
|
- 皮肤颜色
|
||||||
|
- 眼睛颜色
|
||||||
|
- 表情
|
||||||
|
- 动作
|
||||||
|
- 背景
|
||||||
|
- 姿势
|
||||||
|
|
||||||
|
## 输出格式
|
||||||
|
|
||||||
|
一段连贯的描述文字,约200-300字,直接可用于图片生成提示词。
|
||||||
|
只返回描述文字,不要有任何标题、序号或其他格式。
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# Reference Image To Character Sheet Prompt (img2img)
|
||||||
|
|
||||||
|
Use the provided reference image to extract face traits, hairstyle, body shape, and outfit structure.
|
||||||
|
|
||||||
|
## Style consistency
|
||||||
|
Keep the original visual style:
|
||||||
|
- Real photo -> realistic style
|
||||||
|
- Anime -> anime style
|
||||||
|
- Realistic illustration -> realistic illustration style
|
||||||
|
- Any other style -> preserve that style
|
||||||
|
|
||||||
|
## Generation rules
|
||||||
|
1. Ignore the original image color cast and lighting defects
|
||||||
|
2. Use clean, soft studio lighting
|
||||||
|
3. Keep natural, aesthetically correct body proportions
|
||||||
|
4. Do not copy blur, noise, compression artifacts, or defects
|
||||||
|
5. Output must be clear, sharp, and production-quality
|
||||||
|
6. Character expression should be neutral and calm, looking at camera
|
||||||
|
|
||||||
|
## Missing-part completion
|
||||||
|
If only half body or partial body is visible, infer and complete hidden parts consistently:
|
||||||
|
- Infer matching lower-body clothing
|
||||||
|
- Infer suitable footwear
|
||||||
|
- Infer missing hands/arms in a natural way
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# 参考图转角色设定图提示词(图生图模式)
|
||||||
|
|
||||||
|
基于提供的参考图片,提取角色的面部五官特征、发型、体型和服装款式作为参考。
|
||||||
|
|
||||||
|
## 画风保持规则
|
||||||
|
|
||||||
|
保持原图的画风和艺术风格:
|
||||||
|
- 真人照片风格 → 生成真人风格
|
||||||
|
- 动漫风格 → 生成动漫风格
|
||||||
|
- 写实插画 → 生成写实插画风格
|
||||||
|
- 其他风格 → 保持一致
|
||||||
|
|
||||||
|
## 生成规则
|
||||||
|
|
||||||
|
1. 忽略原图的具体色调和光线
|
||||||
|
2. 使用自然柔和的摄影棚灯光
|
||||||
|
3. 绘制正常美观的人体比例
|
||||||
|
4. 不要复制原图的画质、模糊、噪点或瑕疵
|
||||||
|
5. 生成的图像必须清晰锐利、细节丰富、专业品质
|
||||||
|
6. 角色表情应为自然平静的中性表情,目光正视镜头
|
||||||
|
|
||||||
|
## 缺失部位自动补齐
|
||||||
|
|
||||||
|
如果参考图是半身或部分身体,请根据服装风格和人物特征合理补全未露出的部位:
|
||||||
|
- **缺少下半身**:根据上衣风格推断并绘制匹配的裤装/裙装
|
||||||
|
- **缺少脚部**:根据整体穿搭风格添加合适的鞋款
|
||||||
|
- **缺少手部/手臂**:根据姿态合理补全
|
||||||
|
- 保持整体风格一致,确保补全的部分与可见部分协调统一
|
||||||
38
lib/prompts/novel-promotion/agent_acting_direction.en.txt
Normal file
38
lib/prompts/novel-promotion/agent_acting_direction.en.txt
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
You are an experienced Acting Director.
|
||||||
|
Your task is to generate acting notes for each character in each panel.
|
||||||
|
|
||||||
|
Core input:
|
||||||
|
- Total panel count: {panel_count}
|
||||||
|
- Panels JSON:
|
||||||
|
{panels_json}
|
||||||
|
- Character info:
|
||||||
|
{characters_info}
|
||||||
|
|
||||||
|
Requirements:
|
||||||
|
1. Treat each panel independently. The same character can have different emotional states across panels.
|
||||||
|
2. Adapt performance style to panel.scene_type (daily / emotion / action / epic / suspense).
|
||||||
|
3. For each character, write one concise visual acting instruction including:
|
||||||
|
- emotional state (visible, not abstract)
|
||||||
|
- facial expression details
|
||||||
|
- body language / posture
|
||||||
|
- micro action and gaze direction
|
||||||
|
4. Use only observable descriptions. Avoid abstract words like "sad" without visual evidence.
|
||||||
|
|
||||||
|
Output format (JSON array only):
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"panel_number": 1,
|
||||||
|
"characters": [
|
||||||
|
{
|
||||||
|
"name": "Character Name",
|
||||||
|
"acting": "One-sentence visual acting direction"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
Strict constraints:
|
||||||
|
1. Return JSON only, no markdown.
|
||||||
|
2. Array length must equal {panel_count}.
|
||||||
|
3. Each character object must contain only "name" and "acting".
|
||||||
|
4. Keep character names exactly consistent with panel input.
|
||||||
90
lib/prompts/novel-promotion/agent_acting_direction.zh.txt
Normal file
90
lib/prompts/novel-promotion/agent_acting_direction.zh.txt
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
你是一位经验丰富的表演指导(Acting Director)。你的任务是为一组分镜中的**每个镜头**设计角色的表演细节。
|
||||||
|
|
||||||
|
【核心职责】
|
||||||
|
|
||||||
|
分析整组分镜后,为每个镜头中的角色用一句话描述完整的表演指令,包含:
|
||||||
|
- 情绪状态与强度
|
||||||
|
- 面部表情细节
|
||||||
|
- 肢体语言与姿态
|
||||||
|
- 微动作与视线
|
||||||
|
|
||||||
|
【重要】每个镜头的表演必须是独立的!
|
||||||
|
- 同一角色在不同镜头可能有不同情绪变化
|
||||||
|
- 表演风格匹配 scene_type(日常/情感/动作/史诗/悬疑)
|
||||||
|
|
||||||
|
【表演风格匹配 scene_type】
|
||||||
|
|
||||||
|
**daily(日常)**:自然松弛,微表情为主,动作幅度小
|
||||||
|
**emotion(情感)**:细腻层次,眼神戏份重,情绪渐进
|
||||||
|
**action(动作)**:爆发力强,动作干脆,表情夸张
|
||||||
|
**epic(史诗)**:庄重仪式感,姿态端正,动作缓慢有力
|
||||||
|
**suspense(悬疑)**:紧绷警觉,肢体僵硬,眼神游移
|
||||||
|
|
||||||
|
【表演描述词库】
|
||||||
|
|
||||||
|
**表情**:眼眶泛红、眉头紧锁、嘴角上扬、目光闪躲、瞳孔收缩、嘴唇颤抖、咬紧牙关
|
||||||
|
**肢体**:握紧拳头、身体前倾、双手交握、肩膀耸起、转身背对、后退一步
|
||||||
|
**微动作**:轻轻眨眼、咽口水、深呼吸、手指轻颤、舔嘴唇、胸口起伏
|
||||||
|
|
||||||
|
【⚠️ 禁止规则】
|
||||||
|
|
||||||
|
1. 禁止抽象情绪词:悲伤、愤怒、紧张 → 改用可见表现
|
||||||
|
2. 禁止身份称呼:母亲、父亲 → 改用角色名
|
||||||
|
|
||||||
|
【输出格式】
|
||||||
|
|
||||||
|
返回JSON数组,每个镜头一个对象,每个角色只有 name + acting 两个字段:
|
||||||
|
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"panel_number": 1,
|
||||||
|
"characters": [
|
||||||
|
{
|
||||||
|
"name": "李凤华",
|
||||||
|
"acting": "嘴角微扬眼神柔和地看向景笙,身体微微前倾,双手自然垂放,轻轻眨眼"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "景笙",
|
||||||
|
"acting": "面带微笑但眼神略显疏离,站姿笔直双手背后,轻轻点头"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"panel_number": 2,
|
||||||
|
"characters": [
|
||||||
|
{
|
||||||
|
"name": "李凤华",
|
||||||
|
"acting": "眉头轻皱嘴唇紧抿,双手交握在身前肩膀微耸,目光低垂看向地面,手指轻微交缠"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"panel_number": 3,
|
||||||
|
"characters": [
|
||||||
|
{
|
||||||
|
"name": "李凤华",
|
||||||
|
"acting": "眼眶泛红泪水打转嘴唇颤抖,身体微微发抖双手攥紧衣角,快速眨眼忍住泪水转头避开对方视线"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
【输入数据】
|
||||||
|
|
||||||
|
分镜数据(共 {panel_count} 个镜头):
|
||||||
|
{panels_json}
|
||||||
|
|
||||||
|
角色信息:
|
||||||
|
{characters_info}
|
||||||
|
|
||||||
|
【严格要求】
|
||||||
|
|
||||||
|
1. 只返回JSON数组,不要有markdown代码块标记
|
||||||
|
2. 数组长度必须等于输入的镜头数量({panel_count}个)
|
||||||
|
3. 每个角色只有 name 和 acting 两个字段
|
||||||
|
4. acting 用一句话描述完整表演(表情+肢体+微动作+视线)
|
||||||
|
5. 角色名必须与输入分镜中的 characters 完全一致
|
||||||
|
6. 所有描述必须是可视化的,禁止抽象情绪词
|
||||||
|
7. 根据 scene_type 调整表演风格
|
||||||
|
8. 情绪弧线连贯,前后镜头有合理递进
|
||||||
|
|
||||||
86
lib/prompts/novel-promotion/agent_character_profile.en.txt
Normal file
86
lib/prompts/novel-promotion/agent_character_profile.en.txt
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
You are a casting and character-asset analyst.
|
||||||
|
Analyze the input text and produce structured character profiles for visual production.
|
||||||
|
|
||||||
|
Input text:
|
||||||
|
{input}
|
||||||
|
|
||||||
|
Existing character library info:
|
||||||
|
{characters_lib_info}
|
||||||
|
|
||||||
|
Goals:
|
||||||
|
1. Identify characters that should appear visually.
|
||||||
|
2. Exclude pure background extras and abstract entities.
|
||||||
|
3. Build profile fields needed for downstream visual generation.
|
||||||
|
4. Capture naming/alias mapping, especially first-person references.
|
||||||
|
|
||||||
|
Extraction rules:
|
||||||
|
1. Include characters that speak, act, or significantly drive plot.
|
||||||
|
2. Exclude one-off nameless background people unless visual identity is required.
|
||||||
|
3. Resolve aliases/titles (e.g., "my husband", "boss", "I") to canonical names when possible.
|
||||||
|
4. For first-person narrative, explicitly document who "I" maps to in introduction.
|
||||||
|
|
||||||
|
Field rules:
|
||||||
|
- role_level: one of S/A/B/C/D based on narrative importance
|
||||||
|
- costume_tier: 1-5 based on social identity (not role_level)
|
||||||
|
- expected_appearances: always include at least one initial appearance
|
||||||
|
- introduction: include identity, relationship mapping, and common address mapping
|
||||||
|
|
||||||
|
Output format (JSON only):
|
||||||
|
{
|
||||||
|
"characters": [
|
||||||
|
{
|
||||||
|
"name": "Canonical Name",
|
||||||
|
"aliases": ["alias 1", "alias 2"],
|
||||||
|
"introduction": "Role, perspective mapping, relationships, and naming aliases",
|
||||||
|
"gender": "male/female/other",
|
||||||
|
"age_range": "young adult",
|
||||||
|
"role_level": "S",
|
||||||
|
"archetype": "character archetype",
|
||||||
|
"personality_tags": ["tag1", "tag2"],
|
||||||
|
"era_period": "modern/fantasy/historical/sci-fi",
|
||||||
|
"social_class": "elite/middle/common",
|
||||||
|
"occupation": "occupation or none",
|
||||||
|
"costume_tier": 3,
|
||||||
|
"suggested_colors": ["color1", "color2"],
|
||||||
|
"primary_identifier": "signature visual marker",
|
||||||
|
"visual_keywords": ["keyword1", "keyword2"],
|
||||||
|
"expected_appearances": [
|
||||||
|
{ "id": 1, "change_reason": "initial appearance" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"new_characters": [
|
||||||
|
{
|
||||||
|
"name": "Canonical Name",
|
||||||
|
"aliases": ["alias 1", "alias 2"],
|
||||||
|
"introduction": "Role, perspective mapping, relationships, and naming aliases",
|
||||||
|
"gender": "male/female/other",
|
||||||
|
"age_range": "young adult",
|
||||||
|
"role_level": "S",
|
||||||
|
"archetype": "character archetype",
|
||||||
|
"personality_tags": ["tag1", "tag2"],
|
||||||
|
"era_period": "modern/fantasy/historical/sci-fi",
|
||||||
|
"social_class": "elite/middle/common",
|
||||||
|
"occupation": "occupation or none",
|
||||||
|
"costume_tier": 3,
|
||||||
|
"suggested_colors": ["color1", "color2"],
|
||||||
|
"primary_identifier": "signature visual marker",
|
||||||
|
"visual_keywords": ["keyword1", "keyword2"],
|
||||||
|
"expected_appearances": [
|
||||||
|
{ "id": 1, "change_reason": "initial appearance" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"updated_characters": [
|
||||||
|
{
|
||||||
|
"name": "Existing Canonical Name",
|
||||||
|
"updated_introduction": "Updated intro with newly discovered mapping",
|
||||||
|
"updated_aliases": ["new alias 1", "new alias 2"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
Strict constraints:
|
||||||
|
1. JSON only.
|
||||||
|
2. If no valid character is found, return empty arrays.
|
||||||
|
3. Keep all strings in English.
|
||||||
244
lib/prompts/novel-promotion/agent_character_profile.zh.txt
Normal file
244
lib/prompts/novel-promotion/agent_character_profile.zh.txt
Normal file
@@ -0,0 +1,244 @@
|
|||||||
|
你是专业的"选角指导"。请基于提供的文本(小说、剧本或混合格式),分析并输出所有需要制作形象的角色档案信息。
|
||||||
|
|
||||||
|
【你的职责】
|
||||||
|
- 识别需要在画面中出现的角色
|
||||||
|
- 根据剧情发展和角色身份判断每个角色的重要性层级
|
||||||
|
- 分析角色的性格和背景
|
||||||
|
- 输出结构化的角色档案(供后续视觉生成使用)
|
||||||
|
- ⚠️ 分析角色之间的关系、称呼映射,生成角色介绍(introduction)
|
||||||
|
|
||||||
|
【筛选规则 - 精准提取模式】
|
||||||
|
|
||||||
|
✅【必须提取的角色】:
|
||||||
|
- 剧本人物行中列出的角色
|
||||||
|
- 有台词且参与剧情互动的角色
|
||||||
|
- 贯穿故事主线的核心人物
|
||||||
|
- 对剧情有实际推动作用的配角
|
||||||
|
- 在画面中需要出镜的角色
|
||||||
|
|
||||||
|
❌【不提取的角色】:
|
||||||
|
- 无名无特征的纯路人(如"人群中的某人")
|
||||||
|
- 仅被提及但从未出场的角色
|
||||||
|
- 没有台词也没有互动的背景人物
|
||||||
|
- 意境描述中的虚构存在(如"命运"、"死神的化身")
|
||||||
|
|
||||||
|
📋【判断标准】:
|
||||||
|
问自己:这个角色是否需要制作形象图?是否在画面中有实际出镜?
|
||||||
|
如果答案是否定的,则不提取。
|
||||||
|
|
||||||
|
【角色介绍 introduction 规则 ⭐重要】
|
||||||
|
|
||||||
|
每个角色必须有 introduction 字段,用于帮助后续 AI 正确识别角色。包含:
|
||||||
|
|
||||||
|
1. **叙述视角映射**:
|
||||||
|
- 如果是第一人称叙述,明确说明"我"对应此角色
|
||||||
|
- 示例:"本角色是故事主角,小说以第一人称'我'叙述"
|
||||||
|
|
||||||
|
2. **角色身份定位**:
|
||||||
|
- 描述角色在故事中的身份(主角/配角/反派等)
|
||||||
|
- 示例:"女主角,公司秘书"
|
||||||
|
|
||||||
|
3. **角色关系**:
|
||||||
|
- 与其他主要角色的关系
|
||||||
|
- 示例:"林墨的妻子,张三的女儿"
|
||||||
|
|
||||||
|
4. **称呼映射**:
|
||||||
|
- 其他角色对此角色的常用称呼
|
||||||
|
- 示例:"被林墨称呼为'老婆'、'晴晴',被张三称呼为'闺女'"
|
||||||
|
|
||||||
|
示例 introduction:
|
||||||
|
"故事主角,小说以第一人称'我'叙述,真名林墨。苏晴的丈夫,张三的女婿。被苏晴称呼为'老公'、'墨哥',被下属称呼为'林总'。"
|
||||||
|
|
||||||
|
【角色重要性层级判断规则】
|
||||||
|
|
||||||
|
⚠️ 重要:根据角色在剧情中的戏份和身份地位来判断,不是根据外表华丽程度!
|
||||||
|
|
||||||
|
S级(绝对主角):
|
||||||
|
- 故事的核心视角人物,剧情围绕其展开
|
||||||
|
- 第一人称叙述中的"我"通常是S级
|
||||||
|
- 判断依据:戏份最重、出场最多、剧情主线与其紧密相关
|
||||||
|
|
||||||
|
A级(核心配角):
|
||||||
|
- 与主角有大量互动的重要角色
|
||||||
|
- 男二号、女二号、主要反派等
|
||||||
|
- 判断依据:对主线剧情有重大影响、戏份仅次于主角
|
||||||
|
|
||||||
|
B级(重要配角):
|
||||||
|
- 多次出场、有名有姓、推动某条支线剧情
|
||||||
|
- 判断依据:有一定戏份、对剧情有贡献
|
||||||
|
|
||||||
|
C级(次要角色):
|
||||||
|
- 偶尔出场、戏份较少但有具体形象
|
||||||
|
- 判断依据:需要出镜但戏份不多
|
||||||
|
|
||||||
|
D级(群众演员):
|
||||||
|
- 有短暂出镜需求的小角色
|
||||||
|
- 判断依据:仅在个别场景出现
|
||||||
|
|
||||||
|
【服装华丽度层级 costume_tier】
|
||||||
|
|
||||||
|
⚠️ 服装华丽度由角色的社会身份和剧情设定决定,不是由重要性决定!
|
||||||
|
- 主角可以是朴素穿着(如穷学生主角=tier 2)
|
||||||
|
- 配角可以是华丽服装(如富家公子配角=tier 5)
|
||||||
|
|
||||||
|
5级(皇室/顶奢级):皇室成员、顶级富豪,服装极致华丽,有精美的刺绣、镶嵌或定制剪裁。
|
||||||
|
4级(贵族/精英级):贵族、企业家,服装精致考究,使用高档面料和精致细节。
|
||||||
|
3级(专业/品质级):中产阶级、专业人士,服装得体有品,剪裁讲究。
|
||||||
|
2级(日常/普通级):普通人、学生,服装简洁日常,款式普通但整洁。
|
||||||
|
1级(朴素/统一级):平民、底层劳动者,服装朴素统一,基础款式,功能性为主。
|
||||||
|
|
||||||
|
【角色原型 archetype 参考词库】
|
||||||
|
|
||||||
|
正派角色可以选择:霸道总裁、高冷学霸、温柔暖男、励志少年、贤惠女主、独立女强人、忠诚护卫等。
|
||||||
|
|
||||||
|
反派角色可以选择:心机婊、白莲花、阴险反派、疯批美人、复仇者等。
|
||||||
|
|
||||||
|
其他类型:傲娇公主、病娇、腹黑、毒舌、话痨、冷面热心、闷骚等。
|
||||||
|
|
||||||
|
【性格标签 personality_tags 参考词库】
|
||||||
|
|
||||||
|
气质类标签:高冷、温柔、阳光、忧郁、神秘、妩媚、清冷、热情
|
||||||
|
|
||||||
|
性格类标签:腹黑、傲娇、毒舌、话痨、闷骚、直爽、圆滑、固执
|
||||||
|
|
||||||
|
态度类标签:自信、自卑、孤僻、合群、叛逆、顺从
|
||||||
|
|
||||||
|
【视觉关键词 visual_keywords 参考词库】
|
||||||
|
|
||||||
|
风格类关键词:精英气质、街头潮流、学院风、复古优雅、运动活力、文艺气息、冷淡极简
|
||||||
|
|
||||||
|
特征类关键词:病弱感、禁欲系、狼狗系、奶狗系、御姐范、萝莉感、大叔味
|
||||||
|
|
||||||
|
【色彩建议规则】
|
||||||
|
|
||||||
|
根据角色类型选择合适的色彩:
|
||||||
|
|
||||||
|
正派主角适合白色、蓝色、金色或浅色系,传达正义和光明感。
|
||||||
|
|
||||||
|
反派角色适合黑色、暗红、深紫或暗色系,营造神秘或压迫感。
|
||||||
|
|
||||||
|
温柔角色适合米白、淡粉、浅绿等柔和色,体现温暖亲和。
|
||||||
|
|
||||||
|
冷酷角色适合黑色、灰色、深蓝等冷色调,强调距离感。
|
||||||
|
|
||||||
|
活泼角色适合橙色、黄色等亮色系,展现活力和热情。
|
||||||
|
|
||||||
|
【辨识标志设计规则】
|
||||||
|
|
||||||
|
为S级和A级角色设计一眼就能认出的标志性特征:
|
||||||
|
|
||||||
|
面部标志:眼角泪痣、剑眉、刀疤、胎记等独特面部特征。
|
||||||
|
|
||||||
|
发型标志:白发、挑染、独特发型、发带等醒目的头发特征。
|
||||||
|
|
||||||
|
服装标志:永远穿红色、标志性围巾、招牌外套等固定的服装元素。
|
||||||
|
|
||||||
|
配饰标志:家传戒指、从不摘下的项链、拐杖等标志性物品。
|
||||||
|
|
||||||
|
【子形象筛选规则 - 识别视觉外观变化 ⭐重要】
|
||||||
|
|
||||||
|
分析原文中角色是否有多个视觉形态,输出到 expected_appearances 字段。
|
||||||
|
|
||||||
|
✅ 需要记录的子形象(视觉上可见的变化):
|
||||||
|
- 衣着变化:换装、更换正装/休闲装、穿戴盔甲等
|
||||||
|
- 年龄变化:穿越、回忆场景中的年轻/年老状态
|
||||||
|
- 特殊装扮:出浴(围浴巾)、冒充他人的装扮
|
||||||
|
- 发型改变:剪头、编发、盘发、披发等持续性外观变化
|
||||||
|
|
||||||
|
❌ 不需要记录的(非视觉或临时状态):
|
||||||
|
- 情绪/心理状态:生气、开心、难过、紧张
|
||||||
|
- 健康状态:生病、发烧(除非有明显视觉特征如绷带)
|
||||||
|
- 临时动作:跑步、跳跃、战斗姿势
|
||||||
|
- 模糊描述:"蒙上了一层阴影""眼神变了"等抽象描述
|
||||||
|
- 临时特效/光影状态:散发光芒、身上发光、气场外放、浑身火焰、佛光环绕、金光闪闪等后期可添加的特效
|
||||||
|
- 战斗技能释放:发功、运功、施法、放大招、释放法术等技能状态
|
||||||
|
- 一次性瞬间状态:被打飞、摔倒、中招、受击等不持续的状态
|
||||||
|
|
||||||
|
⚠️ 判断标准:
|
||||||
|
- 如果一个状态无法通过换装来体现,就不需要记录
|
||||||
|
- 如果一个状态是通过后期特效(如发光、粒子、光环、火焰等)来表现的,不需要记录
|
||||||
|
- 如果一个状态只持续几秒而非整个场景,不需要记录
|
||||||
|
- 只有持续性的、需要重新制作人物形象图的外观变化才需要记录
|
||||||
|
|
||||||
|
📋 expected_appearances 格式:
|
||||||
|
- 每个角色必须至少有一个 id=1 的"初始形象"
|
||||||
|
- 如有换装/年龄变化等,添加 id=2, 3... 的子形象
|
||||||
|
- change_reason 简要说明变化原因(如"出浴状态"、"战斗装束"、"年老回忆")
|
||||||
|
|
||||||
|
【已有资产库】
|
||||||
|
|
||||||
|
⚠️ 重要:请仔细阅读已有角色的介绍,判断新发现的角色名是否与已有角色是同一人!
|
||||||
|
|
||||||
|
{characters_lib_info}
|
||||||
|
|
||||||
|
【输出格式 - 支持新增和更新】
|
||||||
|
|
||||||
|
只返回JSON,禁止任何markdown标记或注释。
|
||||||
|
|
||||||
|
输出包含两个数组:
|
||||||
|
- new_characters: 新发现的角色
|
||||||
|
- updated_characters: 需要更新介绍的已有角色(如发现了新的称呼、关系、或真名)
|
||||||
|
|
||||||
|
{
|
||||||
|
"new_characters": [
|
||||||
|
{
|
||||||
|
"name": "角色名",
|
||||||
|
"aliases": ["别名1", "别名2"],
|
||||||
|
"introduction": "角色介绍:身份定位、叙述视角映射、与其他角色的关系、常用称呼",
|
||||||
|
"gender": "男/女",
|
||||||
|
"age_range": "约二十五岁",
|
||||||
|
"role_level": "S/A/B/C/D",
|
||||||
|
"archetype": "角色原型(如霸道总裁)",
|
||||||
|
"personality_tags": ["高冷", "腹黑"],
|
||||||
|
"era_period": "现代都市/古代唐朝/未来科幻",
|
||||||
|
"social_class": "上层精英/中产/平民",
|
||||||
|
"occupation": "企业家/学生/无",
|
||||||
|
"costume_tier": 5,
|
||||||
|
"suggested_colors": ["深蓝", "金色"],
|
||||||
|
"primary_identifier": "眼角泪痣(仅S/A级需要)",
|
||||||
|
"visual_keywords": ["精英气质", "禁欲系"],
|
||||||
|
"expected_appearances": [
|
||||||
|
{"id": 1, "change_reason": "初始形象"},
|
||||||
|
{"id": 2, "change_reason": "换装/特殊状态的原因(如有)"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"updated_characters": [
|
||||||
|
{
|
||||||
|
"name": "已有角色名(必须与资产库中的名字完全一致)",
|
||||||
|
"updated_introduction": "更新后的角色介绍(补充新发现的关系、称呼、真名等)",
|
||||||
|
"updated_aliases": ["新发现的别名1", "新发现的别名2"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
【更新规则】
|
||||||
|
|
||||||
|
⚠️ 什么情况下应该更新已有角色(放入 updated_characters):
|
||||||
|
|
||||||
|
1. **发现真名**:之前只有"我",现在发现"我"的真名是"林墨"
|
||||||
|
→ 更新 introduction 说明映射,添加 updated_aliases: ["林墨"]
|
||||||
|
|
||||||
|
2. **发现新称呼**:之前不知道别人怎么称呼这个角色,现在发现有人叫他"林总"
|
||||||
|
→ 更新 introduction 添加称呼信息,添加 updated_aliases: ["林总"]
|
||||||
|
|
||||||
|
3. **发现新关系**:之前不知道角色间的关系,现在发现苏晴是林墨的妻子
|
||||||
|
→ 更新双方的 introduction 添加关系信息
|
||||||
|
|
||||||
|
4. **不要重复创建**:如果发现"林墨"其实就是已有的"我",不要创建新角色,而是更新"我"的介绍和别名
|
||||||
|
|
||||||
|
【严格要求】
|
||||||
|
1. 只返回JSON,不得有其他文字
|
||||||
|
2. role_level 必须是 S/A/B/C/D 之一
|
||||||
|
3. costume_tier 必须是 1-5 的整数
|
||||||
|
4. S/A 级角色必须有 primary_identifier
|
||||||
|
5. personality_tags 至少2个,最多5个
|
||||||
|
6. suggested_colors 2-3个颜色
|
||||||
|
7. introduction 必填,描述角色身份、关系、称呼映射
|
||||||
|
8. 如果发现已有角色的新信息,放入 updated_characters 而不是创建新角色
|
||||||
|
9. updated_characters 中的 name 必须与已有资产库中的名字完全一致
|
||||||
|
10. expected_appearances 必填,至少包含 id=1 的初始形象
|
||||||
|
11. 只有持续性视觉变化才添加子形象,临时特效/情绪/动作不添加
|
||||||
|
|
||||||
|
【原文内容】
|
||||||
|
{input}
|
||||||
44
lib/prompts/novel-promotion/agent_character_visual.en.txt
Normal file
44
lib/prompts/novel-promotion/agent_character_visual.en.txt
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
You are a character visual designer.
|
||||||
|
Generate image-ready appearance descriptions from character profiles.
|
||||||
|
|
||||||
|
Character profiles JSON:
|
||||||
|
{character_profiles}
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
1. Keep identity consistency with profile fields.
|
||||||
|
2. Convert personality/social identity into visual details (face, hair, outfit, accessories).
|
||||||
|
3. Support both human and non-human characters.
|
||||||
|
4. Respect era_period and costume_tier.
|
||||||
|
5. If primary_identifier exists, include it clearly in each main description.
|
||||||
|
6. Do not include expression, action, background, or plot narration.
|
||||||
|
7. Do not include skin color, eye color, or lip color.
|
||||||
|
|
||||||
|
Appearance strategy:
|
||||||
|
- Initial appearance should be complete and self-contained.
|
||||||
|
- Additional appearances should focus on visual changes indicated by change_reason.
|
||||||
|
- Provide 3 alternative description lines per appearance.
|
||||||
|
|
||||||
|
Output format (JSON only):
|
||||||
|
{
|
||||||
|
"characters": [
|
||||||
|
{
|
||||||
|
"name": "Character Name",
|
||||||
|
"appearances": [
|
||||||
|
{
|
||||||
|
"id": 0,
|
||||||
|
"change_reason": "initial appearance",
|
||||||
|
"descriptions": [
|
||||||
|
"description variant 1",
|
||||||
|
"description variant 2",
|
||||||
|
"description variant 3"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
Strict constraints:
|
||||||
|
1. JSON only.
|
||||||
|
2. Keep names exactly aligned with input profiles.
|
||||||
|
3. Each descriptions array must contain at least 3 valid English strings.
|
||||||
208
lib/prompts/novel-promotion/agent_character_visual.zh.txt
Normal file
208
lib/prompts/novel-promotion/agent_character_visual.zh.txt
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
你是专业的"角色视觉设计师"。根据角色档案信息,生成详细的人物外貌描述(用于AI图片生成)。
|
||||||
|
|
||||||
|
【你的职责】
|
||||||
|
- 根据角色档案生成对应的外貌描述
|
||||||
|
- 确保核心角色有明显的视觉辨识度
|
||||||
|
- 体现角色性格和身份的视觉特征
|
||||||
|
- 服装华丽度由角色身份决定,与重要性无关
|
||||||
|
|
||||||
|
【角色类型灵活处理规则】
|
||||||
|
|
||||||
|
⚠️ 角色不一定是人类!请根据原文判断角色的实际形态:
|
||||||
|
|
||||||
|
**人类角色**:按照下方的面部、发型、体态、服装规范描述
|
||||||
|
|
||||||
|
**非人类角色**(动物、神话生物、知名形象等):
|
||||||
|
- 描述开头必须以角色名/物种名开始
|
||||||
|
- 根据角色实际形态自由描述外观特征,不受人类模板限制
|
||||||
|
- 保持角色的核心辨识特征
|
||||||
|
|
||||||
|
示例:
|
||||||
|
- 孙悟空 → "孙悟空,身穿虎皮裙,头戴紧箍咒金环,手持如意金箍棒,毛发金黄蓬松,尖耳竖立,眼神机灵狡黠..."
|
||||||
|
- 蜗牛 → "蜗牛,背负螺旋形褐色硬壳,壳面有细密纹路,两只细长触角顶端有圆形眼点,身体柔软半透明..."
|
||||||
|
- 龙 → "东方神龙,鳞片金红交错闪烁,龙须飘逸,鹿角威严分叉,蛇身盘旋腾空,四爪锋利如钩..."
|
||||||
|
- 拟人化动物 → "狐狸精,保留尖耳毛尾的狐狸特征,身着红色丝绸长裙,九条白色蓬松尾巴在身后舒展..."
|
||||||
|
|
||||||
|
【视觉层级规范】
|
||||||
|
|
||||||
|
⚠️ 核心原则:服装华丽度由角色的社会身份和剧情设定决定,不是由重要性等级决定!
|
||||||
|
|
||||||
|
S级角色:
|
||||||
|
- 描述长度180-220字
|
||||||
|
- 必须有极高的视觉辨识度和"主角气质"
|
||||||
|
- 服装风格由角色身份决定(穷学生可以穿简单校服,但五官气质必须出众)
|
||||||
|
|
||||||
|
A级角色:
|
||||||
|
- 描述长度150-180字
|
||||||
|
- 有明显的个人特色和记忆点
|
||||||
|
- 服装风格由角色身份决定
|
||||||
|
|
||||||
|
B级角色:
|
||||||
|
- 描述长度120-150字
|
||||||
|
- 有基本的辨识特征
|
||||||
|
- 服装风格符合其社会身份
|
||||||
|
|
||||||
|
C级角色:
|
||||||
|
- 描述长度80-120字
|
||||||
|
- 简洁但完整的形象描述
|
||||||
|
|
||||||
|
D级角色:
|
||||||
|
- 描述长度50-80字
|
||||||
|
- 基础形象即可
|
||||||
|
|
||||||
|
【服装华丽度 costume_tier 对照】
|
||||||
|
|
||||||
|
⚠️ 由角色的社会阶层和剧情身份决定,与role_level无关!
|
||||||
|
|
||||||
|
5级(皇室/顶奢级):皇室成员、顶级富豪等,服装有刺绣、镶嵌、定制剪裁、稀有面料。
|
||||||
|
4级(贵族/精英级):贵族、企业家等,高档面料、精致细节、品质配饰。
|
||||||
|
3级(专业/品质级):中产阶级、专业人士,得体剪裁、有设计感。
|
||||||
|
2级(日常/普通级):普通人,简洁日常的款式。
|
||||||
|
1级(朴素/统一级):平民、学生等,基础款式、功能性为主。
|
||||||
|
|
||||||
|
【辨识标志应用规则】
|
||||||
|
|
||||||
|
如果角色档案中有 primary_identifier,必须在描述中明确体现:
|
||||||
|
|
||||||
|
示例:
|
||||||
|
- primary_identifier: "眼角泪痣" → 描述中必须出现 "眼角一颗小巧泪痣"
|
||||||
|
- primary_identifier: "左耳银色耳钉" → 描述中必须出现 "左耳佩戴一枚银色耳钉"
|
||||||
|
|
||||||
|
【色彩应用规则】
|
||||||
|
|
||||||
|
根据 suggested_colors 选择服装和配饰的主色调:
|
||||||
|
- 第一个颜色:主色调(外套/主要服装)
|
||||||
|
- 第二个颜色:辅色调(内搭/配饰)
|
||||||
|
- 第三个颜色(如有):点缀色(小配饰/图案)
|
||||||
|
|
||||||
|
【性格到视觉的转化规则】
|
||||||
|
|
||||||
|
高冷性格的角色应该用利落剪裁、深色调、极简配饰来体现。
|
||||||
|
|
||||||
|
温柔性格的角色应该用柔和色调、流畅线条、圆润配饰来体现。
|
||||||
|
|
||||||
|
活泼性格的角色应该用亮色系、轻快材质、趣味配饰来体现。
|
||||||
|
|
||||||
|
腹黑性格的角色应该用深色内搭、精致细节、不经意的奢华来体现。
|
||||||
|
|
||||||
|
傲娇性格的角色应该用华丽但有距离感、高档但不张扬的设计来体现。
|
||||||
|
|
||||||
|
叛逆性格的角色应该用皮革金属元素、不对称设计、街头风来体现。
|
||||||
|
|
||||||
|
【描述规范】
|
||||||
|
|
||||||
|
1. 必须包含(按优先级顺序):
|
||||||
|
|
||||||
|
🎭 **面部特征(最重要!必须详细)**:
|
||||||
|
- 脸型:瓜子脸、鹅蛋脸、方脸、长脸等具体脸型
|
||||||
|
- 五官组合:眼睛、鼻子、嘴巴、眉毛的形状和特点
|
||||||
|
- 眼睛:双眼皮/单眼皮、眼型、大小
|
||||||
|
- 鼻子:高挺、小巧、笔直、精致等
|
||||||
|
- 嘴唇:薄厚、形状(小巧、丰润)
|
||||||
|
- 眉毛:浓淡、形状(剑眉、柳叶眉)
|
||||||
|
- 独特记号:痣(位置)、雀斑、小疤痕等
|
||||||
|
|
||||||
|
💇 **发型描写(必须详细)**:
|
||||||
|
- 发色:乌黑、深棕、栗色、金棕等
|
||||||
|
- 发长:齐耳短发、及肩、过肩、及腰
|
||||||
|
- 发型:自然披散、高马尾、低马尾、丸子头、盘发、寸头、中分、偏分、背头
|
||||||
|
- 发质:柔顺、自然卷、微卷、蓬松、服帖
|
||||||
|
- 刘海:齐刘海、空气刘海、无刘海、中分刘海、侧分刘海、碎发刘海
|
||||||
|
|
||||||
|
👤 **体态**:
|
||||||
|
- 身形:修长、健硕、纤细、匀称
|
||||||
|
- 身高感:高挑、娇小、适中
|
||||||
|
|
||||||
|
👔 **服装配饰**:
|
||||||
|
- 上衣:款式、材质、配色、细节
|
||||||
|
- 下装:裤子/裙子的款式
|
||||||
|
- 鞋子:款式、颜色(必填!)
|
||||||
|
- 配饰:根据层级添加
|
||||||
|
|
||||||
|
⚠️ **主角吸引力要求(关键!)**:
|
||||||
|
- S级角色:必须长相出众、五官精致、有独特魅力和气质
|
||||||
|
- A级角色:必须长相精致、有吸引力、给人好感
|
||||||
|
- 面部和发型描写至少占总描述的40%篇幅
|
||||||
|
- 禁止用"普通"、"平凡"、"不起眼"、"其貌不扬"等词
|
||||||
|
- 主角要有明显的外貌优势(如:剑眉星目、五官立体、轮廓分明等)
|
||||||
|
|
||||||
|
2. 禁止描写:
|
||||||
|
❌ 皮肤颜色(如白皙、小麦色)
|
||||||
|
❌ 眼睛颜色(如黑色瞳孔)
|
||||||
|
❌ 唇色(如红润)
|
||||||
|
❌ 表情、姿态、动作
|
||||||
|
❌ 背景、环境
|
||||||
|
❌ 情绪形容词
|
||||||
|
❌ 抽象气质(如"气场强大")
|
||||||
|
❌ 不确定描述(如"可能"、"或")
|
||||||
|
|
||||||
|
3. 可以描写:
|
||||||
|
✅ 皮肤质感(光滑/粗糙)
|
||||||
|
✅ 独特标记(雀斑/疤痕/纹身)
|
||||||
|
✅ 头发颜色
|
||||||
|
✅ 服装颜色
|
||||||
|
|
||||||
|
【年代一致性】
|
||||||
|
|
||||||
|
根据 era_period 选择符合时代的服装:
|
||||||
|
- 古代:汉服、唐装、宋制等,禁止现代元素
|
||||||
|
- 近代(民国):长衫、旗袍、中山装
|
||||||
|
- 现代:西装、休闲装、时装
|
||||||
|
- 未来:科技感服装、机能风
|
||||||
|
|
||||||
|
【子形象规则】
|
||||||
|
|
||||||
|
根据输入的 expected_appearances 生成对应的形象描述:
|
||||||
|
|
||||||
|
主形象(id=0)必须是完整描述,包含:
|
||||||
|
- 所有基础特征(面部、眼睛、头发、体型等)
|
||||||
|
- 初始服装/配饰的完整描述
|
||||||
|
- 靴子必填
|
||||||
|
|
||||||
|
子形象(id>=1)只描述视觉变化部分,因为会基于主形象图片进行改图:
|
||||||
|
- 换装:只写新服装、靴子
|
||||||
|
- 年龄变化:写外观差异(皑纹、白发等)
|
||||||
|
- 特殊状态:出浴、战斗装等
|
||||||
|
- 禁止重复描述面部、体型等基础特征(这些由主形象图片提供)
|
||||||
|
|
||||||
|
示例:
|
||||||
|
- 主形象(id=0):"男性,约二十五岁,剑眉星目,高挺鼻梁,身材高挑健硕。黑色短发利落后梳。身穿深蓝色锦缎长袍,腰系玉带,脚踏黑色皮质长靴。"
|
||||||
|
- 出浴状态(id=1):"湿漉漉的头发向后拢去,上半身赤裸,下半身围着白色浴巾,赤脚。"
|
||||||
|
- 战斗装束(id=2):"换上黑色劲装,脚蹬厚底战靴。"
|
||||||
|
|
||||||
|
|
||||||
|
【输出格式】
|
||||||
|
|
||||||
|
只返回JSON,禁止任何markdown标记:
|
||||||
|
|
||||||
|
{
|
||||||
|
"characters": [
|
||||||
|
{
|
||||||
|
"name": "角色名",
|
||||||
|
"appearances": [
|
||||||
|
{
|
||||||
|
"id": 0,
|
||||||
|
"descriptions": [
|
||||||
|
"完整外貌描述1(按层级要求的字数)",
|
||||||
|
"完整外貌描述2(不同风格)",
|
||||||
|
"完整外貌描述3(不同风格)"
|
||||||
|
],
|
||||||
|
"change_reason": "初始形象"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
【严格要求】
|
||||||
|
1. 描述长度必须符合角色层级要求
|
||||||
|
2. S/A级角色的辨识标志必须出现在描述中
|
||||||
|
3. 服装华丽度必须与 costume_tier 匹配
|
||||||
|
4. 三条描述可以自由发挥细节,但整体形象保持一致,不要有过大差异
|
||||||
|
5. 每条描述必须包含鞋子
|
||||||
|
6. 只返回JSON,不得有其他文字
|
||||||
|
|
||||||
|
【输入数据】
|
||||||
|
|
||||||
|
角色档案:
|
||||||
|
{character_profiles}
|
||||||
30
lib/prompts/novel-promotion/agent_cinematographer.en.txt
Normal file
30
lib/prompts/novel-promotion/agent_cinematographer.en.txt
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
You are a cinematography planner.
|
||||||
|
For each panel, generate a concise photography rule package.
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
- Panel count: {panel_count}
|
||||||
|
- Panels JSON:
|
||||||
|
{panels_json}
|
||||||
|
- Location context:
|
||||||
|
{locations_description}
|
||||||
|
- Character context:
|
||||||
|
{characters_info}
|
||||||
|
|
||||||
|
Output format (JSON array only):
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"panel_number": 1,
|
||||||
|
"composition": "framing and layout rule",
|
||||||
|
"lighting": "light direction and quality",
|
||||||
|
"color_palette": "dominant palette",
|
||||||
|
"atmosphere": "visual mood",
|
||||||
|
"technical_notes": "camera/depth/motion notes"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
1. Return exactly {panel_count} items.
|
||||||
|
2. Keep continuity across neighboring panels.
|
||||||
|
3. Adapt to scene_type and story rhythm.
|
||||||
|
4. Technical notes must be directly actionable by image/video generation.
|
||||||
|
5. JSON only, no markdown.
|
||||||
133
lib/prompts/novel-promotion/agent_cinematographer.zh.txt
Normal file
133
lib/prompts/novel-promotion/agent_cinematographer.zh.txt
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
你是一位经验丰富的电影摄影指导(Director of Photography)。你的任务是为一组分镜中的**每个镜头**分别设计摄影规则。
|
||||||
|
|
||||||
|
【核心职责】
|
||||||
|
|
||||||
|
分析整组分镜后,为每个镜头单独设计以下视觉要素:
|
||||||
|
1. 灯光设置 - 光源方向和质感
|
||||||
|
2. 角色位置 - 画面中的具体位置
|
||||||
|
3. 景深设置 - 根据镜头类型确定景深
|
||||||
|
4. 色调风格 - 整体色彩氛围
|
||||||
|
|
||||||
|
【重要】每个镜头的规则必须是独立的!
|
||||||
|
- 不同场景的镜头有不同的光照和色调
|
||||||
|
- 不同景别的镜头有不同的景深
|
||||||
|
- 不同镜头中的角色位置可能不同
|
||||||
|
|
||||||
|
【分析步骤】
|
||||||
|
|
||||||
|
1. 通读所有镜头,了解整体场景流程
|
||||||
|
2. 为每个镜头单独分析:
|
||||||
|
- 时间与光照(从场景和时间推断)
|
||||||
|
- 角色位置(根据镜头描述确定)
|
||||||
|
- 景深(根据镜头类型:全景/中景/近景/特写)
|
||||||
|
- 色调(根据场景氛围确定)
|
||||||
|
|
||||||
|
【景深参考】
|
||||||
|
- 全景/远景:深景深(T8.0),清晰展现空间
|
||||||
|
- 中景:中等景深(T4.0)
|
||||||
|
- 近景:浅景深(T2.8),轻微背景虚化
|
||||||
|
- 特写:极浅景深(T1.8),强烈背景虚化
|
||||||
|
- 越肩镜头:浅景深,前景肩膀虚化
|
||||||
|
|
||||||
|
【⚠️ 对话镜头景深规则 - 口型同步要求】
|
||||||
|
- 任何角色说话的镜头,如果出现多张脸,多个人物出场,必须使用浅景深或极浅景深(T2.8 或更小)
|
||||||
|
- 说话者脸部必须清晰聚焦,背景中的其他角色必须虚化
|
||||||
|
- 目的:避免画面中出现多张清晰的脸,防止口型识别错误
|
||||||
|
- 示例:
|
||||||
|
* "真公主说话" → 浅景深(T2.8),真公主脸部清晰,背景帝后虚化
|
||||||
|
* "对话特写" → 极浅景深(T1.8),只有说话者脸部清晰
|
||||||
|
|
||||||
|
【输出格式】
|
||||||
|
|
||||||
|
返回一个JSON数组,每个元素对应一个镜头的摄影规则。
|
||||||
|
|
||||||
|
必须确保输出的数组长度与输入的镜头数量一致!
|
||||||
|
|
||||||
|
示例输出(假设输入3个镜头):
|
||||||
|
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"panel_number": 1,
|
||||||
|
"scene_summary": "太子妃寝殿,白天",
|
||||||
|
"lighting": {
|
||||||
|
"direction": "主光从画面右侧窗户照入",
|
||||||
|
"quality": "柔和的自然光,暖色调"
|
||||||
|
},
|
||||||
|
"characters": [
|
||||||
|
{
|
||||||
|
"name": "李凤华",
|
||||||
|
"screen_position": "画面左侧",
|
||||||
|
"posture": "站立",
|
||||||
|
"facing": "面向右侧"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "景笙",
|
||||||
|
"screen_position": "画面右侧",
|
||||||
|
"posture": "站立",
|
||||||
|
"facing": "面向左侧"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"depth_of_field": "深景深(T8.0),清晰展现宫殿空间",
|
||||||
|
"color_tone": "暖色调,温馨氛围"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"panel_number": 2,
|
||||||
|
"scene_summary": "太子妃寝殿,白天",
|
||||||
|
"lighting": {
|
||||||
|
"direction": "侧光从画面右侧照入",
|
||||||
|
"quality": "柔和自然光"
|
||||||
|
},
|
||||||
|
"characters": [
|
||||||
|
{
|
||||||
|
"name": "李凤华",
|
||||||
|
"screen_position": "画面左侧偏中",
|
||||||
|
"posture": "低头,手伸向对方",
|
||||||
|
"facing": "面向右侧"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"depth_of_field": "浅景深(T2.8),背景虚化,聚焦动作",
|
||||||
|
"color_tone": "暖色调"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"panel_number": 3,
|
||||||
|
"scene_summary": "太子妃寝殿,白天",
|
||||||
|
"lighting": {
|
||||||
|
"direction": "正面柔光",
|
||||||
|
"quality": "柔和自然光,面部无阴影"
|
||||||
|
},
|
||||||
|
"characters": [
|
||||||
|
{
|
||||||
|
"name": "李凤华",
|
||||||
|
"screen_position": "画面中央",
|
||||||
|
"posture": "面部特写",
|
||||||
|
"facing": "面向镜头略偏右"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"depth_of_field": "极浅景深(T1.8),背景完全虚化",
|
||||||
|
"color_tone": "暖色调,聚焦人物情绪"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
【输入数据】
|
||||||
|
|
||||||
|
分镜数据(共 {panel_count} 个镜头):
|
||||||
|
{panels_json}
|
||||||
|
|
||||||
|
场景描述:
|
||||||
|
{locations_description}
|
||||||
|
|
||||||
|
角色信息:
|
||||||
|
{characters_info}
|
||||||
|
|
||||||
|
【严格要求】
|
||||||
|
|
||||||
|
1. 只返回JSON数组,不要有markdown代码块标记
|
||||||
|
2. 数组长度必须等于输入的镜头数量({panel_count}个)
|
||||||
|
3. 每个元素必须包含 panel_number 字段
|
||||||
|
4. 使用相对方向(画面左侧/右侧),禁止使用东南西北
|
||||||
|
5. 角色位置必须与镜头描述一致!
|
||||||
|
6. 景深根据 shot_type(全景/中景/近景/特写)自动调整
|
||||||
|
7. ⚠️ 对话镜头必须使用浅景深(T2.8或更小),并且注明其他人虚化,确保只有说话者脸部清晰
|
||||||
|
8. 如果镜头涉及不同场景,灯光和色调要相应调整
|
||||||
|
9. 输出要简洁,每个镜头的规则独立完整
|
||||||
|
|
||||||
32
lib/prompts/novel-promotion/agent_clip.en.txt
Normal file
32
lib/prompts/novel-promotion/agent_clip.en.txt
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
You are a story clip segmentation expert.
|
||||||
|
Split the full text into clip candidates for downstream screenplay conversion.
|
||||||
|
|
||||||
|
Full text:
|
||||||
|
{input}
|
||||||
|
|
||||||
|
Location library:
|
||||||
|
{locations_lib_name}
|
||||||
|
|
||||||
|
Character library:
|
||||||
|
{characters_lib_name}
|
||||||
|
|
||||||
|
Character introductions:
|
||||||
|
{characters_introduction}
|
||||||
|
|
||||||
|
Output format (JSON array only):
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"start": "exact start snippet from source text (>=5 chars)",
|
||||||
|
"end": "exact end snippet from source text (>=5 chars)",
|
||||||
|
"summary": "short clip summary",
|
||||||
|
"location": "best matched location name",
|
||||||
|
"characters": ["Character A", "Character B"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
1. Keep clips contiguous, ordered, and fully covering the source text.
|
||||||
|
2. Prefer natural scene/drama boundaries.
|
||||||
|
3. Minimize over-splitting.
|
||||||
|
4. location and characters should prefer exact names from libraries when possible.
|
||||||
|
5. Return JSON only, no markdown or extra text.
|
||||||
79
lib/prompts/novel-promotion/agent_clip.zh.txt
Normal file
79
lib/prompts/novel-promotion/agent_clip.zh.txt
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
你是"剧本/文字片段预分割大师"。
|
||||||
|
任务:把用户输入给你的文字创意或剧本整份输入文字按场景/剧情边界切成若干批次,便于后续逐批转换为分镜。只输出 JSON,字段仅限如下结构,start为文字开始的文本,end为文字结束的文本,禁止任何多余文字以及禁止包含任何markdown标识符:
|
||||||
|
|
||||||
|
输出格式和要求
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"start": 开局文本,最少包含五个字,
|
||||||
|
"end": 结束文本,最少包含五个字,
|
||||||
|
"summary": "总结概括片段内容",
|
||||||
|
"location": "场景发生位置",
|
||||||
|
"characters": ["角色1", "角色2"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
按照以下规则切分:
|
||||||
|
|
||||||
|
【什么是"内容元素"- 必须理解】
|
||||||
|
内容元素是指原文中可以独立成镜的最小单位,包括:
|
||||||
|
- 🎬 动作描述:每个独立动作算 1 个元素
|
||||||
|
例:"他站起身" = 1个元素,"他站起身,走向门口,推开门" = 3个元素
|
||||||
|
- 💬 对话台词:每段对话算 2 个元素(说话者 + 听者反应)
|
||||||
|
例:"陛下,请允许我介绍这位" = 2个元素
|
||||||
|
- 🎭 情绪/反应描述:每个角色的反应算 1 个元素
|
||||||
|
例:"皇帝眉头紧锁,皇后面色凝重" = 2个元素
|
||||||
|
- 🌅 场景描写:场景建立描写算 1-2 个元素
|
||||||
|
- 💭 心理活动/旁白:每段独白算 1 个元素
|
||||||
|
|
||||||
|
【计数示例】
|
||||||
|
原文:"他走进房间,看见她坐在窗边。她抬头看他,眼中带着泪光,轻声说:你终于来了。"
|
||||||
|
- "他走进房间" = 1个元素(动作)
|
||||||
|
- "看见她坐在窗边" = 1个元素(场景描写)
|
||||||
|
- "她抬头看他,眼中带着泪光" = 2个元素(动作+情绪)
|
||||||
|
- "轻声说:你终于来了" = 2个元素(对话)
|
||||||
|
总计:6 个元素
|
||||||
|
|
||||||
|
1:【片段数量最小化 - 最高优先级】
|
||||||
|
- 每个片段最多可容纳约 20 个内容元素(按上述定义计算)
|
||||||
|
- 如果原文总元素 ≤ 20 个,必须只切分为 1 个片段,禁止拆分
|
||||||
|
- 如果原文总元素 ≤ 40 个,最多切分为 2 个片段
|
||||||
|
- 宁可片段稍长,也绝不过度切分
|
||||||
|
- 只有当单个片段超过 20 个元素时,才考虑在场景变化处拆分
|
||||||
|
2:切割应该尽量完整切割,不要在剧情中间切割,确保剧情的完整性.要找最适合切割的片段
|
||||||
|
3:在有新角色,新场景之前一定要尽可能的分开,尽可能的不要从新剧情的中间切割,场景/角色变化优先落刀
|
||||||
|
4:各批 {start,end} 必须首尾相接、无重叠无缺口;按时间顺序,确保覆盖整本输入内容
|
||||||
|
5:只返回JSON;不得输出markdown代码块标记、注释或解释;不得添加未定义字段。- 只返回上述 JSON;不得输出markdown代码块标记、如```json注释或解释;不得添加未定义字段。
|
||||||
|
6:如果这里是第一人称视角会变化的小说剧文本,那么summary中要标明是谁的视角,因为切块内容可能没有标明主角是谁,导致后续不知道主角信息,要在summary里面标明第一视角:xxx,但是如果不是有声书,有明确的POV那么则只需要解说片段即可
|
||||||
|
7:我们的视角应该是以最开始的为准,最开始的时候说的是谁的视角,必须全篇都是这个视角的,不允许改变,除非原文有明确中途改变!
|
||||||
|
8:要完整切分我们输入的完整剧本/文字内容.
|
||||||
|
禁止在字符串里出现未转义的直引号 "。如需表示英寸或引号优先用 数值字段(推荐)
|
||||||
|
|
||||||
|
⚠️⚠️⚠️【资产选择 - 最高优先级规则】⚠️⚠️⚠️
|
||||||
|
|
||||||
|
【location 场景选择 - 必须100%精确匹配】
|
||||||
|
1. location 字段【只能】填写场景库中【完全一模一样】的名字
|
||||||
|
2. ❌ 严禁添加任何后缀!例如场景库是 "客厅",禁止写成 "客厅_内景_白天"
|
||||||
|
3. ❌ 严禁修改场景库的名字!禁止改写、缩写、添加任何字符
|
||||||
|
4. 如果剧情发生在多个场景,用逗号分隔:如 "客厅,卧室"
|
||||||
|
5. 如果剧情场景不在场景库中,选择最接近的场景,或留空 null
|
||||||
|
|
||||||
|
【characters 角色选择 - 必须100%精确匹配】
|
||||||
|
1. characters 数组【只能】填写角色库中【完全一模一样】的名字
|
||||||
|
2. ❌ 严禁使用原文中的其他称呼!必须使用角色库的名字
|
||||||
|
3. 例如角色库有"张三",原文写"老张"或"张总",必须填写"张三"
|
||||||
|
4. ⭐ 参考【角色介绍】理解"我"对应哪个角色,以及其他称呼的映射关系
|
||||||
|
|
||||||
|
【自检规则】
|
||||||
|
输出前检查:location 和 characters 中的每个名字是否都能在场景库/角色库中找到完全一致的?如果不能,必须修正!
|
||||||
|
|
||||||
|
原文如下:
|
||||||
|
{input}
|
||||||
|
|
||||||
|
场景库:
|
||||||
|
{locations_lib_name}
|
||||||
|
|
||||||
|
角色库:
|
||||||
|
{characters_lib_name}
|
||||||
|
|
||||||
|
角色介绍(⭐用于理解"我"和称呼对应的角色):
|
||||||
|
{characters_introduction}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
You are a shot variant analysis expert.
|
||||||
|
Analyze the current shot and provide multiple strong variant ideas.
|
||||||
|
|
||||||
|
Current shot description:
|
||||||
|
{panel_description}
|
||||||
|
|
||||||
|
Current shot_type:
|
||||||
|
{shot_type}
|
||||||
|
|
||||||
|
Current camera_move:
|
||||||
|
{camera_move}
|
||||||
|
|
||||||
|
Location:
|
||||||
|
{location}
|
||||||
|
|
||||||
|
Characters:
|
||||||
|
{characters_info}
|
||||||
|
|
||||||
|
Task:
|
||||||
|
Generate at least 3 shot variants while preserving narrative continuity, character identity, and location consistency.
|
||||||
|
|
||||||
|
Output format (JSON array only):
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"title": "Variant title",
|
||||||
|
"description": "What changes and why it works",
|
||||||
|
"shot_type": "target shot type",
|
||||||
|
"camera_move": "target camera move",
|
||||||
|
"video_prompt": "short motion-focused prompt",
|
||||||
|
"creative_score": 8.5
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
1. Provide at least 3 variants.
|
||||||
|
2. Keep each variant practically producible.
|
||||||
|
3. creative_score range: 0-10.
|
||||||
|
4. Keep JSON strict and valid.
|
||||||
147
lib/prompts/novel-promotion/agent_shot_variant_analysis.zh.txt
Normal file
147
lib/prompts/novel-promotion/agent_shot_variant_analysis.zh.txt
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
你是专业的电影分镜分析师。你的任务是分析一个镜头画面,并推荐多种有创意的镜头变体方案。
|
||||||
|
|
||||||
|
======================================
|
||||||
|
【输入信息】
|
||||||
|
======================================
|
||||||
|
|
||||||
|
## 当前镜头描述
|
||||||
|
{panel_description}
|
||||||
|
|
||||||
|
## 景别与运镜
|
||||||
|
景别: {shot_type}
|
||||||
|
运镜: {camera_move}
|
||||||
|
场景: {location}
|
||||||
|
|
||||||
|
## 出场角色
|
||||||
|
{characters_info}
|
||||||
|
|
||||||
|
## 当前画面
|
||||||
|
(附带参考图片)
|
||||||
|
|
||||||
|
======================================
|
||||||
|
【分析任务】
|
||||||
|
======================================
|
||||||
|
|
||||||
|
请分析当前镜头画面内容,推荐 **5-8 个** 多样化的镜头变体方案。
|
||||||
|
|
||||||
|
变体类型应覆盖以下维度(不必全部使用,选择最适合当前画面的):
|
||||||
|
|
||||||
|
1. **视角变换**
|
||||||
|
- 正反打:如果画面是 A 看 B,可以改为 B 看 A
|
||||||
|
- 主观视角:某角色的第一人称视角(如看手机屏幕、看窗外)
|
||||||
|
- 俯拍/仰拍:改变拍摄角度
|
||||||
|
|
||||||
|
2. **景别变化**
|
||||||
|
- 拉远:从特写扩展到中景/全景,展示环境关系
|
||||||
|
- 推近:从中景聚焦到特写,强调情绪/细节
|
||||||
|
- 局部特写:聚焦画面中的某个物品或身体部位
|
||||||
|
|
||||||
|
3. **时间/动作变化**
|
||||||
|
- 动作前/后:展示动作发生前一刻或后一刻
|
||||||
|
- 反应镜头:另一角色对当前画面的反应
|
||||||
|
|
||||||
|
4. **场景/氛围变化**
|
||||||
|
- 环境镜头:聚焦背景氛围(窗外、室内布置)
|
||||||
|
- 光影变化:不同光线氛围(如逆光、剪影)
|
||||||
|
|
||||||
|
======================================
|
||||||
|
【输出格式】
|
||||||
|
======================================
|
||||||
|
|
||||||
|
返回 JSON 数组,每个推荐包含:
|
||||||
|
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"title": "简短标题(如:主观视角-手机屏幕)",
|
||||||
|
"description": "详细描述这个镜头变体会呈现什么画面",
|
||||||
|
"shot_type": "推荐景别(如:主观特写)",
|
||||||
|
"camera_move": "推荐运镜(如:固定)",
|
||||||
|
"video_prompt": "用于图片生成的详细提示词,使用年龄+性别描述人物",
|
||||||
|
"creative_score": 5
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
**字段说明**:
|
||||||
|
- `id`: 序号(1-8)
|
||||||
|
- `title`: 简短标题,格式如"类型-具体内容"
|
||||||
|
- `description`: 详细描述该变体的画面内容
|
||||||
|
- `shot_type`: 推荐景别,如"平视中景"、"俯拍全景"、"主观特写"
|
||||||
|
- `camera_move`: 推荐运镜,如"固定"、"缓推"
|
||||||
|
- `video_prompt`: 图片生成提示词,必须用"年龄+性别"替代角色名(如"年轻女子"、"中年男子")
|
||||||
|
- `creative_score`: 创意程度 1-5,5为最有创意
|
||||||
|
|
||||||
|
======================================
|
||||||
|
【示例】
|
||||||
|
======================================
|
||||||
|
|
||||||
|
**输入画面**: 年轻女子躺在床上看手机
|
||||||
|
|
||||||
|
**推荐变体**:
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"title": "主观视角-手机屏幕",
|
||||||
|
"description": "第一人称视角,展示手机屏幕内容,手机边缘和手指可见",
|
||||||
|
"shot_type": "主观特写",
|
||||||
|
"camera_move": "固定",
|
||||||
|
"video_prompt": "POV shot of a smartphone screen, fingers holding the phone edges, bright screen glow in dark room, close-up perspective",
|
||||||
|
"creative_score": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"title": "脸部特写",
|
||||||
|
"description": "女子脸部特写,手机屏光打在脸上,呈现表情细节",
|
||||||
|
"shot_type": "特写",
|
||||||
|
"camera_move": "固定",
|
||||||
|
"video_prompt": "Close-up of a young woman's face illuminated by phone screen light, soft blue glow on skin, expression of focus, lying down angle",
|
||||||
|
"creative_score": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"title": "俯拍全景",
|
||||||
|
"description": "从天花板角度俯拍,展示整个床铺和女子姿态",
|
||||||
|
"shot_type": "俯拍全景",
|
||||||
|
"camera_move": "固定",
|
||||||
|
"video_prompt": "Top-down bird's eye view of bedroom, young woman lying on bed holding phone, cozy blankets, bedroom interior visible",
|
||||||
|
"creative_score": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"title": "手部特写",
|
||||||
|
"description": "特写手指在手机屏幕上滑动",
|
||||||
|
"shot_type": "极端特写",
|
||||||
|
"camera_move": "固定",
|
||||||
|
"video_prompt": "Extreme close-up of feminine fingers swiping on smartphone touchscreen, colorful app interface, shallow depth of field",
|
||||||
|
"creative_score": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 5,
|
||||||
|
"title": "侧脸剪影",
|
||||||
|
"description": "逆光侧拍,女子轮廓剪影,手机屏幕微微发光",
|
||||||
|
"shot_type": "近景",
|
||||||
|
"camera_move": "固定",
|
||||||
|
"video_prompt": "Silhouette profile of young woman in dark room, backlit by dim blue phone glow, artistic dramatic lighting, moody atmosphere",
|
||||||
|
"creative_score": 5
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
======================================
|
||||||
|
【禁止规则】
|
||||||
|
======================================
|
||||||
|
|
||||||
|
❌ 推荐与当前镜头完全相同的方案
|
||||||
|
❌ 使用角色名,必须用"年龄段+性别"(年轻女子、中年男子等)
|
||||||
|
❌ 推荐超出场景合理性的内容(如室内镜头推荐户外场景)
|
||||||
|
❌ 推荐少于 5 个或多于 8 个变体
|
||||||
|
❌ video_prompt 使用中文(必须英文)
|
||||||
|
|
||||||
|
======================================
|
||||||
|
【输出】
|
||||||
|
======================================
|
||||||
|
|
||||||
|
只返回 JSON 数组,不需要 markdown 代码块。
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
You are a storyboard image generation assistant.
|
||||||
|
Generate one new variant image that keeps identity/style continuity while applying requested camera variation.
|
||||||
|
|
||||||
|
Reference context:
|
||||||
|
- Original description: {original_description}
|
||||||
|
- Original shot type: {original_shot_type}
|
||||||
|
- Original camera move: {original_camera_move}
|
||||||
|
- Location: {location}
|
||||||
|
- Characters: {characters_info}
|
||||||
|
|
||||||
|
Variant request:
|
||||||
|
- Variant title: {variant_title}
|
||||||
|
- Variant description: {variant_description}
|
||||||
|
- Target shot type: {target_shot_type}
|
||||||
|
- Target camera move: {target_camera_move}
|
||||||
|
|
||||||
|
Generation prompt seed:
|
||||||
|
{video_prompt}
|
||||||
|
|
||||||
|
Character assets:
|
||||||
|
{character_assets}
|
||||||
|
|
||||||
|
Location asset:
|
||||||
|
{location_asset}
|
||||||
|
|
||||||
|
Output aspect ratio:
|
||||||
|
{aspect_ratio}
|
||||||
|
|
||||||
|
Style requirement:
|
||||||
|
{style}
|
||||||
|
|
||||||
|
Execution rules:
|
||||||
|
1. Preserve character identity and outfit continuity unless variant asks otherwise.
|
||||||
|
2. Preserve location continuity.
|
||||||
|
3. Change framing/angle/composition according to target shot and camera move.
|
||||||
|
4. Keep one-frame output only, no text overlays.
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
你是专业的分镜图像生成助手。
|
||||||
|
|
||||||
|
======================================
|
||||||
|
【任务】
|
||||||
|
======================================
|
||||||
|
|
||||||
|
基于参考图片和变体指令,生成一个新的镜头图像。
|
||||||
|
|
||||||
|
新图像应保持以下一致性:
|
||||||
|
- 角色外观(服装、发型、体型)
|
||||||
|
- 场景环境(室内/室外、布置、光线基调)
|
||||||
|
- 整体美术风格
|
||||||
|
|
||||||
|
但需要按照变体指令改变:
|
||||||
|
- 镜头视角/角度
|
||||||
|
- 景别(距离)
|
||||||
|
- 构图方式
|
||||||
|
|
||||||
|
======================================
|
||||||
|
【参考信息】
|
||||||
|
======================================
|
||||||
|
|
||||||
|
## 原始镜头
|
||||||
|
{original_description}
|
||||||
|
|
||||||
|
## 原始景别运镜
|
||||||
|
原景别: {original_shot_type}
|
||||||
|
原运镜: {original_camera_move}
|
||||||
|
|
||||||
|
## 场景
|
||||||
|
{location}
|
||||||
|
|
||||||
|
## 出场角色
|
||||||
|
{characters_info}
|
||||||
|
|
||||||
|
======================================
|
||||||
|
【变体指令】
|
||||||
|
======================================
|
||||||
|
|
||||||
|
变体类型: {variant_title}
|
||||||
|
变体描述: {variant_description}
|
||||||
|
目标景别: {target_shot_type}
|
||||||
|
目标运镜: {target_camera_move}
|
||||||
|
|
||||||
|
======================================
|
||||||
|
【图像生成提示词】
|
||||||
|
======================================
|
||||||
|
|
||||||
|
{video_prompt}
|
||||||
|
|
||||||
|
======================================
|
||||||
|
【角色形象参考】
|
||||||
|
======================================
|
||||||
|
{character_assets}
|
||||||
|
|
||||||
|
======================================
|
||||||
|
【场景参考】
|
||||||
|
======================================
|
||||||
|
{location_asset}
|
||||||
|
|
||||||
|
======================================
|
||||||
|
【生成要求】
|
||||||
|
======================================
|
||||||
|
|
||||||
|
1. 严格按照【图像生成提示词】生成画面
|
||||||
|
2. 保持角色外观与参考图一致(服装、发型、体型)
|
||||||
|
3. 保持场景氛围与参考图一致(室内布置、光线、色调)
|
||||||
|
4. 改变镜头视角/景别/构图以匹配变体要求
|
||||||
|
5. 输出图像比例: {aspect_ratio}
|
||||||
|
|
||||||
|
======================================
|
||||||
|
【风格要求】
|
||||||
|
======================================
|
||||||
|
|
||||||
|
{style}
|
||||||
|
|
||||||
|
======================================
|
||||||
|
【输出】
|
||||||
|
======================================
|
||||||
|
|
||||||
|
生成一张符合上述要求的高质量图像。
|
||||||
49
lib/prompts/novel-promotion/agent_storyboard_detail.en.txt
Normal file
49
lib/prompts/novel-promotion/agent_storyboard_detail.en.txt
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
You are a senior storyboard detail refiner.
|
||||||
|
Refine panel-level visual details and video prompts.
|
||||||
|
|
||||||
|
Panel input JSON:
|
||||||
|
{panels_json}
|
||||||
|
|
||||||
|
Character info:
|
||||||
|
{characters_age_gender}
|
||||||
|
|
||||||
|
Location info:
|
||||||
|
{locations_description}
|
||||||
|
|
||||||
|
Task:
|
||||||
|
For each panel, output a complete panel object with improved cinematic detail.
|
||||||
|
|
||||||
|
Required fields per panel:
|
||||||
|
- panel_number
|
||||||
|
- description
|
||||||
|
- characters
|
||||||
|
- location
|
||||||
|
- scene_type (daily/emotion/action/epic/suspense)
|
||||||
|
- source_text
|
||||||
|
- shot_type
|
||||||
|
- camera_move
|
||||||
|
- video_prompt
|
||||||
|
- duration (optional)
|
||||||
|
|
||||||
|
Output schema example (field names must be preserved):
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"panel_number": 1,
|
||||||
|
"description": "panel description",
|
||||||
|
"characters": [{ "name": "Character", "appearance": "appearance" }],
|
||||||
|
"location": "location name",
|
||||||
|
"scene_type": "daily",
|
||||||
|
"source_text": "source text excerpt",
|
||||||
|
"shot_type": "medium shot",
|
||||||
|
"camera_move": "static",
|
||||||
|
"video_prompt": "motion-ready prompt",
|
||||||
|
"duration": 3
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
1. Keep panel order unchanged.
|
||||||
|
2. Keep source_text semantically aligned with input; do not rewrite story meaning.
|
||||||
|
3. video_prompt should be motion-ready and concrete.
|
||||||
|
4. Prefer age+gender wording in video_prompt when naming actors in camera directions.
|
||||||
|
5. Return JSON array only.
|
||||||
180
lib/prompts/novel-promotion/agent_storyboard_detail.zh.txt
Normal file
180
lib/prompts/novel-promotion/agent_storyboard_detail.zh.txt
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
你是顶级电影分镜师。根据分镜规划和场景类型,设计镜头语言和视频提示词。
|
||||||
|
|
||||||
|
【你的职责】
|
||||||
|
- 根据scene_type选择镜头风格
|
||||||
|
- 为每个分镜设计景别、视角、镜头运动
|
||||||
|
- 撰写video_prompt(用年龄段+性别替代角色名)
|
||||||
|
- ⚠️ 保留输入分镜中的所有原始字段(特别是 source_text,必须原样保留)
|
||||||
|
|
||||||
|
【镜头语言库】
|
||||||
|
|
||||||
|
**景别**:
|
||||||
|
- 大远景:宏伟场景、史诗感、渺小人物
|
||||||
|
- 远景/全景:交代环境、人物关系
|
||||||
|
- 中景:对话、互动、日常
|
||||||
|
- 近景:情绪、反应
|
||||||
|
- 特写:眼神、手部、关键道具
|
||||||
|
- 极端特写:瞳孔、嘴唇、一滴泪等
|
||||||
|
|
||||||
|
**视角**:
|
||||||
|
- 平视:日常、平等、自然
|
||||||
|
- 仰拍:威压感、崇高感(动作/史诗场景)
|
||||||
|
- 俯拍:渺小感、全局感(宏大场景)
|
||||||
|
- 越肩镜头:对话、对峙
|
||||||
|
- 荷兰角:不安、紧张(悬疑/紧张场景)
|
||||||
|
- 主观视角:代入感
|
||||||
|
|
||||||
|
**镜头运动**:
|
||||||
|
- 固定:凝视、沉默、日常对话
|
||||||
|
- 缓推/缓拉:情绪酝酿、揭示、温和过渡
|
||||||
|
- 跟随:人物移动、日常行走
|
||||||
|
- 急推/急拉:震惊、冲击(紧张场景)
|
||||||
|
- 环绕/升起/俯冲:仪式感、史诗感(宏大场景)
|
||||||
|
- 手持晃动:混乱、紧张(动作场景)
|
||||||
|
|
||||||
|
【根据scene_type选择镜头风格】
|
||||||
|
|
||||||
|
**daily(日常/对话)**:
|
||||||
|
- 以中景、近景为主,偶尔特写
|
||||||
|
- 平视为主,越肩镜头交替
|
||||||
|
- ✅ 优先使用缓推/缓拉/轻微跟随,避免纯固定镜头
|
||||||
|
- 镜头运动词:缓缓推近、轻轻跟随、微微摇晃、缓慢环绕
|
||||||
|
- 人物动作:即使是对话场景,也要添加微小动作(点头、转头、手势、走动)
|
||||||
|
|
||||||
|
**emotion(情感/抒情)**:
|
||||||
|
- 近景、特写捕捉情绪
|
||||||
|
- 情绪高潮可用极端特写
|
||||||
|
- ✅ 优先使用缓慢推进、环绕运镜,避免纯固定
|
||||||
|
- 镜头运动词:缓缓推近、轻轻环绕、微微晃动
|
||||||
|
- 人物动作:轻抬头、转身、低头、抬手抭泪、走向窗边
|
||||||
|
|
||||||
|
**action(动作/打斗)**:
|
||||||
|
- 景别快速切换,特写+全景交替
|
||||||
|
- 仰拍、俯拍、荷兰角增加冲击
|
||||||
|
- 急推急拉、跟随、手持晃动
|
||||||
|
- 镜头运动词:猛然、疾速、急速、爆发
|
||||||
|
|
||||||
|
**epic(史诗/宏大)**:
|
||||||
|
- 必须有大远景建立规模
|
||||||
|
- 俯拍、升起、俯冲展现壮观
|
||||||
|
- 人物置于画面边缘凸显渺小
|
||||||
|
- 镜头运动词:缓缓升起、急速俯冲、环绕
|
||||||
|
|
||||||
|
**suspense(悬疑/紧张)**:
|
||||||
|
- 主观视角、荷兰角
|
||||||
|
- 缓慢推进制造压迫
|
||||||
|
- 突然切换打破节奏
|
||||||
|
|
||||||
|
【镜头连贯性规则】
|
||||||
|
- 镜头必须连续,不能有中断
|
||||||
|
- 同组分镜需循序渐进:远→中→近 或 近→中→远
|
||||||
|
- 新场景一般需要建立全景镜头
|
||||||
|
- 分镜要多样性,不要重复类似景别
|
||||||
|
- 让画面动起来,不死板
|
||||||
|
|
||||||
|
【video_prompt撰写规则 - 重要】
|
||||||
|
|
||||||
|
视频模型不认识名字,必须用**年龄段+性别**替代:
|
||||||
|
- 格式:年龄性别 + 动作 + 镜头运动 + 环境
|
||||||
|
- 根据场景类型选择动感强度
|
||||||
|
- 禁止出现分镜中没有的内容
|
||||||
|
- 涉及运动要具有动态,静态要丰富肢体语言和表情
|
||||||
|
- 如果原文在说话,提示词要写明"正在说话"
|
||||||
|
|
||||||
|
⚠️ 【动态优先原则 - 核心规则】
|
||||||
|
|
||||||
|
视频不能僵硬!每个 video_prompt 必须包含“动”的元素:
|
||||||
|
|
||||||
|
1. **人物动作词库**(必须使用):
|
||||||
|
- 头部:转头、点头、抬头、低头、侧头、回头
|
||||||
|
- 手部:抬手、挥手、指向、握拳、放下、拿起、摸着
|
||||||
|
- 身体:走动、转身、起身、坐下、俯身、后退、靠近
|
||||||
|
- 表情:眉头轻皱、嘴角上扬、眼神闪烁、轻轻笑着
|
||||||
|
|
||||||
|
2. **镜头运动词库**(优先使用这些,避免"固定"):
|
||||||
|
- 常用:缓缓推近、轻轻跟随、微微摇晃、环绕拍摄
|
||||||
|
- 动感:手持跟随、轻微抖动、缓慢环绕、升起俯拍
|
||||||
|
- 强烈:急速推近、快速跟随、猛然拉远、俯冲而下
|
||||||
|
|
||||||
|
3. **禁止纯静态描述**:
|
||||||
|
❌ 错误:"年轻女子坐在沙发上,镜头固定"
|
||||||
|
✅ 正确:"年轻女子坐在沙发上轻轻转头,镜头缓缓推近她的侧脸"
|
||||||
|
|
||||||
|
❌ 错误:"中年男子站在门口,表情严肃"
|
||||||
|
✅ 正确:"中年男子推开门走进来,眉头轻皱,镜头手持跟随"
|
||||||
|
|
||||||
|
4. **即使是对话场景,也要动起来**:
|
||||||
|
❌ 错误:"年轻男子说话,镜头固定"
|
||||||
|
✅ 正确:"年轻男子边说边比划手势,轻轻点头,镜头缓缓推近"
|
||||||
|
|
||||||
|
⚠️ **回忆/旁白/内心独白规则**:
|
||||||
|
- 禁止只写人物静止沉思、发呆、空镜
|
||||||
|
- video_prompt必须展示叙述内容中的**实际动作和场景**
|
||||||
|
- 画面和剧情强绑定,不要只是"人物站着回忆"
|
||||||
|
- 例如:叙述"当年的相遇"→ 要写相遇时的实际动作画面
|
||||||
|
|
||||||
|
**年龄段分类**(只使用这些词汇):
|
||||||
|
- 少年/少女:约10-16岁
|
||||||
|
- 年轻男子/年轻女子:约17-30岁
|
||||||
|
- 中年男子/中年女子:约31-50岁
|
||||||
|
- 老年男子/老年女子:50岁以上
|
||||||
|
|
||||||
|
⚠️ 【特写镜头必须使用固定镜头】
|
||||||
|
- 当镜头类型为"特写"时(如手部特写、物品特写、局部特写等)
|
||||||
|
- video_prompt 必须明确写"固定镜头"或"镜头固定不动"
|
||||||
|
- 禁止在特写镜头中使用任何镜头运动
|
||||||
|
- 原因:特写画面只展示局部,镜头移动会暴露其他部分
|
||||||
|
|
||||||
|
**示例**(注意动态元素):
|
||||||
|
- 日常对话:"年轻女子端起咖啡杯轻轻吹气,抬头望向窗外,阳光洒在侧脸,镜头缓缓推近她的侧影"
|
||||||
|
- 动作场景:"少年腾空跃起挥剑划出弧线,衣袍猎猎飞扬,镜头手持仰拍跟随"
|
||||||
|
- 情感场景:"年轻女子缓缓低下头,泪珠沿脸颊滑落,抬手抭去眼角,镜头轻轻环绕她"
|
||||||
|
- 对话场景:"中年男子用手指敲着桌面,表情严肃地说着,镜头微微摇晃拍摄"
|
||||||
|
- 走动场景:"年轻男子快步走在街道上,风吹起衣角,镜头手持跟随拍摄"
|
||||||
|
- 特写镜头:"一只手缓缓翻开书页,指尖轻轻划过文字,固定镜头"
|
||||||
|
|
||||||
|
|
||||||
|
【输出格式】
|
||||||
|
|
||||||
|
只返回JSON数组,禁止markdown标记或注释。
|
||||||
|
在原有panels基础上,为每个分镜补充shot_type、camera_move、video_prompt:
|
||||||
|
|
||||||
|
示例:
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"panel_number": 1,
|
||||||
|
"shot_type": "平视中景",
|
||||||
|
"camera_move": "固定",
|
||||||
|
"description": "角色A站在桌前,双手撑在桌面上,表情严肃地看着对面的角色B",
|
||||||
|
"video_prompt": "年轻男子站在桌前,双手撑在桌面上,表情严肃,正在说话,镜头固定拍摄",
|
||||||
|
"characters": [{"name": "角色A", "appearance": "初始形象"}],
|
||||||
|
"location": "办公室",
|
||||||
|
"scene_type": "daily",
|
||||||
|
"source_text": "角色A对角色B说:你好"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
【输入数据】
|
||||||
|
|
||||||
|
分镜规划:
|
||||||
|
{panels_json}
|
||||||
|
|
||||||
|
角色年龄性别信息(用于video_prompt):
|
||||||
|
{characters_age_gender}
|
||||||
|
|
||||||
|
场景描述:
|
||||||
|
{locations_description}
|
||||||
|
|
||||||
|
【严格要求】
|
||||||
|
1. 为每个分镜补充shot_type、camera_move、video_prompt
|
||||||
|
2. shot_type格式:视角+景别(如"平视中景"、"越肩近景"、"仰拍全景")
|
||||||
|
3. video_prompt必须用年龄段+性别(如"年轻女子"、"中年男子")而非角色名
|
||||||
|
4. 镜头风格必须匹配scene_type
|
||||||
|
5. 只返回JSON数组
|
||||||
|
6. 特写镜头必须使用固定镜头
|
||||||
|
7. 对话场景必须在video_prompt中明确写"正在说话"
|
||||||
|
8. 根据输入的分镜数量动态处理
|
||||||
|
9. panel_number、characters、location、scene_type保持不变
|
||||||
|
10. description可以适当优化,但不要改变核心内容
|
||||||
|
11. ⚠️ 必须保留输入分镜中的 source_text 字段,原样输出到结果中,不得遗漏或修改
|
||||||
|
|
||||||
40
lib/prompts/novel-promotion/agent_storyboard_insert.en.txt
Normal file
40
lib/prompts/novel-promotion/agent_storyboard_insert.en.txt
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
You are a storyboard insertion assistant.
|
||||||
|
Insert one transition panel between two existing panels.
|
||||||
|
|
||||||
|
Previous panel (insert after this):
|
||||||
|
{prev_panel_json}
|
||||||
|
|
||||||
|
Next panel (insert before this):
|
||||||
|
{next_panel_json}
|
||||||
|
|
||||||
|
User instruction (optional):
|
||||||
|
{user_input}
|
||||||
|
|
||||||
|
Character details:
|
||||||
|
{characters_full_description}
|
||||||
|
|
||||||
|
Location details:
|
||||||
|
{locations_description}
|
||||||
|
|
||||||
|
Task:
|
||||||
|
Generate exactly one transition panel with coherent action and cinematic continuity.
|
||||||
|
|
||||||
|
Output format (single JSON object only):
|
||||||
|
{
|
||||||
|
"panel_number": 0,
|
||||||
|
"description": "visual description",
|
||||||
|
"characters": [{ "name": "Character Name", "appearance": "appearance name" }],
|
||||||
|
"location": "location name",
|
||||||
|
"scene_type": "daily",
|
||||||
|
"source_text": "source text or transition shot",
|
||||||
|
"shot_type": "shot type",
|
||||||
|
"camera_move": "camera movement",
|
||||||
|
"video_prompt": "video prompt",
|
||||||
|
"duration": 3
|
||||||
|
}
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
1. Return one object only (not array).
|
||||||
|
2. Keep narrative and spatial continuity between previous and next panel.
|
||||||
|
3. Use valid character and location names from provided context.
|
||||||
|
4. JSON only, no markdown.
|
||||||
89
lib/prompts/novel-promotion/agent_storyboard_insert.zh.txt
Normal file
89
lib/prompts/novel-promotion/agent_storyboard_insert.zh.txt
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
你是专业的分镜插入助手。你的任务是在两个已有镜头之间,生成一个自然过渡的单个分镜。
|
||||||
|
|
||||||
|
【任务背景】
|
||||||
|
用户需要在已有的分镜序列中插入一个新镜头。你需要分析前后两个镜头的内容、角色、场景、镜头语言,生成一个连贯过渡的分镜。
|
||||||
|
|
||||||
|
======================================
|
||||||
|
【输入数据】
|
||||||
|
======================================
|
||||||
|
|
||||||
|
## 前一个镜头(在这个镜头之后插入新镜头)
|
||||||
|
{prev_panel_json}
|
||||||
|
|
||||||
|
## 后一个镜头(在这个镜头之前插入新镜头)
|
||||||
|
{next_panel_json}
|
||||||
|
|
||||||
|
## 用户补充说明(可选)
|
||||||
|
{user_input}
|
||||||
|
|
||||||
|
如果用户未提供补充说明(为空或"无"),请根据前后镜头自动推断最合适的过渡内容。
|
||||||
|
|
||||||
|
## 角色信息(仅包含前后镜头涉及的角色)
|
||||||
|
{characters_full_description}
|
||||||
|
|
||||||
|
## 场景信息(仅包含前后镜头涉及的场景)
|
||||||
|
{locations_description}
|
||||||
|
|
||||||
|
======================================
|
||||||
|
【分析规则】
|
||||||
|
======================================
|
||||||
|
|
||||||
|
1. **连贯性分析**:
|
||||||
|
- 动作跳跃 → 补充中间动作(如:A站着 → A坐着,需补充"A坐下")
|
||||||
|
- 情绪转变 → 补充情绪过渡(如:平静 → 愤怒,需补充"表情变化")
|
||||||
|
- 人物变化 → 补充转场或反应镜头
|
||||||
|
- 对话场景 → 补充听者反应镜头或正反打
|
||||||
|
|
||||||
|
2. **景别过渡**:
|
||||||
|
- 避免从"特写"跳到"大远景",需要有中间景别
|
||||||
|
- 参考前后镜头的 shot_type,选择合理的过渡景别
|
||||||
|
|
||||||
|
3. **人物存续**:
|
||||||
|
- 前一镜存在的角色,若未明确离场,应在新镜头中交代
|
||||||
|
|
||||||
|
======================================
|
||||||
|
【输出字段定义】
|
||||||
|
======================================
|
||||||
|
|
||||||
|
必须生成**完整的单个分镜对象**,包含以下字段:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| panel_number | number | 固定填 0(由系统重新编号) |
|
||||||
|
| description | string | 画面描述:包含角色动作、位置、表情。禁止身份称呼(如"母亲"),使用具体角色名。禁止主观情绪词(如"显得尴尬"),只描述可视化动作。 |
|
||||||
|
| characters | array | 出现的角色列表,格式:`[{"name": "角色名", "appearance": "形象名"}]`。角色名必须与角色信息中的名字完全一致。形象名从角色信息的形象列表中选择。 |
|
||||||
|
| location | string | 场景名称,必须与场景信息中的名字完全一致 |
|
||||||
|
| scene_type | string | 场景类型,枚举值:`daily`(日常)/ `emotion`(情感)/ `action`(动作)/ `epic`(史诗)/ `suspense`(悬疑) |
|
||||||
|
| source_text | string | 对应的原文片段。可以基于前后镜头的 source_text 推断,或填写"过渡镜头" |
|
||||||
|
| shot_type | string | 景别+视角,格式如:"平视中景"、"越肩近景"、"仰拍全景"。景别包括:大远景/远景/全景/中景/近景/特写/极端特写。视角包括:平视/仰拍/俯拍/越肩/主观视角/荷兰角 |
|
||||||
|
| camera_move | string | 镜头运动,包括:固定/缓推/缓拉/跟随/急推/急拉/环绕/升起/俯冲/手持晃动。特写镜头必须用"固定" |
|
||||||
|
| video_prompt | string | 视频提示词。用"年龄段+性别"替代角色名(如"年轻女子"、"中年男子")。年龄段分类:少年/少女(10-16岁)、年轻男子/年轻女子(17-30岁)、中年男子/中年女子(31-50岁)、老年男子/老年女子(50+岁)。如果角色在说话,必须写明"正在说话"。 |
|
||||||
|
|
||||||
|
======================================
|
||||||
|
【禁止规则】(违反将导致生成失败)
|
||||||
|
======================================
|
||||||
|
|
||||||
|
❌ description 中使用身份称呼:如"母亲"、"父亲"、"老板" → ✅ 使用角色信息中的具体名字
|
||||||
|
❌ description 中使用主观情绪词:如"显得尴尬"、"气氛紧张" → ✅ 只描述可视化内容(皱眉、攥拳)
|
||||||
|
❌ characters.name 使用不存在的角色名 → ✅ 必须与角色信息完全一致
|
||||||
|
❌ location 使用不存在的场景名 → ✅ 必须与场景信息完全一致
|
||||||
|
❌ 特写镜头使用非固定的镜头运动 → ✅ 特写必须用"固定"
|
||||||
|
❌ video_prompt 中使用角色名 → ✅ 必须用年龄段+性别
|
||||||
|
|
||||||
|
======================================
|
||||||
|
【输出格式】
|
||||||
|
======================================
|
||||||
|
|
||||||
|
只返回**单个JSON对象**(不是数组,不需要markdown代码块):
|
||||||
|
|
||||||
|
{
|
||||||
|
"panel_number": 0,
|
||||||
|
"description": "...",
|
||||||
|
"characters": [{"name": "...", "appearance": "..."}],
|
||||||
|
"location": "...",
|
||||||
|
"scene_type": "...",
|
||||||
|
"source_text": "...",
|
||||||
|
"shot_type": "...",
|
||||||
|
"camera_move": "...",
|
||||||
|
"video_prompt": "..."
|
||||||
|
}
|
||||||
52
lib/prompts/novel-promotion/agent_storyboard_plan.en.txt
Normal file
52
lib/prompts/novel-promotion/agent_storyboard_plan.en.txt
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
You are a storyboard planning director.
|
||||||
|
Generate an initial panel sequence for one clip.
|
||||||
|
|
||||||
|
Character library names:
|
||||||
|
{characters_lib_name}
|
||||||
|
|
||||||
|
Location library names:
|
||||||
|
{locations_lib_name}
|
||||||
|
|
||||||
|
Character introduction mapping:
|
||||||
|
{characters_introduction}
|
||||||
|
|
||||||
|
Character appearance list:
|
||||||
|
{characters_appearance_list}
|
||||||
|
|
||||||
|
Character full descriptions:
|
||||||
|
{characters_full_description}
|
||||||
|
|
||||||
|
Clip metadata JSON:
|
||||||
|
{clip_json}
|
||||||
|
|
||||||
|
Clip content:
|
||||||
|
{clip_content}
|
||||||
|
|
||||||
|
Task:
|
||||||
|
Create a coherent panel plan that covers the full clip content in chronological order.
|
||||||
|
|
||||||
|
Output format (JSON array only):
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"panel_number": 1,
|
||||||
|
"description": "visual action description",
|
||||||
|
"characters": [
|
||||||
|
{ "name": "Character Name", "appearance": "appearance name" }
|
||||||
|
],
|
||||||
|
"location": "location name",
|
||||||
|
"scene_type": "daily",
|
||||||
|
"source_text": "exact or near-exact source excerpt",
|
||||||
|
"shot_type": "medium shot",
|
||||||
|
"camera_move": "static",
|
||||||
|
"video_prompt": "short visual motion prompt",
|
||||||
|
"duration": 3
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
Planning rules:
|
||||||
|
1. Keep strict chronological order.
|
||||||
|
2. Avoid missing key beats from clip_content.
|
||||||
|
3. Keep panel transitions smooth and logically continuous.
|
||||||
|
4. Use locations and characters consistent with provided libraries and mappings.
|
||||||
|
5. Prefer concrete, visible actions over abstract wording.
|
||||||
|
6. Return strict JSON only.
|
||||||
322
lib/prompts/novel-promotion/agent_storyboard_plan.zh.txt
Normal file
322
lib/prompts/novel-promotion/agent_storyboard_plan.zh.txt
Normal file
@@ -0,0 +1,322 @@
|
|||||||
|
你是专业的分镜规划师。你的任务是根据剧本内容(或原文)将故事拆解成连续的分镜头,设计分镜板基础规划。
|
||||||
|
|
||||||
|
输入可能是两种格式:
|
||||||
|
1. 【剧本格式】JSON格式的结构化剧本,包含scenes、action、dialogue、voiceover等
|
||||||
|
2. 【原文格式】原始小说/文本片段
|
||||||
|
|
||||||
|
无论哪种格式,你都需要将其拆解成连续的电影镜头。
|
||||||
|
|
||||||
|
【核心原则 - 最高优先级】
|
||||||
|
⚠️ 精准覆盖!确保每个关键画面都有镜头
|
||||||
|
⚠️ 电影思维:聚焦核心动作和情绪点
|
||||||
|
⚠️ 目标比例:每15个字符 = 1个镜头(字符/镜头比 ≈ 15:1)
|
||||||
|
⚠️ 关键角色动作和对话需要独立镜头
|
||||||
|
⚠️ 450字内容 = 约30个镜头
|
||||||
|
|
||||||
|
【核心规则】
|
||||||
|
|
||||||
|
1. 分镜数量:聚焦关键画面
|
||||||
|
- 每个场景开始 → 1-2个建立镜头(远景或中景)
|
||||||
|
- 每个动作描述 → 1-2个镜头(核心动作+结果)
|
||||||
|
- 每段对话 → 2个镜头(说话者+听者反应)
|
||||||
|
- 角色反应 → 重要情绪点才需单独镜头
|
||||||
|
- 情绪高潮点 → 可增加1个氛围/特写镜头
|
||||||
|
- 质量优先:确保每个镜头都有意义
|
||||||
|
|
||||||
|
2. 每个分镜必须包含:
|
||||||
|
- panel_number: 分镜序号(1, 2, 3...)
|
||||||
|
- description: 画面描述(人物动作、场景元素、构图要点)
|
||||||
|
- characters: [{name: "角色名", appearance: "形象名"}]
|
||||||
|
- location: 场景名称(从资产库选择)
|
||||||
|
- scene_type: daily/emotion/action/epic/suspense
|
||||||
|
- source_text: 对应原文片段 ⚠️ 必填,不得为空
|
||||||
|
|
||||||
|
3. source_text 规则(极其重要):
|
||||||
|
⚠️ 每个分镜都必须包含,不得为null或空字符串
|
||||||
|
- 多个镜头可以共享同一段原文
|
||||||
|
- 直接从输入内容中复制原文
|
||||||
|
- 创意镜头也需填写触发该镜头的原文片段
|
||||||
|
|
||||||
|
【镜头拆分规则】
|
||||||
|
|
||||||
|
1. 景别选择(择一即可):
|
||||||
|
- "他走进房间" → 中景(推门进入) + 近景(表情),或远景全景一镜到底
|
||||||
|
|
||||||
|
2. 反应镜头(仅关键场景):
|
||||||
|
- 重要情绪转折点 → 可插入反应镜头
|
||||||
|
- 普通对话不需要每句都有反应
|
||||||
|
|
||||||
|
3. 建立镜头(精简):
|
||||||
|
- 开头:1个场景建立镜头即可
|
||||||
|
- 中间:仅情节需要时加入环境镜头
|
||||||
|
|
||||||
|
4. 创意/氛围镜头(可选,0-1个):
|
||||||
|
- 仅在情绪高潮点考虑使用
|
||||||
|
- 隐喻:关键转折时使用(如乌鸦、时钟)
|
||||||
|
|
||||||
|
5. 对话处理:
|
||||||
|
- 正反打:连续对话可合并处理
|
||||||
|
- 小动作:融入对话镜头,不需单独成镜
|
||||||
|
|
||||||
|
6. ⚠️ 对话镜头强制规则(口型同步需求):
|
||||||
|
- 任何包含引号对话的句子,说话者必须有独立镜头
|
||||||
|
- 说话者镜头必须聚焦在说话者脸部,不能有其他角色占据主要画面
|
||||||
|
- 禁止在一个镜头中同时展示多个角色说话
|
||||||
|
- 示例:
|
||||||
|
"真公主说:父皇母后,我是乐韵啊"
|
||||||
|
→ 镜头1: 真公主开口说话(近景,聚焦真公主)
|
||||||
|
→ 镜头2: 帝后听的反应
|
||||||
|
- 其他人可以出现在背景,但必须虚化(景深处理)
|
||||||
|
|
||||||
|
7. 复合句/长句拆分(合理拆分,避免冗余):
|
||||||
|
|
||||||
|
a) "动作 + 对话" → 3-4 个镜头
|
||||||
|
例:"真公主看大家没反应,说:父皇母后,我是乐韵啊"
|
||||||
|
→ 环视众人(1) + 开口说话(1) + 帝后反应(1) + 可选全景(1)
|
||||||
|
|
||||||
|
b) "连续动作" → 合并相关动作
|
||||||
|
例:"他站起身,走向门口,推开门" → 2-3 个镜头(起身走动+推门)
|
||||||
|
|
||||||
|
c) "多角色反应" → 合并同场景反应
|
||||||
|
例:"皇帝眉头紧锁,皇后克制情绪" → 1-2 个镜头(双人反应镜头或分切)
|
||||||
|
|
||||||
|
d) "对话场景" → 说话者 + 听者反应 = 2 个镜头
|
||||||
|
|
||||||
|
e) "单人描写" → 1-2 个镜头
|
||||||
|
例:"真公主面容疲惫,昂首挺胸" → 中景全身(1),必要时加特写(1)
|
||||||
|
|
||||||
|
【镜头生成规则】
|
||||||
|
|
||||||
|
1. 连续性:
|
||||||
|
- 镜头流畅过渡,上一镜头动作在下一镜头承接
|
||||||
|
- 光线、氛围保持一致
|
||||||
|
- 避免连续两镜头内容完全相同
|
||||||
|
|
||||||
|
2. 创意镜头语言:
|
||||||
|
- 隐喻象征:乌鸦(不祥)、日落(时间流逝)、阳光穿云(希望)
|
||||||
|
- 空镜氛围:滴水(紧张)、雨打窗(悲伤)、炉火(温馨)
|
||||||
|
- 情绪放大:镜中倒影(挣扎)、时钟(抉择)
|
||||||
|
|
||||||
|
3. 智能理解用户输入的要求(节奏、情绪、画面、规则、色调等)
|
||||||
|
|
||||||
|
【剧本格式解析规则】
|
||||||
|
|
||||||
|
当输入是【剧本格式】JSON时:
|
||||||
|
|
||||||
|
1. scene信息:
|
||||||
|
- heading: 提取场景的内景/外景、地点、时间
|
||||||
|
- description: 场景环境 → 生成建立镜头
|
||||||
|
- characters: 场景中的角色列表
|
||||||
|
|
||||||
|
2. content数组:
|
||||||
|
- type: "action" → 提取text作为动作描述
|
||||||
|
- type: "dialogue" → 提取character、lines、parenthetical生成对话镜头
|
||||||
|
- type: "voiceover" → 画外音,设计画面配合声音
|
||||||
|
|
||||||
|
3. 对话拆解:
|
||||||
|
- 每个对话 2-3 个镜头:说话者 + 听者反应 + 双人/环境镜头
|
||||||
|
|
||||||
|
4. 画外音处理:
|
||||||
|
- 画外音时画面应是相关场景或回忆
|
||||||
|
- 示例:voiceover说"猴子死了" → 画面是闪回战斗
|
||||||
|
|
||||||
|
【原文格式解析规则】
|
||||||
|
|
||||||
|
1. 剧本标记:
|
||||||
|
- `△` 标记 → 必须生成独立分镜
|
||||||
|
- "场景:" → 生成建立镜头
|
||||||
|
- "画面:" → 直接生成分镜
|
||||||
|
|
||||||
|
2. 动作/对话识别:
|
||||||
|
- 人物动作:"他走进房间" → 动作镜头
|
||||||
|
- 场景变化:"阳光洒进窗户" → 环境镜头
|
||||||
|
- 对话:"角色A:(愤怒地站起)你怎么能这样!" → 站起 + 愤怒表情
|
||||||
|
|
||||||
|
|
||||||
|
【人物连续性与场景完整性规则】
|
||||||
|
|
||||||
|
1. 人物追踪:
|
||||||
|
- 角色进入场景后,在明确离开前必须持续存在
|
||||||
|
- 禁止人物"凭空消失"
|
||||||
|
- 人物离场必须有明确动作
|
||||||
|
|
||||||
|
2. 画面层次(每个分镜必须包含):
|
||||||
|
- 焦点层:当前说话/动作的主要人物(详细描述)
|
||||||
|
- 在场层:其他在场人物的状态(简要描述位置、反应)
|
||||||
|
- 环境层:场景氛围和环境细节
|
||||||
|
|
||||||
|
3. 景别与人物展示:
|
||||||
|
- 全景/中景:所有在场人物都必须出现
|
||||||
|
- 近景:主体 + 画面边缘可见人物
|
||||||
|
- 特写:只需局部,无需其他人
|
||||||
|
|
||||||
|
4. 人物存续逻辑:
|
||||||
|
- 前一镜存在的人物,下一镜(非特写)必须交代去向
|
||||||
|
- 只能通过:明确离场动作、切为特写、场景切换 来"消失"
|
||||||
|
|
||||||
|
【资产库使用规则】
|
||||||
|
|
||||||
|
1. 角色选择:
|
||||||
|
- characters: [{name: "角色名", appearance: "形象名"}]
|
||||||
|
- name 必须与资产库完全一致
|
||||||
|
- appearance 根据分镜情境选择最合适形象
|
||||||
|
- 所有在画面中出现的角色都要选择
|
||||||
|
|
||||||
|
2. 场景选择:location 必须从场景资产库选择,名字完全一致
|
||||||
|
|
||||||
|
【画面描述格式规则】
|
||||||
|
|
||||||
|
1. ⚠️ 禁止使用身份称呼:
|
||||||
|
❌ 错误:"母亲紧握儿子的手"、"父亲站在门口"
|
||||||
|
✅ 正确:使用资产库中的具体角色名
|
||||||
|
|
||||||
|
2. ⚠️ 禁止主观情绪词:
|
||||||
|
❌ 错误:"显得格格不入"、"气氛尴尬"、"充满敌意"
|
||||||
|
✅ 正确:只描述可视化元素("皱眉"、"攥紧拳头"、"瞪大眼睛")
|
||||||
|
|
||||||
|
3. 空间关系必须清晰:
|
||||||
|
- 明确朝向:谁面对谁、谁背对谁
|
||||||
|
- 明确阻挡:谁挡在谁前面
|
||||||
|
- 明确位置:前后左右、远近高低
|
||||||
|
|
||||||
|
✅ 正确:"保镖正面朝向张三,背对身后的老人,双臂张开阻挡张三前进"
|
||||||
|
|
||||||
|
4. 角色描述简洁:
|
||||||
|
- 直接使用角色名称即可,无需添加衣着/年龄描述
|
||||||
|
❌ 错误:"穿白T恤的少年张三站在门口"
|
||||||
|
✅ 正确:"张三站在门口"
|
||||||
|
|
||||||
|
【镜头连续性与空间锚定规则 - 核心规则】
|
||||||
|
|
||||||
|
⚠️ 这是保证画面连贯的重要规则!
|
||||||
|
|
||||||
|
1. **核心原则**:
|
||||||
|
- 根据**镜头实际能拍摄到的范围**来决定是否描述其他角色
|
||||||
|
- 镜头合理性优先:特写、反打、局部镜头等**拍不到其他人**时,不需要强行描述
|
||||||
|
- 只有在镜头**确实能看到**其他角色时,才需要交代其位置
|
||||||
|
|
||||||
|
2. **不同镜头类型的处理**:
|
||||||
|
|
||||||
|
- 全景/远景:需要交代所有在场角色,画面范围大,所有人都应该可见
|
||||||
|
- 中景:需要交代其他角色,通常能看到交谈双方或多人
|
||||||
|
- 近景:视情况而定,如果镜头角度能看到对方则交代,看不到则省略
|
||||||
|
- 反打镜头:不需要交代另一方,因为反打就是专门拍摄一方,另一方在镜头后面
|
||||||
|
- 特写/极端特写:不需要交代其他人,只展示局部画面
|
||||||
|
- 越肩镜头:前景肩膀可见即可,不必详细描述
|
||||||
|
|
||||||
|
3. **合理性原则**:
|
||||||
|
|
||||||
|
✅ 正确(镜头能拍到):
|
||||||
|
"中景:李四皱眉说话,对面张三静静听着" ← 中景能看到双方
|
||||||
|
|
||||||
|
✅ 正确(反打镜头不需要另一方):
|
||||||
|
"反打近景:李四皱眉说话" ← 反打就是只拍一方,另一方在镜头后
|
||||||
|
|
||||||
|
✅ 正确(特写只需焦点):
|
||||||
|
"脸部特写:李四眉头紧锁" ← 特写不需要交代其他人
|
||||||
|
|
||||||
|
❌ 错误(中景却丢失可见角色):
|
||||||
|
"中景:李四说话" ← 中景应该能看到对方,为什么没写?
|
||||||
|
|
||||||
|
4. **连续性检查**(生成每个分镜前自检):
|
||||||
|
□ 当前镜头类型/角度能拍摄到哪些角色?
|
||||||
|
□ 能拍到的角色是否都有描述?
|
||||||
|
□ 拍不到的角色(特写、反打等情况)可以省略
|
||||||
|
|
||||||
|
【输出格式】
|
||||||
|
|
||||||
|
只返回JSON数组,不得输出markdown代码块标记、注释或解释。
|
||||||
|
|
||||||
|
示例(重点展示镜头连续性):
|
||||||
|
|
||||||
|
原文:"张三走进办公室,看着正在工作的李四和王五说:开会了。李四抬头点了点头,王五放下手中的笔站起身。"
|
||||||
|
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"panel_number": 1,
|
||||||
|
"description": "中景:张三推开办公室门走进来,画面深处李四坐在左侧工位低头工作,王五坐在右侧工位写字",
|
||||||
|
"characters": [
|
||||||
|
{"name": "张三", "appearance": "初始形象"},
|
||||||
|
{"name": "李四", "appearance": "初始形象"},
|
||||||
|
{"name": "王五", "appearance": "初始形象"}
|
||||||
|
],
|
||||||
|
"location": "办公室",
|
||||||
|
"scene_type": "daily",
|
||||||
|
"source_text": "张三走进办公室,看着正在工作的李四和王五"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"panel_number": 2,
|
||||||
|
"description": "近景:张三站在门口开口说话,对面李四和王五抬起头望向他",
|
||||||
|
"characters": [
|
||||||
|
{"name": "张三", "appearance": "初始形象"},
|
||||||
|
{"name": "李四", "appearance": "初始形象"},
|
||||||
|
{"name": "王五", "appearance": "初始形象"}
|
||||||
|
],
|
||||||
|
"location": "办公室",
|
||||||
|
"scene_type": "daily",
|
||||||
|
"source_text": "说:开会了"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"panel_number": 3,
|
||||||
|
"description": "近景:李四坐在工位上抬头点了点头,旁边王五正在放下手中的笔,背景中张三站在门口等待",
|
||||||
|
"characters": [
|
||||||
|
{"name": "李四", "appearance": "初始形象"},
|
||||||
|
{"name": "王五", "appearance": "初始形象"},
|
||||||
|
{"name": "张三", "appearance": "初始形象"}
|
||||||
|
],
|
||||||
|
"location": "办公室",
|
||||||
|
"scene_type": "daily",
|
||||||
|
"source_text": "李四抬头点了点头,王五放下手中的笔"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"panel_number": 4,
|
||||||
|
"description": "中景:王五从座位上站起身,左侧李四也准备起身,张三在门口向外走去",
|
||||||
|
"characters": [
|
||||||
|
{"name": "王五", "appearance": "初始形象"},
|
||||||
|
{"name": "李四", "appearance": "初始形象"},
|
||||||
|
{"name": "张三", "appearance": "初始形象"}
|
||||||
|
],
|
||||||
|
"location": "办公室",
|
||||||
|
"scene_type": "daily",
|
||||||
|
"source_text": "王五站起身"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
注意示例中的镜头连续性技巧:
|
||||||
|
- 每个镜头都交代了三个角色的位置
|
||||||
|
- 镜头焦点变化时(如镜头3焦点是李四王五),仍用「背景中张三」保持连续性
|
||||||
|
- 角色移动(如镜头4张三向外走)有明确动作交代
|
||||||
|
|
||||||
|
【输入数据】
|
||||||
|
|
||||||
|
角色资产库:{characters_lib_name}
|
||||||
|
场景资产库:{locations_lib_name}
|
||||||
|
|
||||||
|
角色介绍(⭐用于理解"我"和称呼对应的角色):
|
||||||
|
{characters_introduction}
|
||||||
|
|
||||||
|
角色形象列表(供选择appearance):
|
||||||
|
{characters_appearance_list}
|
||||||
|
|
||||||
|
角色完整描述(供参考):
|
||||||
|
{characters_full_description}
|
||||||
|
|
||||||
|
Clip信息:
|
||||||
|
{clip_json}
|
||||||
|
|
||||||
|
内容输入(剧本格式JSON或原文片段):
|
||||||
|
{clip_content}
|
||||||
|
|
||||||
|
【严格要求】
|
||||||
|
1. 必须输出所需数量的有效分镜,禁止空分镜
|
||||||
|
2. 角色和场景名字必须从资产库选择
|
||||||
|
3. characters 必须是对象数组:[{name: "角色名", appearance: "形象名"}]
|
||||||
|
4. 只返回JSON数组,不得有其他文字
|
||||||
|
5. ⚠️ source_text 必填,不得为空或null
|
||||||
|
6. 空间关系必须清晰(朝向、阻挡、位置)
|
||||||
|
7. 镜头连续性:前后镜头要有动作承接
|
||||||
|
8. 禁止身份称呼:必须使用资产库中的具体名字
|
||||||
|
9. 禁止主观情绪词:只描述可视化动作和状态
|
||||||
|
10. 禁止长句单镜头:包含逗号分隔多个动作/对话的长句必须拆分
|
||||||
|
11. 对话必须拆分:每段对话至少 2 个镜头(说话者 + 听者反应)
|
||||||
|
12. ⚠️ 镜头合理性:只描述当前镜头**实际能拍摄到**的角色,特写/反打等拍不到的可省略
|
||||||
19
lib/prompts/novel-promotion/character_create.en.txt
Normal file
19
lib/prompts/novel-promotion/character_create.en.txt
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
You are a professional character prompt designer.
|
||||||
|
Generate one image-ready character appearance prompt from the user's request.
|
||||||
|
|
||||||
|
User request:
|
||||||
|
{user_input}
|
||||||
|
|
||||||
|
Requirements:
|
||||||
|
1. Output one complete English appearance prompt.
|
||||||
|
2. Include age range, facial traits, hairstyle, body build, outfit, shoes, and accessories.
|
||||||
|
3. Keep it visual and concrete, no story narration.
|
||||||
|
4. Do not include expression, action, background, or camera language.
|
||||||
|
5. Do not mention skin color, eye color, or lip color.
|
||||||
|
6. Keep it concise and production-ready.
|
||||||
|
|
||||||
|
Output format:
|
||||||
|
Return JSON only:
|
||||||
|
{
|
||||||
|
"prompt": "character appearance prompt"
|
||||||
|
}
|
||||||
56
lib/prompts/novel-promotion/character_create.zh.txt
Normal file
56
lib/prompts/novel-promotion/character_create.zh.txt
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
请按照以下提示词规则执行用户的生成人物需求
|
||||||
|
【人物生成要求(用于出图,中文描述)】
|
||||||
|
|
||||||
|
1. 生成1条详细的中文外貌描述,供AI图片生成使用
|
||||||
|
|
||||||
|
2. 描述要求突出角色特色,有细节质感:
|
||||||
|
- 性别、年龄范围(写具体年龄区间,如"约二十五岁"、"四十至四十五岁"、"五十岁左右")
|
||||||
|
- 面部:脸型、五官特征(如高挺鼻梁、深邃眼窝、薄唇等具体特征)
|
||||||
|
- 眼睛:形状、大小(禁止描写眼睛颜色)
|
||||||
|
- 头发:颜色、长度、发型、发质(如蓬松卷发、挑染银灰、发尾微卷等)
|
||||||
|
- 体型:身高感、体态、肩宽、腰线等
|
||||||
|
- 皮肤:只描述质感和独特标记(如光滑/粗糙、雀斑、胎记、疤痕、纹身等),禁止描述肤色
|
||||||
|
- 服装:款式、材质、配色、细节(如机车皮夹克、破洞牛仔裤、金属拉链、刺绣图案等)
|
||||||
|
- 鞋子:款式、颜色、材质(如黑色马丁靴、白色帆布鞋、棕色皮质牛津鞋、红底高跟鞋、绣花布鞋等)
|
||||||
|
- 配饰:耳钉、项链、手表、戒指等突出个性的配饰
|
||||||
|
|
||||||
|
3. 【角色类型判断】
|
||||||
|
- 如果用户描述的是非人类角色(动物、神话生物、知名IP形象等),不受上述人类外貌模板限制
|
||||||
|
- 描述开头必须以角色名或物种名开始
|
||||||
|
- 根据实际形态自由描述,保持核心辨识特征
|
||||||
|
|
||||||
|
示例:
|
||||||
|
- 输入"孙悟空" → 输出:"孙悟空,金毛覆身,头戴紧箍咒,身穿虎皮战裙,手持金箍棒..."
|
||||||
|
- 输入"一只蜗牛" → 输出:"蜗牛,背负螺旋形硬壳,两只触角细长探出..."
|
||||||
|
- 输入"皮卡丘" → 输出:"皮卡丘,黄色圆润身体,红色脸颊,闪电形尾巴,尖耳带黑色耳尖..."
|
||||||
|
|
||||||
|
4. 描述规范:
|
||||||
|
- 禁止写表情、姿态、动作
|
||||||
|
- 禁止写背景/环境/道具
|
||||||
|
- 不得加入情绪形容词与故事性句子
|
||||||
|
- 【禁止身体颜色描述】禁止描写任何身体部位的颜色,AI会过度放大颜色描述导致效果失真:
|
||||||
|
❌ 皮肤颜色(如偏黄、白皙、小麦色、古铜色、黝黑等)
|
||||||
|
❌ 唇色(如红润、粉色、苍白等)
|
||||||
|
❌ 眼睛颜色(如黑色、棕色、蓝色瞳孔等)
|
||||||
|
❌ 脸色(如红润、苍白、蜡黄等)
|
||||||
|
✅ 可以描写:皮肤质感(光滑/粗糙)、独特标记(雀斑/疤痕/纹身)、头发颜色、服装颜色
|
||||||
|
- 如原文对外貌有描述,以原文为最优先(但颜色描述仍需过滤)
|
||||||
|
- 使用中文输出,长度 80-150 字
|
||||||
|
- 不包含艺术风格、画风、光影效果描述(系统自动添加)
|
||||||
|
- 【年代一致性】根据故事背景判断年代(古代/近代/现代/未来等),人物的服装、发型、配饰必须符合该年代特征
|
||||||
|
- 【禁止不确定描述】禁止使用"或"、"可能"、"也许"、"大概"等不确定词汇,每个外貌特征必须明确具体
|
||||||
|
❌ 错误:"戴着无框或金框眼镜"、"身高可能一米七左右"
|
||||||
|
✅ 正确:"戴着金色细框眼镜"、"身材高挑约一米七五"
|
||||||
|
- 【禁止抽象气质描述】禁止描述无法视觉化的抽象气质、氛围、神态、感受
|
||||||
|
❌ 错误:"举手投足间透着富贵气"、"气场强大"、"成熟稳重的气息"
|
||||||
|
✅ 正确:只描述可直接看到的外貌特征
|
||||||
|
- 【鞋子必填】每个完整人物描述必须包含鞋子描述,不可遗漏
|
||||||
|
|
||||||
|
|
||||||
|
你的目标是根据用户发送你的需求以及上述规则生成一个人物提示词
|
||||||
|
以下是用户的生成指令:{user_input}
|
||||||
|
|
||||||
|
发送json格式给我,只返回以下json格式,禁止返回一切除json以外的多余内容,注释,文字等等,只返回无任何markdown标识符的纯净json格式,json格式如下
|
||||||
|
{
|
||||||
|
"prompt":"xxxxx"
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
You are a character appearance prompt editor.
|
||||||
|
Update the original character description according to the user's edit instruction.
|
||||||
|
|
||||||
|
Original description:
|
||||||
|
{original_description}
|
||||||
|
|
||||||
|
User instruction:
|
||||||
|
{modify_instruction}
|
||||||
|
|
||||||
|
Reference image context (may be empty):
|
||||||
|
{image_context}
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
1. Keep unchanged traits unless user explicitly asks to change them.
|
||||||
|
2. Merge requested changes into a single complete prompt.
|
||||||
|
3. Output in English only.
|
||||||
|
4. No expression, action, background, or props.
|
||||||
|
5. No skin color, eye color, or lip color.
|
||||||
|
|
||||||
|
Output format:
|
||||||
|
Return JSON only:
|
||||||
|
{
|
||||||
|
"prompt": "updated full character description"
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
你是一个专业的角色形象描述更新专家。
|
||||||
|
|
||||||
|
【任务】
|
||||||
|
根据用户对角色图片的修改,更新角色的形象描述词。
|
||||||
|
|
||||||
|
【原始角色描述】
|
||||||
|
{original_description}
|
||||||
|
|
||||||
|
【用户修改指令】
|
||||||
|
{modify_instruction}
|
||||||
|
|
||||||
|
{image_context}
|
||||||
|
|
||||||
|
【更新规则】
|
||||||
|
1. 仔细理解用户的修改指令,找出需要修改的具体特征
|
||||||
|
2. 如果有参考图片,请识别参考图片中的关键视觉特征(如服装款式、颜色、材质、配饰等)
|
||||||
|
3. 将修改内容准确融入原始描述中,替换或补充相关部分
|
||||||
|
4. 保持描述的流畅性和一致性
|
||||||
|
5. 保留未被修改的原有特征
|
||||||
|
6. 遵循以下描述规范:
|
||||||
|
- 禁止写表情、姿态、动作
|
||||||
|
- 禁止写背景/环境/道具
|
||||||
|
- 禁止描写身体部位颜色(皮肤色、唇色、眼睛颜色等)
|
||||||
|
- 使用中文输出,长度 80-150 字
|
||||||
|
|
||||||
|
【输出格式】
|
||||||
|
只返回JSON格式,禁止返回任何其他内容:
|
||||||
|
{
|
||||||
|
"prompt": "更新后的完整角色描述"
|
||||||
|
}
|
||||||
22
lib/prompts/novel-promotion/character_modify.en.txt
Normal file
22
lib/prompts/novel-promotion/character_modify.en.txt
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
You are a professional character prompt modifier.
|
||||||
|
Modify an existing character description based on user instruction.
|
||||||
|
|
||||||
|
Current description:
|
||||||
|
{character_input}
|
||||||
|
|
||||||
|
User instruction:
|
||||||
|
{user_input}
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
1. Keep identity consistency.
|
||||||
|
2. Apply only requested edits, keep other valid details.
|
||||||
|
3. Return one complete rewritten prompt, not partial fragments.
|
||||||
|
4. Output in English only.
|
||||||
|
5. Do not describe expression, action, background, scene, or props.
|
||||||
|
6. Do not mention skin color, eye color, or lip color.
|
||||||
|
|
||||||
|
Output format:
|
||||||
|
Return JSON only:
|
||||||
|
{
|
||||||
|
"prompt": "modified character description"
|
||||||
|
}
|
||||||
52
lib/prompts/novel-promotion/character_modify.zh.txt
Normal file
52
lib/prompts/novel-promotion/character_modify.zh.txt
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
请按照以下规则执行用户的人物生成提示词修改需求
|
||||||
|
1:人物规则按照以下规则修改
|
||||||
|
【人物生成要求(用于出图,中文描述)】
|
||||||
|
|
||||||
|
1. 生成1条详细的中文外貌描述,供AI图片生成使用
|
||||||
|
|
||||||
|
2. 描述要求突出角色特色,有细节质感:
|
||||||
|
- 性别、年龄范围(写具体年龄区间,如"约二十五岁"、"四十至四十五岁"、"五十岁左右")
|
||||||
|
- 面部:脸型、五官特征(如高挺鼻梁、深邃眼窝、薄唇等具体特征)
|
||||||
|
- 眼睛:形状、大小(禁止描写眼睛颜色)
|
||||||
|
- 头发:颜色、长度、发型、发质(如蓬松卷发、挑染银灰、发尾微卷等)
|
||||||
|
- 体型:身高感、体态、肩宽、腰线等
|
||||||
|
- 皮肤:只描述质感和独特标记(如光滑/粗糙、雀斑、胎记、疤痕、纹身等),禁止描述肤色
|
||||||
|
- 服装:款式、材质、配色、细节(如机车皮夹克、破洞牛仔裤、金属拉链、刺绣图案等)
|
||||||
|
- 鞋子:款式、颜色、材质(如黑色马丁靴、白色帆布鞋、棕色皮质牛津鞋、红底高跟鞋、绣花布鞋等)
|
||||||
|
- 配饰:耳钉、项链、手表、戒指等突出个性的配饰
|
||||||
|
|
||||||
|
3. 描述规范:
|
||||||
|
- 禁止写表情、姿态、动作
|
||||||
|
- 禁止写背景/环境/道具
|
||||||
|
- 不得加入情绪形容词与故事性句子
|
||||||
|
- 【禁止身体颜色描述】禁止描写任何身体部位的颜色,AI会过度放大颜色描述导致效果失真:
|
||||||
|
❌ 皮肤颜色(如偏黄、白皙、小麦色、古铜色、黝黑等)
|
||||||
|
❌ 唇色(如红润、粉色、苍白等)
|
||||||
|
❌ 眼睛颜色(如黑色、棕色、蓝色瞳孔、琥珀色等)
|
||||||
|
❌ 脸色(如红润、苍白、蜡黄等)
|
||||||
|
✅ 可以描写:皮肤质感(光滑/粗糙)、独特标记(雀斑/疤痕/纹身)、头发颜色、服装颜色
|
||||||
|
- 如原文对外貌有描述,以原文为最优先(但颜色描述仍需过滤)
|
||||||
|
- 使用中文输出,长度 80-150 字
|
||||||
|
- 不包含艺术风格、画风、光影效果描述(系统自动添加)
|
||||||
|
- 【年代一致性】根据故事背景判断年代(古代/近代/现代/未来等),人物的服装、发型、配饰必须符合该年代特征
|
||||||
|
- 【禁止不确定描述】禁止使用"或"、"可能"、"也许"、"大概"等不确定词汇,每个外貌特征必须明确具体
|
||||||
|
❌ 错误:"戴着无框或金框眼镜"、"身高可能一米七左右"
|
||||||
|
✅ 正确:"戴着金色细框眼镜"、"身材高挑约一米七五"
|
||||||
|
- 【禁止抽象气质描述】禁止描述无法视觉化的抽象气质、氛围、神态、感受
|
||||||
|
❌ 错误:"举手投足间透着富贵气"、"气场强大"、"成熟稳重的气息"
|
||||||
|
✅ 正确:只描述可直接看到的外貌特征
|
||||||
|
- 【鞋子必填】每个完整人物描述必须包含鞋子描述,不可遗漏
|
||||||
|
- 【非人类角色处理】如果当前角色是非人类(动物、神话生物、知名形象等):
|
||||||
|
* 不受人类外貌模板限制,根据实际形态描述
|
||||||
|
* 描述开头保持角色名或物种名
|
||||||
|
* 示例:修改"孙悟空"的服装 → "孙悟空,金毛蓬松,换上白色僧袍,头戴紧箍咒..."
|
||||||
|
|
||||||
|
2:你的目标是根据用户发送你的需求将人物修改为符合用户提示词的样子
|
||||||
|
|
||||||
|
以下是原本的人物生成提示词:{character_input}
|
||||||
|
以下是用户的修改指令:{user_input}
|
||||||
|
|
||||||
|
修改后发送json格式给我,只返回以下json格式,禁止返回一切除json以外的多余内容,注释,文字等等,只返回无任何markdown标识符的纯净json格式,json格式如下
|
||||||
|
{
|
||||||
|
"prompt":"xxxxx"
|
||||||
|
}
|
||||||
32
lib/prompts/novel-promotion/character_regenerate.en.txt
Normal file
32
lib/prompts/novel-promotion/character_regenerate.en.txt
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
You are a character appearance regenerator.
|
||||||
|
Generate 3 new character appearance variants based on story context.
|
||||||
|
|
||||||
|
Character name:
|
||||||
|
{character_name}
|
||||||
|
|
||||||
|
Appearance type / reason:
|
||||||
|
{change_reason}
|
||||||
|
|
||||||
|
Current descriptions (reference only, do not copy directly):
|
||||||
|
{current_descriptions}
|
||||||
|
|
||||||
|
Story context:
|
||||||
|
{novel_text}
|
||||||
|
|
||||||
|
Requirements:
|
||||||
|
1. Produce 3 clearly different variants while preserving core identity.
|
||||||
|
2. Each variant must be a full standalone appearance description.
|
||||||
|
3. Output in English.
|
||||||
|
4. Do not include expression, action, background, or story narration.
|
||||||
|
5. Do not include skin color, eye color, or lip color.
|
||||||
|
6. Keep each description concise and image-generation friendly.
|
||||||
|
|
||||||
|
Output format:
|
||||||
|
Return JSON only:
|
||||||
|
{
|
||||||
|
"descriptions": [
|
||||||
|
"variant 1",
|
||||||
|
"variant 2",
|
||||||
|
"variant 3"
|
||||||
|
]
|
||||||
|
}
|
||||||
60
lib/prompts/novel-promotion/character_regenerate.zh.txt
Normal file
60
lib/prompts/novel-promotion/character_regenerate.zh.txt
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
你是"角色形象重塑师"。请根据小说原文,为指定角色重新生成 3 条全新的外貌描述。
|
||||||
|
|
||||||
|
【角色信息】
|
||||||
|
- 角色名:{character_name}
|
||||||
|
- 形象类型:{change_reason}
|
||||||
|
- 当前描述(参考,需要生成不同的):
|
||||||
|
{current_descriptions}
|
||||||
|
|
||||||
|
【生成要求】
|
||||||
|
1. 根据小说原文对该角色的描写,生成 3 条各有特色、互不相同的外貌描述
|
||||||
|
2. 必须与当前描述有明显差异(换一种风格/配色/细节),但保持角色核心特征
|
||||||
|
3. ⚠️ 【重要】每条描述都必须是【完整的人物描述】,包含所有基础特征(面部、眼睛、体型等)+ 服装/状态,禁止只写变化部分
|
||||||
|
4. 【非人类角色处理】
|
||||||
|
- 如果角色是非人类(动物、神话生物、知名形象等),不受人类模板限制
|
||||||
|
- 每条描述开头必须以角色名或物种名开始
|
||||||
|
- 根据实际形态自由描述外观特征
|
||||||
|
5. 描述内容:
|
||||||
|
- 性别、年龄段(不写具体数字)
|
||||||
|
- 面部:脸型、五官特征(如高挺鼻梁、深邃眼窝、薄唇等)
|
||||||
|
- 眼睛:形状、大小(禁止描写眼睛颜色)
|
||||||
|
- 头发:颜色、长度、发型、发质(如蓬松卷发、挑染银灰等)
|
||||||
|
- 体型:身高感、体态、肩宽、腰线等
|
||||||
|
- 皮肤:只描述质感和独特标记(如光滑/粗糙、雀斑、疤痕、纹身等),禁止描述肤色
|
||||||
|
- 服装:款式、材质、配色、细节(如机车皮夹克、金属拉链等)
|
||||||
|
- 鞋子:款式、颜色、材质(如黑色马丁靴、白色帆布鞋、棕色皮质牛津鞋、红底高跟鞋、绣花布鞋等)
|
||||||
|
- 配饰:耳钉、项链、手表、戒指等突出个性的配饰
|
||||||
|
|
||||||
|
4. 描述规范:
|
||||||
|
- 禁止写表情、姿态、动作
|
||||||
|
- 禁止写背景/环境/道具
|
||||||
|
- 不得加入情绪形容词与故事性句子
|
||||||
|
- 【禁止身体颜色描述】禁止描写任何身体部位的颜色,AI会过度放大颜色描述导致效果失真:
|
||||||
|
❌ 皮肤颜色(如偏黄、白皙、小麦色、古铜色、黝黑等)
|
||||||
|
❌ 唇色(如红润、粉色、苍白等)
|
||||||
|
❌ 眼睛颜色(如黑色、棕色、蓝色瞳孔、琥珀色等)
|
||||||
|
❌ 脸色(如红润、苍白、蜡黄等)
|
||||||
|
✅ 可以描写:皮肤质感(光滑/粗糙)、独特标记(雀斑/疤痕/纹身)、头发颜色、服装颜色
|
||||||
|
- 如原文对外貌有描述,以原文为最优先(但颜色描述仍需过滤)
|
||||||
|
- 使用中文输出,长度 80-150 字
|
||||||
|
- 不包含艺术风格、画风、光影效果描述(系统自动添加)
|
||||||
|
- 【年代一致性】根据故事背景判断年代,服装、发型、配饰必须符合该年代特征
|
||||||
|
|
||||||
|
【输出格式】只返回以下 JSON,不要任何其他内容
|
||||||
|
{
|
||||||
|
"descriptions": [
|
||||||
|
"新描述1(80-150字)",
|
||||||
|
"新描述2(80-150字)",
|
||||||
|
"新描述3(80-150字)"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
【小说原文】
|
||||||
|
{novel_text}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
40
lib/prompts/novel-promotion/episode_split.en.txt
Normal file
40
lib/prompts/novel-promotion/episode_split.en.txt
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
You are a long-text episode splitter.
|
||||||
|
Analyze the full text and split it into balanced episodes.
|
||||||
|
|
||||||
|
Input text:
|
||||||
|
{CONTENT}
|
||||||
|
|
||||||
|
Core rules:
|
||||||
|
1. Keep episode lengths as balanced as possible.
|
||||||
|
2. Prefer natural breakpoints (chapter boundary, scene shift, time jump).
|
||||||
|
3. Keep chronology and full coverage (no overlap, no missing text).
|
||||||
|
4. Provide reliable startMarker and endMarker copied from source text.
|
||||||
|
5. Return JSON only.
|
||||||
|
|
||||||
|
Output format:
|
||||||
|
{
|
||||||
|
"analysis": {
|
||||||
|
"totalWords": 0,
|
||||||
|
"episodeCount": 0,
|
||||||
|
"targetWordsPerEpisode": 0,
|
||||||
|
"allowedRange": "0-0"
|
||||||
|
},
|
||||||
|
"episodes": [
|
||||||
|
{
|
||||||
|
"number": 1,
|
||||||
|
"title": "Episode title",
|
||||||
|
"summary": "Short summary",
|
||||||
|
"estimatedWords": 0,
|
||||||
|
"startMarker": "exact start marker",
|
||||||
|
"endMarker": "exact end marker",
|
||||||
|
"startIndex": 0,
|
||||||
|
"endIndex": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"validation": {
|
||||||
|
"maxWords": 0,
|
||||||
|
"minWords": 0,
|
||||||
|
"variance": 0,
|
||||||
|
"isBalanced": true
|
||||||
|
}
|
||||||
|
}
|
||||||
94
lib/prompts/novel-promotion/episode_split.zh.txt
Normal file
94
lib/prompts/novel-promotion/episode_split.zh.txt
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
你是一个专业的内容分析助手。请分析以下文本,将其智能分割为多个剧集。
|
||||||
|
|
||||||
|
## ⚠️ 核心规则:字数必须均衡(最重要)
|
||||||
|
|
||||||
|
**所有剧集的字数必须尽可能均衡!偏差不得超过 ±20%**
|
||||||
|
|
||||||
|
### 📊 第一步:精确计算(必须执行)
|
||||||
|
|
||||||
|
1. 统计总字数:count_total_characters(文本)
|
||||||
|
2. 计算目标集数:total ÷ 650 = N 集(四舍五入)
|
||||||
|
3. 计算每集目标字数:target = total ÷ N
|
||||||
|
4. 确定允许范围:[target × 0.8, target × 1.2]
|
||||||
|
|
||||||
|
**示例**:
|
||||||
|
- 总字数 6500 字 → 10 集 → 每集目标 650 字 → 范围 520-780 字
|
||||||
|
- 总字数 13000 字 → 20 集 → 每集目标 650 字 → 范围 520-780 字
|
||||||
|
|
||||||
|
### 📐 第二步:均衡分割(必须执行)
|
||||||
|
|
||||||
|
❌ **绝对禁止**:
|
||||||
|
- 任何一集超过 target × 1.3(如目标650字,禁止超过845字)
|
||||||
|
- 任何一集少于 target × 0.6(如目标650字,禁止少于390字)
|
||||||
|
- 前几集很长、后几集很短(或反过来)
|
||||||
|
|
||||||
|
✅ **必须保证**:
|
||||||
|
- 所有集的字数在目标值 ±20% 范围内
|
||||||
|
- 最长集与最短集的差距不超过 300 字
|
||||||
|
- 字数分布均匀,不能头重脚轻或头轻脚重
|
||||||
|
|
||||||
|
### 🎬 第三步:寻找分割点
|
||||||
|
|
||||||
|
1. **优先识别自然断点**:
|
||||||
|
- 章节标记:「第X集」「Chapter X」「Episode X」
|
||||||
|
- 场景编号:`X-Y【场景】` 中的 X 变化(1-x → 2-x 是新集)
|
||||||
|
- 时间跳跃:「第二天」「三个月后」
|
||||||
|
|
||||||
|
2. **在自然断点附近微调**:
|
||||||
|
- 如果自然断点导致字数不均,可在附近段落边界调整
|
||||||
|
- 优先在对话结束、场景转换处分割
|
||||||
|
- 宁可牺牲一点叙事连贯性,也要保证字数均衡
|
||||||
|
|
||||||
|
## 输入文本
|
||||||
|
|
||||||
|
{{CONTENT}}
|
||||||
|
|
||||||
|
## 📝 输出格式
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"analysis": {
|
||||||
|
"totalWords": 6500,
|
||||||
|
"episodeCount": 10,
|
||||||
|
"targetWordsPerEpisode": 650,
|
||||||
|
"allowedRange": "520-780"
|
||||||
|
},
|
||||||
|
"episodes": [
|
||||||
|
{
|
||||||
|
"number": 1,
|
||||||
|
"title": "剧集标题(4-8字)",
|
||||||
|
"summary": "50字以内的剧情简介",
|
||||||
|
"estimatedWords": 650,
|
||||||
|
"startMarker": "该集开头的前20个字符(精确复制原文)",
|
||||||
|
"endMarker": "该集结尾的后20个字符(精确复制原文)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"validation": {
|
||||||
|
"maxWords": 720,
|
||||||
|
"minWords": 590,
|
||||||
|
"variance": 130,
|
||||||
|
"isBalanced": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## ⚠️ 最终验证清单(输出前必须检查)
|
||||||
|
|
||||||
|
在输出之前,你必须验证以下条件:
|
||||||
|
|
||||||
|
1. ☐ episodes.length ≈ totalWords ÷ 650(误差 ±1 集)
|
||||||
|
2. ☐ 所有 estimatedWords 都在 allowedRange 范围内
|
||||||
|
3. ☐ maxWords - minWords ≤ 300 字
|
||||||
|
4. ☐ 没有任何一集超过 850 字
|
||||||
|
5. ☐ 没有任何一集少于 400 字
|
||||||
|
6. ☐ 上一集 endMarker 紧邻下一集 startMarker,无内容遗漏
|
||||||
|
7. ☐ endMarker 不包含下一集的任何内容
|
||||||
|
|
||||||
|
**如果验证失败,必须重新调整分割点直到通过!**
|
||||||
|
|
||||||
|
## 🔧 场景编号说明
|
||||||
|
|
||||||
|
- `X-Y【场景描述】` 格式中,X = 集数,Y = 场景序号
|
||||||
|
- 1-1, 1-2, 1-3 都属于第 1 集
|
||||||
|
- 2-1 开始第 2 集
|
||||||
|
- 分集在 X 变化时进行
|
||||||
24
lib/prompts/novel-promotion/image_prompt_modify.en.txt
Normal file
24
lib/prompts/novel-promotion/image_prompt_modify.en.txt
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
You are a prompt refinement expert for storyboard image/video generation.
|
||||||
|
|
||||||
|
Current image prompt:
|
||||||
|
{prompt_input}
|
||||||
|
|
||||||
|
Current video prompt:
|
||||||
|
{video_prompt_input}
|
||||||
|
|
||||||
|
User instruction:
|
||||||
|
{user_input}
|
||||||
|
|
||||||
|
Requirements:
|
||||||
|
1. Return an updated image prompt and video prompt.
|
||||||
|
2. Keep subject identity and scene continuity unless user asks to change.
|
||||||
|
3. Write in concise English.
|
||||||
|
4. Image prompt should focus on visual composition.
|
||||||
|
5. Video prompt should focus on motion/performance/camera behavior.
|
||||||
|
|
||||||
|
Output format:
|
||||||
|
Return JSON only:
|
||||||
|
{
|
||||||
|
"image_prompt": "updated image prompt",
|
||||||
|
"video_prompt": "updated video prompt"
|
||||||
|
}
|
||||||
38
lib/prompts/novel-promotion/image_prompt_modify.zh.txt
Normal file
38
lib/prompts/novel-promotion/image_prompt_modify.zh.txt
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
请按照以下提示词规则执行用户的ai生图以及视频运动提示词场景需求
|
||||||
|
1. 详细描述镜头角度和地点、动作、人物、环境,也就是谁、和谁、在哪里、干了什么,如果是空镜标题等,那么也要描述出原文想要表达的东西,例如作者名字,或者和原文有关的内容,提示词务必详细完整
|
||||||
|
|
||||||
|
2. **场景描述使用规则(重要):**
|
||||||
|
- locations字段:只写场景名字(如"病房_白天")
|
||||||
|
- image_prompt字段:直接使用场景名字(如"病房_白天"),可以结合具体位置描述(如"病房_白天的窗边")
|
||||||
|
- **禁止添加光线、色调、天气描述**:场景资产已包含完整的光线和色调信息,提示词中不要添加"阳光"、"光线"、"明亮"、"昏暗"、"温暖色调"、"冷色调"、"天气"等描述
|
||||||
|
- **人物所在的位置和互动的地方要和场景提示词中已有的物品有关系,不要出现人物和场景没有的关系进行互动**
|
||||||
|
|
||||||
|
3. **人物描述使用规则:**
|
||||||
|
- characters字段:只写人物名字(如"Victor")
|
||||||
|
- image_prompt字段:直接使用人物名字(如"Victor"),可以结合动作和情绪描述(如"Victor站在门口,表情严肃")
|
||||||
|
- 写图片生成提示词的时候务必要写明地点+人物名字+情绪+动作
|
||||||
|
- 我们这个是有声书第一视角,第一视角默认就是主角的视角,也就是在pov中的人物决策,如无其他意外那么这个就是主角,按照这个名字来进行主视角生成
|
||||||
|
|
||||||
|
4. 我们的场景限制只对有效目前人物处在实际发生地点的有效!例如有回忆、旁白等情况,我们不应该完全限制在固定场景之内,而是可以插入多样性的画面
|
||||||
|
|
||||||
|
5. 使用中文输出提示词,提示词编写规范按照用简洁连贯的自然语言写明:主体 + 行为 + 环境 + 空间关系
|
||||||
|
例如:Dr.Smith站在病床边,看着躺在床上的Mary,病房_白天
|
||||||
|
|
||||||
|
6.涉及到文字内容的全部使用原文srt的语言作为输出,如原文srt是英文,就代表着我们这个面向英文观众,那么如果会画面内容里面出现文字的话(例如出现了医院名字的特写)就要输出英文的具体文字提示词(注意,主要提示词还是中文),如:医院的镜头特写,上面写着hospital
|
||||||
|
|
||||||
|
7.使用主体+行为+环境的提示词,确保不要错过主体,除非无主体的空镜,否则一定要交代主体是谁
|
||||||
|
|
||||||
|
按照用户的修改指令把提示词修改为用户需要的样子,用户可能会发送一些额外内容让你修改,例如让你把人物修改为另外一个人物,这个时候你会看到人物或场景的具体描述词
|
||||||
|
例如用户输入:把张三(黑色长发,穿着西装...)修改为小明(蓝色头发,红色眼睛...)然后就可以利用这些具体描述词来按照以上的生成提示词规则修改。
|
||||||
|
|
||||||
|
当前的图片提示词:{prompt_input}
|
||||||
|
|
||||||
|
当前的视频提示词:{video_prompt_input}
|
||||||
|
|
||||||
|
用户的修改指令:{user_input}
|
||||||
|
|
||||||
|
结果发送json格式给我,只返回以下json格式,禁止返回一切除json以外的多余内容,注释,文字等等,只返回无任何markdown标识符的纯净json格式,json格式如下
|
||||||
|
{
|
||||||
|
"image_prompt": "修改后的图片提示词(静态画面描述)",
|
||||||
|
"video_prompt": ""
|
||||||
|
}
|
||||||
19
lib/prompts/novel-promotion/location_create.en.txt
Normal file
19
lib/prompts/novel-promotion/location_create.en.txt
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
You are a professional environment prompt designer.
|
||||||
|
Generate one scene prompt for image generation.
|
||||||
|
|
||||||
|
User request:
|
||||||
|
{user_input}
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
1. Output in English only.
|
||||||
|
2. Start with scene name in this format: "[Scene Name] ..."
|
||||||
|
3. Describe a wide, clear environment with spatial layout and key objects.
|
||||||
|
4. Mention lighting direction and atmosphere.
|
||||||
|
5. No protagonist actions or dialogue.
|
||||||
|
6. If crowd is implied by context, use generic crowd terms only (guests, pedestrians, audience).
|
||||||
|
|
||||||
|
Output format:
|
||||||
|
Return JSON only:
|
||||||
|
{
|
||||||
|
"prompt": "[Scene Name] environment description"
|
||||||
|
}
|
||||||
30
lib/prompts/novel-promotion/location_create.zh.txt
Normal file
30
lib/prompts/novel-promotion/location_create.zh.txt
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
请按照以下提示词规则执行用户的生成场景需求
|
||||||
|
|
||||||
|
【场景生成要求(用于出图,中文描述)】
|
||||||
|
|
||||||
|
1. 生成1条中文环境描述(60-120字),像真实摄影场景一样描述
|
||||||
|
|
||||||
|
2. **开头必须明确写明场景名称**:
|
||||||
|
- 描述开头必须以"【场景名称】"的形式标注空间属性
|
||||||
|
- 示例:「皇宫」殿内铺设着... / 「客厅」窗外阳光透过... / 「卧室」床边放着...
|
||||||
|
- 这样AI在生成图片时能明确理解这是什么类型的空间
|
||||||
|
|
||||||
|
3. 核心原则:
|
||||||
|
- 写真实存在的物体,不要写抽象感受
|
||||||
|
- 材质要具体(深棕色实木地板、青灰色石砖墙、做旧铁艺栏杆)
|
||||||
|
- 物品要有使用痕迹和生活气息(桌上散落的书籍、墙角堆放的杂物、窗台晒干的植物)
|
||||||
|
- 光线要写清楚来源和效果(午后阳光斜照进来在地板上拉出长影、暖黄色壁灯打在墙面上)
|
||||||
|
|
||||||
|
4. 禁止:不写主角人物具体动作、不写画风、不写"温馨""优雅"等抽象词
|
||||||
|
|
||||||
|
5. 【人群处理规则】
|
||||||
|
- 如果用户描述的场景暗示有人群(如宴会厅、集市、教室上课等),在描述中加入模糊人群元素
|
||||||
|
- 人群描述示例:"大厅中宾客三两成群"、"街道上行人往来"、"座位上零散坐着几位观众"
|
||||||
|
- 如果是私密空间或用户明确要求空镜,则不添加人群
|
||||||
|
|
||||||
|
以下是用户的生成指令:{user_input}
|
||||||
|
|
||||||
|
只返回以下json格式,禁止返回一切除json以外的多余内容
|
||||||
|
{
|
||||||
|
"prompt":"「场景名称」场景描述内容"
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
You are a scene description editor.
|
||||||
|
Update the original location description based on user instruction.
|
||||||
|
|
||||||
|
Location name:
|
||||||
|
{location_name}
|
||||||
|
|
||||||
|
Original description:
|
||||||
|
{original_description}
|
||||||
|
|
||||||
|
User instruction:
|
||||||
|
{modify_instruction}
|
||||||
|
|
||||||
|
Reference image context (may be empty):
|
||||||
|
{image_context}
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
1. Keep unchanged scene elements unless explicitly modified.
|
||||||
|
2. Return one complete updated description in English.
|
||||||
|
3. Keep scene name at the beginning: "[{location_name}] ..."
|
||||||
|
4. No protagonist actions or story narration.
|
||||||
|
|
||||||
|
Output format:
|
||||||
|
Return JSON only:
|
||||||
|
{
|
||||||
|
"prompt": "updated location description"
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
你是一个专业的场景描述更新专家。
|
||||||
|
|
||||||
|
【任务】
|
||||||
|
根据用户对场景图片的修改,更新场景的描述词。
|
||||||
|
|
||||||
|
【场景名称】
|
||||||
|
{location_name}
|
||||||
|
|
||||||
|
【原始场景描述】
|
||||||
|
{original_description}
|
||||||
|
|
||||||
|
【用户修改指令】
|
||||||
|
{modify_instruction}
|
||||||
|
|
||||||
|
{image_context}
|
||||||
|
|
||||||
|
【更新规则】
|
||||||
|
1. **开头必须明确写明场景名称**:
|
||||||
|
- 描述开头必须以「{location_name}」的形式标注空间属性
|
||||||
|
- 示例:「皇宫」殿内铺设着... / 「客厅」窗外阳光透过...
|
||||||
|
- 这样AI在生成图片时能明确理解这是什么类型的空间
|
||||||
|
|
||||||
|
2. 仔细理解用户的修改指令,找出需要修改的具体特征
|
||||||
|
3. 如果有参考图片,请识别参考图片中的关键视觉特征(如建筑风格、装饰元素、光线氛围、色调等)
|
||||||
|
4. 将修改内容准确融入原始描述中,替换或补充相关部分
|
||||||
|
5. 保持描述的流畅性和一致性
|
||||||
|
6. 保留未被修改的原有特征
|
||||||
|
7. 遵循以下描述规范:
|
||||||
|
- 只描述场景本身,禁止描述人物
|
||||||
|
- 使用中文输出,长度 50-100 字
|
||||||
|
|
||||||
|
【输出格式】
|
||||||
|
只返回JSON格式,禁止返回任何其他内容:
|
||||||
|
{
|
||||||
|
"prompt": "「场景名」更新后的完整场景描述"
|
||||||
|
}
|
||||||
24
lib/prompts/novel-promotion/location_modify.en.txt
Normal file
24
lib/prompts/novel-promotion/location_modify.en.txt
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
You are a professional scene prompt modifier.
|
||||||
|
Modify an existing scene description while preserving scene identity.
|
||||||
|
|
||||||
|
Location name:
|
||||||
|
{location_name}
|
||||||
|
|
||||||
|
Current description:
|
||||||
|
{location_input}
|
||||||
|
|
||||||
|
User instruction:
|
||||||
|
{user_input}
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
1. Keep core scene identity and function.
|
||||||
|
2. Apply requested changes with concrete visual details.
|
||||||
|
3. Output in English only.
|
||||||
|
4. Start with scene name: "[{location_name}] ..."
|
||||||
|
5. No protagonist actions, dialogue, or narrative plot.
|
||||||
|
|
||||||
|
Output format:
|
||||||
|
Return JSON only:
|
||||||
|
{
|
||||||
|
"prompt": "modified location description"
|
||||||
|
}
|
||||||
64
lib/prompts/novel-promotion/location_modify.zh.txt
Normal file
64
lib/prompts/novel-promotion/location_modify.zh.txt
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
请按照以下提示词规则执行用户的修改场景需求
|
||||||
|
|
||||||
|
【场景名称】
|
||||||
|
{location_name}
|
||||||
|
|
||||||
|
【场景生成要求(用于出图,中文描述)】
|
||||||
|
|
||||||
|
1. **开头必须明确写明场景名称**:
|
||||||
|
- 描述开头必须以「{location_name}」的形式标注空间属性
|
||||||
|
- 示例:「皇宫」殿内铺设着... / 「客厅」窗外阳光透过...
|
||||||
|
- 这样AI在生成图片时能明确理解这是什么类型的空间
|
||||||
|
|
||||||
|
2. 每个场景生成 1 条中文环境描述,用于AI图片生成
|
||||||
|
|
||||||
|
3. 必须包含具体的视觉元素,按以下结构描述:
|
||||||
|
|
||||||
|
**空间定位**:明确场景类型
|
||||||
|
- 室内:客厅/卧室/厨房/办公室/医院病房/教室等
|
||||||
|
- 室外:街道/巷子/公园/山谷/海边/广场等
|
||||||
|
|
||||||
|
**主要结构**:描述可见的建筑元素
|
||||||
|
- 墙面/地面/天花板的材质和颜色(如"白色墙面"/"木质地板"/"水泥地面"/"瓷砖地面")
|
||||||
|
- 门窗位置和类型(如"左侧有大窗户"/"右侧木门"/"落地窗"/"玻璃门")
|
||||||
|
- 建筑特征(如"高天花板"/"拱形门"/"砖墙"/"玻璃幕墙")
|
||||||
|
|
||||||
|
**家具/道具**:列出场景中的主要物体
|
||||||
|
- 家具摆放(如"中央有沙发"/"墙边书架"/"床靠窗"/"办公桌")
|
||||||
|
- 特征道具(如"桌上有台灯"/"墙上挂画"/"地上地毯"/"书架上有书")
|
||||||
|
- 植物装饰(如"窗台有盆栽"/"角落有绿植")
|
||||||
|
|
||||||
|
**光线环境**:描述光源和照明效果
|
||||||
|
- 自然光:时间段+光线特征
|
||||||
|
* 白天:窗外阳光透过窗帘/明亮日光从窗户照入/柔和晨光
|
||||||
|
* 夜晚:月光从窗外照入/窗外夜色/窗外灯光
|
||||||
|
- 人工光:光源类型+位置
|
||||||
|
* 天花板吊灯照明/墙壁壁灯柔和光线/台灯局部照明/落地灯/射灯
|
||||||
|
|
||||||
|
**氛围细节**:补充环境特征
|
||||||
|
- 整体色调:暖色调/冷色调/中性色/明亮/昏暗
|
||||||
|
- 空间感:宽敞/狭窄/纵深感强/开阔/封闭
|
||||||
|
- 状态特征:整洁/凌乱/陈旧/现代/简约/复古
|
||||||
|
|
||||||
|
4. 描述规范:
|
||||||
|
- 使用具体名词,避免抽象形容词
|
||||||
|
* ✅ 好:"木质书桌"/"灰色布艺沙发"/"白色窗帘"
|
||||||
|
* ❌ 差:"优雅的家具"/"舒适的环境"/"温馨的氛围"
|
||||||
|
- 描述固定元素,不写主角人物具体动作、情绪
|
||||||
|
- 【人群处理规则】如果用户要求添加人群,或场景本身暗示有人群(如宴会、集市等):
|
||||||
|
* 可以加入模糊人群描述:\"人群\"、\"宾客\"、\"路人\"等
|
||||||
|
* 示例:\"大厅远处三两宾客交谈\"、\"街角有行人匆匆走过\"
|
||||||
|
- 长度控制在 60-100 字
|
||||||
|
- 不包含艺术风格描述(如"美式漫画风"/"水彩风"),风格由系统自动添加
|
||||||
|
- 不包含光影效果描述(如"dramatic lighting"/"柔和渐变"),由风格控制
|
||||||
|
|
||||||
|
你的目标是根据用户的修改指令,在原有场景描述的基础上进行修改
|
||||||
|
|
||||||
|
当前场景描述:{location_input}
|
||||||
|
|
||||||
|
用户的修改指令:{user_input}
|
||||||
|
|
||||||
|
发送json格式给我,只返回以下json格式,禁止返回一切除json以外的多余内容,注释,文字等等,只返回无任何markdown标识符的纯净json格式,json格式如下
|
||||||
|
{
|
||||||
|
"prompt":"「场景名」xxxxx"
|
||||||
|
}
|
||||||
25
lib/prompts/novel-promotion/location_regenerate.en.txt
Normal file
25
lib/prompts/novel-promotion/location_regenerate.en.txt
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
You are a scene variant regenerator.
|
||||||
|
Generate 3 new scene description variants for the same location.
|
||||||
|
|
||||||
|
Location name:
|
||||||
|
{location_name}
|
||||||
|
|
||||||
|
Current descriptions (reference):
|
||||||
|
{current_descriptions}
|
||||||
|
|
||||||
|
Requirements:
|
||||||
|
1. Generate 3 clearly different but same-location variants.
|
||||||
|
2. Keep the scene name prefix in each line: "[{location_name}] ..."
|
||||||
|
3. Output in English only.
|
||||||
|
4. Keep environment-only description (no protagonist actions).
|
||||||
|
5. Keep each variant concise and image-generation friendly.
|
||||||
|
|
||||||
|
Output format:
|
||||||
|
Return JSON only:
|
||||||
|
{
|
||||||
|
"descriptions": [
|
||||||
|
"[{location_name}] variant 1",
|
||||||
|
"[{location_name}] variant 2",
|
||||||
|
"[{location_name}] variant 3"
|
||||||
|
]
|
||||||
|
}
|
||||||
41
lib/prompts/novel-promotion/location_regenerate.zh.txt
Normal file
41
lib/prompts/novel-promotion/location_regenerate.zh.txt
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
你是"场景重塑师"。请根据当前的场景描述,为指定场景重新生成 3 条全新的场景描述变体。
|
||||||
|
|
||||||
|
【场景信息】
|
||||||
|
- 场景名:{location_name}
|
||||||
|
- 当前描述(作为参考,需要生成不同的变体):
|
||||||
|
{current_descriptions}
|
||||||
|
|
||||||
|
【生成要求】
|
||||||
|
1. **开头必须明确写明场景名称**:
|
||||||
|
- 每条描述开头必须以「{location_name}」的形式标注空间属性
|
||||||
|
- 示例:「皇宫」殿内铺设着... / 「客厅」窗外阳光透过...
|
||||||
|
- 这样AI在生成图片时能明确理解这是什么类型的空间
|
||||||
|
|
||||||
|
2. 根据当前描述的核心元素,生成 3 条各有特色、互不相同的场景描述变体
|
||||||
|
3. 必须与当前描述有明显差异(换一种氛围/布局/细节),但保持场景核心特征
|
||||||
|
4. 描述内容应包含:
|
||||||
|
- 空间结构:整体布局、空间大小、层次感
|
||||||
|
- 建筑/地形:建筑风格、地形特征、主要构造物
|
||||||
|
- 光影氛围:光源类型、明暗对比、色调倾向
|
||||||
|
- 材质细节:地面、墙面、物体的材质质感
|
||||||
|
- 环境元素:植物、天气、装饰物等
|
||||||
|
- 独特标识:该场景的标志性元素或特殊物件
|
||||||
|
|
||||||
|
5. 描述规范:
|
||||||
|
- 禁止写主角人物具体动作、剧情
|
||||||
|
- 【人群处理规则】如果当前描述中包含人群元素,新描述也应保持人群元素
|
||||||
|
* 可以调整人群的位置、密度、状态,但保持有人群存在
|
||||||
|
* 人群描述使用模糊词汇:"人群"、"宾客"、"路人"等
|
||||||
|
- 使用中文输出,长度 80-150 字
|
||||||
|
- 不包含艺术风格、画风描述(系统自动添加)
|
||||||
|
- 【年代一致性】根据场景特征判断年代,建筑、装饰、物品必须符合该年代特征
|
||||||
|
- 【时间一致性】如场景名包含"白天/黑夜/黄昏"等,描述中的光影必须匹配
|
||||||
|
|
||||||
|
【输出格式】只返回以下 JSON,不要任何其他内容
|
||||||
|
{
|
||||||
|
"descriptions": [
|
||||||
|
"「场景名」新描述1(80-150字)",
|
||||||
|
"「场景名」新描述2(80-150字)",
|
||||||
|
"「场景名」新描述3(80-150字)"
|
||||||
|
]
|
||||||
|
}
|
||||||
59
lib/prompts/novel-promotion/screenplay_conversion.en.txt
Normal file
59
lib/prompts/novel-promotion/screenplay_conversion.en.txt
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
You are a screenplay conversion specialist.
|
||||||
|
Convert the clip text into structured screenplay JSON without adding new story facts.
|
||||||
|
|
||||||
|
Clip ID:
|
||||||
|
{clip_id}
|
||||||
|
|
||||||
|
Clip content:
|
||||||
|
{clip_content}
|
||||||
|
|
||||||
|
Location library:
|
||||||
|
{locations_lib_name}
|
||||||
|
|
||||||
|
Character library:
|
||||||
|
{characters_lib_name}
|
||||||
|
|
||||||
|
Character introductions:
|
||||||
|
{characters_introduction}
|
||||||
|
|
||||||
|
Output format (JSON object only):
|
||||||
|
{
|
||||||
|
"clip_id": "{clip_id}",
|
||||||
|
"original_text": "original clip text",
|
||||||
|
"scenes": [
|
||||||
|
{
|
||||||
|
"scene_number": 1,
|
||||||
|
"heading": {
|
||||||
|
"int_ext": "INT or EXT",
|
||||||
|
"location": "location name",
|
||||||
|
"time": "morning/day/evening/night"
|
||||||
|
},
|
||||||
|
"description": "scene setup",
|
||||||
|
"characters": ["Character A", "Character B"],
|
||||||
|
"content": [
|
||||||
|
{
|
||||||
|
"type": "action",
|
||||||
|
"text": "action description"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "dialogue",
|
||||||
|
"character": "Character A",
|
||||||
|
"parenthetical": "optional performance cue",
|
||||||
|
"lines": "spoken line"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "voiceover",
|
||||||
|
"character": "Narrator or Character",
|
||||||
|
"text": "voiceover content"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
1. Preserve original story facts; do not invent new events.
|
||||||
|
2. Keep scene/content order aligned with source text.
|
||||||
|
3. Resolve character aliases to canonical names when possible.
|
||||||
|
4. Use the best matching location name from library; if none fits, use source location text.
|
||||||
|
5. Return strict JSON only.
|
||||||
247
lib/prompts/novel-promotion/screenplay_conversion.zh.txt
Normal file
247
lib/prompts/novel-promotion/screenplay_conversion.zh.txt
Normal file
@@ -0,0 +1,247 @@
|
|||||||
|
你是专业的编剧和剧本改编师。你的任务是将小说/文学文本转换为标准的影视剧本格式。
|
||||||
|
|
||||||
|
⚠️⚠️⚠️【最高优先级原则 - 100%忠实原文】⚠️⚠️⚠️
|
||||||
|
|
||||||
|
你的工作是**格式转换**,不是**创作**!你必须100%忠实于原文,严禁任何形式的"创造性发挥":
|
||||||
|
|
||||||
|
🚫 绝对禁止:
|
||||||
|
- 添加原文中没有的对话、动作、场景描述
|
||||||
|
- 扩展或改编原文内容(即使你觉得"更合理"或"更生动")
|
||||||
|
- 添加原文没有提及的角色反应、表情、心理活动
|
||||||
|
- 脑补原文没有描写的环境细节、道具、氛围
|
||||||
|
- 添加过渡性描述来"填充空白"
|
||||||
|
- 用你的理解"补全"原文的留白
|
||||||
|
|
||||||
|
✅ 你只能做:
|
||||||
|
- 将原文已有的内容转换为剧本格式
|
||||||
|
- 识别原文明确描述的场景、角色、对话
|
||||||
|
- 提取原文已有的动作和环境描述
|
||||||
|
|
||||||
|
如果原文内容简短或留白,你的剧本也应该简短,不要试图"丰富"它!
|
||||||
|
|
||||||
|
【核心职责】
|
||||||
|
|
||||||
|
1. 把小说文字转换为剧本格式(场景、对话、动作描述)
|
||||||
|
2. 提取场景的时间、地点、环境信息
|
||||||
|
3. 识别场景中出现的角色(匹配资产库)
|
||||||
|
4. 区分动作、对话、画外音等不同类型
|
||||||
|
5. ⭐100%保持原文信息的完整性,不增不减
|
||||||
|
|
||||||
|
【剧本格式规范】
|
||||||
|
|
||||||
|
标准剧本包含以下元素:
|
||||||
|
|
||||||
|
1. **场景头(Scene Heading)**:
|
||||||
|
- 格式: 内景/外景 + 地点 + 时间
|
||||||
|
- 示例: "内景 客厅 清晨" / "外景 取经路 白天"
|
||||||
|
|
||||||
|
2. **场景描述(Scene Description)**:
|
||||||
|
- 简洁描述场景环境、布局、关键道具
|
||||||
|
- 不需要过度细节,只需要建立基本视觉印象
|
||||||
|
|
||||||
|
3. **动作描述(Action)**:
|
||||||
|
- 描述角色的动作、表情、行为
|
||||||
|
- 连续的段落形式,不要拆分成碎片
|
||||||
|
|
||||||
|
4. **对话(Dialogue)**:
|
||||||
|
- 角色名字
|
||||||
|
- 副文本(parenthetical): 括号内的表演指导
|
||||||
|
- 台词内容
|
||||||
|
|
||||||
|
5. **画外音(Voiceover)**:
|
||||||
|
- 旁白、独白、回忆、读信等不在画面中的声音
|
||||||
|
- 标记角色(如果是特定角色的独白)
|
||||||
|
|
||||||
|
【转换规则】
|
||||||
|
|
||||||
|
## 1. 场景识别规则
|
||||||
|
|
||||||
|
- 分析原文,识别场景边界(地点变化、时间跨越)
|
||||||
|
- 每个场景必须包含:
|
||||||
|
* 内景(INT)或外景(EXT)
|
||||||
|
* 地点名称:
|
||||||
|
- 优先从场景资产库选择完全一致的名称
|
||||||
|
- ⚠️【重要】如果资产库中没有匹配的场景,直接使用原文中的场景名称,不要强行匹配错误的资产库场景
|
||||||
|
- 宁可输出资产库中不存在的场景名,也不要用错误的场景名(后续会自动创建缺失的资产)
|
||||||
|
* 时间段(清晨/上午/正午/下午/黄昏/夜晚/深夜)
|
||||||
|
|
||||||
|
## 2. 内容类型识别
|
||||||
|
|
||||||
|
必须准确区分以下类型:
|
||||||
|
|
||||||
|
**action** - 动作描述
|
||||||
|
- 角色的动作、表情、行为
|
||||||
|
- 场景变化、环境细节
|
||||||
|
- 示例: "孙悟空举起金箍棒,朝六耳猕猴砸去。"
|
||||||
|
|
||||||
|
**dialogue** - 对话
|
||||||
|
- 画面中角色的说话
|
||||||
|
- 必须包含:character(角色名)、lines(台词)
|
||||||
|
- 可选包含:parenthetical(副文本,如"愤怒地""小声")
|
||||||
|
- 示例:
|
||||||
|
角色: 孙悟空
|
||||||
|
副文本: 愤怒地
|
||||||
|
台词: "一个冒牌货,也敢拦你孙爷爷的路!"
|
||||||
|
|
||||||
|
**voiceover** - 画外音
|
||||||
|
- 旁白、独白、回忆中的声音、心理活动
|
||||||
|
- 不在画面中出现的声音
|
||||||
|
- 示例: "原来孙悟空真的死在了取经路上。" (旁白)
|
||||||
|
- 示例: 二郎神独白:"猴子死了,我却没有出手..." (特定角色的画外音)
|
||||||
|
|
||||||
|
## 3. 角色识别规则
|
||||||
|
|
||||||
|
- 优先从角色资产库中选择完全一致的名字
|
||||||
|
- ⚠️【重要】如果资产库中没有匹配的角色,直接使用原文中的角色名称,不要强行匹配错误的资产库角色
|
||||||
|
- 宁可输出资产库中不存在的角色名,也不要用错误的角色名(后续会自动创建缺失的资产)
|
||||||
|
- 不要使用简称:用"六耳猕猴"而非"六耳"
|
||||||
|
- 不要使用代词:用具体名字替代"他""她"
|
||||||
|
- characters数组只包含画面中出现的角色,不包括画外音角色
|
||||||
|
|
||||||
|
## 4. 副文本(Parenthetical)提取规则
|
||||||
|
|
||||||
|
从原文中识别并提取表演指导:
|
||||||
|
- "XX愤怒地说" → parenthetical: "愤怒地"
|
||||||
|
- "XX小声嘀咕" → parenthetical: "小声"
|
||||||
|
- "XX(转身)说" → parenthetical: "转身"
|
||||||
|
- "XX边走边说" → parenthetical: "边走边"
|
||||||
|
|
||||||
|
## 5. 动作连续性规则
|
||||||
|
|
||||||
|
- 动作描述应该是连续的段落,不要过度拆分
|
||||||
|
- 多个连续动作可以合并在一个action中
|
||||||
|
- 示例: ❌ 不好: "孙悟空站起来。" + "孙悟空走向门口。" + "孙悟空打开门。"
|
||||||
|
- 示例: ✅ 好: "孙悟空站起来,走向门口,打开门。"
|
||||||
|
|
||||||
|
【输出格式】
|
||||||
|
|
||||||
|
只返回JSON对象,不得有markdown代码块标记、注释或解释。
|
||||||
|
|
||||||
|
{
|
||||||
|
"clip_id": "{clip_id}",
|
||||||
|
"original_text": "原文内容",
|
||||||
|
|
||||||
|
"scenes": [
|
||||||
|
{
|
||||||
|
"scene_number": 1,
|
||||||
|
"heading": {
|
||||||
|
"int_ext": "INT或EXT",
|
||||||
|
"location": "场景名称(必须从资产库选择)",
|
||||||
|
"time": "时间段"
|
||||||
|
},
|
||||||
|
"description": "场景环境描述",
|
||||||
|
"characters": ["角色1", "角色2"],
|
||||||
|
|
||||||
|
"content": [
|
||||||
|
{
|
||||||
|
"type": "action",
|
||||||
|
"text": "动作描述文本"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "dialogue",
|
||||||
|
"character": "角色名",
|
||||||
|
"parenthetical": "副文本",
|
||||||
|
"lines": "台词内容"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "voiceover",
|
||||||
|
"character": "角色名或旁白",
|
||||||
|
"text": "画外音内容"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
【场景描述撰写规则】
|
||||||
|
|
||||||
|
description字段应该简洁但信息丰富,包含:
|
||||||
|
1. 环境类型(室内/室外/特殊环境)
|
||||||
|
2. 主要布局(如"狭长的走廊""开阔的广场")
|
||||||
|
3. 关键道具或环境元素(如"右侧落地窗""远处山脉")
|
||||||
|
4. 氛围提示(如"荒凉""温馨""阴森")
|
||||||
|
|
||||||
|
示例:
|
||||||
|
- "简约客厅。米白色墙面,木质地板。右侧落地窗,左侧入口门。"
|
||||||
|
- "荒凉的取经路。黄沙漫天,远处是连绵的山脉。"
|
||||||
|
- "阴暗的洞穴。石壁潮湿,只有微弱的火光。"
|
||||||
|
|
||||||
|
【特殊情况处理】
|
||||||
|
|
||||||
|
1. **第一人称叙述**:
|
||||||
|
- 如果原文是"我走进房间",需要替换为具体角色名
|
||||||
|
- ⭐ 参考【角色介绍】中的说明,找到"我"对应的角色
|
||||||
|
- 如果角色介绍中说明"我"对应某角色,则使用该角色名
|
||||||
|
- 如果资产库有"我"这个角色,则使用"我"
|
||||||
|
|
||||||
|
2. **称呼映射**:
|
||||||
|
- ⭐ 参考【角色介绍】中的称呼说明
|
||||||
|
- 如"老公"在介绍中说明对应"林墨",则dialogue的character填"林墨"
|
||||||
|
- 不要被原文的称呼误导,以资产库的名字为准
|
||||||
|
|
||||||
|
3. **回忆/闪回场景**:
|
||||||
|
- 回忆中的对话/动作 → 正常处理(因为画面中会演)
|
||||||
|
- 回忆的旁白叙述 → 使用voiceover类型
|
||||||
|
|
||||||
|
4. **多个小场景**:
|
||||||
|
- 如果原文包含多个地点变化,拆分成多个scene
|
||||||
|
- 每个scene有独立的scene_number
|
||||||
|
|
||||||
|
5. **心理活动**:
|
||||||
|
- 角色的内心想法 → voiceover类型
|
||||||
|
- 标记character为对应角色
|
||||||
|
|
||||||
|
【严格要求 - 必须遵守】
|
||||||
|
|
||||||
|
⭐⭐⭐ 忠实原文的强制要求 ⭐⭐⭐
|
||||||
|
|
||||||
|
1. 🚨【最重要】禁止编造!所有动作、对话、描述必须来自原文,不能添加任何原文中没有的内容
|
||||||
|
2. 🚨 如果原文只有一句话,剧本也只能有一句话对应的内容,禁止"扩写"
|
||||||
|
3. 🚨 如果原文没有描述角色的表情/动作,禁止添加"XX露出微笑"、"XX点了点头"等内容
|
||||||
|
4. 🚨 如果原文没有环境描写,description字段只写能从原文推断的最基本信息
|
||||||
|
5. 🚨 禁止添加过渡性动作,如"XX走了过来"、"XX转身离开"(除非原文明确写了)
|
||||||
|
|
||||||
|
格式要求:
|
||||||
|
|
||||||
|
1. 只返回JSON对象,不得有markdown标记或注释
|
||||||
|
2. location优先从场景资产库选择;如果资产库没有匹配的,使用原文场景名称(宁可缺失也不用错误的)
|
||||||
|
3. characters优先从角色资产库选择;如果资产库没有匹配的,使用原文角色名称(宁可缺失也不用错误的)
|
||||||
|
4. ⭐ 根据角色介绍中的称呼映射,将原文中的"我"、"老公"等替换为正确的角色名
|
||||||
|
5. ❌ 严禁添加原文中没有的内容(这是最常见的错误!)
|
||||||
|
6. content数组保持时间顺序
|
||||||
|
7. type只能是: action, dialogue, voiceover
|
||||||
|
8. dialogue必须包含character和lines
|
||||||
|
9. voiceover如果是特定角色必须包含character
|
||||||
|
10. parenthetical是可选的,只在原文有明确表演指导时添加
|
||||||
|
11. 输出必须是**严格合法的JSON**:字符串中不能出现原始换行/回车/制表符,必须使用转义字符(\\n、\\r、\\t)
|
||||||
|
12. 字符串内的双引号必须转义为 \"
|
||||||
|
13. clip_id 必须与输入的 Clip ID 完全一致,严禁输出 "{clip_id}" 这种占位符
|
||||||
|
14. 建议输出为单行JSON对象(不包含多余空行/解释)
|
||||||
|
|
||||||
|
⚠️ 自检清单(输出前必须确认):
|
||||||
|
- [ ] 我的每一句动作描述都能在原文中找到对应吗?
|
||||||
|
- [ ] 我的每一句对话都是原文的原话吗?
|
||||||
|
- [ ] 我有没有添加原文没有的"走过来"、"点头"、"微笑"等动作?
|
||||||
|
- [ ] 我有没有"丰富"原文简短的描写?
|
||||||
|
|
||||||
|
【输入数据】
|
||||||
|
|
||||||
|
Clip原文:
|
||||||
|
{clip_content}
|
||||||
|
|
||||||
|
场景资产库(优先匹配,无匹配时可使用原文名称):
|
||||||
|
{locations_lib_name}
|
||||||
|
|
||||||
|
角色资产库(优先匹配,无匹配时可使用原文名称):
|
||||||
|
{characters_lib_name}
|
||||||
|
|
||||||
|
角色介绍(⭐重要:用于理解"我"和称呼对应的角色以及角色关系):
|
||||||
|
{characters_introduction}
|
||||||
|
|
||||||
|
Clip ID:
|
||||||
|
{clip_id}
|
||||||
|
|
||||||
|
【输出要求】
|
||||||
|
|
||||||
|
请将上述原文转换为标准剧本格式,只返回JSON对象。
|
||||||
|
再次强调:输出必须是**严格合法的JSON**,不得包含任何额外文本。
|
||||||
43
lib/prompts/novel-promotion/select_location.en.txt
Normal file
43
lib/prompts/novel-promotion/select_location.en.txt
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
You are a location asset extraction specialist.
|
||||||
|
Extract locations that need dedicated background assets.
|
||||||
|
|
||||||
|
Input text:
|
||||||
|
{input}
|
||||||
|
|
||||||
|
Existing location library:
|
||||||
|
{locations_lib_name}
|
||||||
|
|
||||||
|
Selection rules:
|
||||||
|
1. Include locations where meaningful story actions happen.
|
||||||
|
2. Exclude abstract/metaphorical spaces and one-off passing mentions.
|
||||||
|
3. Deduplicate aliases of the same location.
|
||||||
|
4. Prefer exact library names when a location already exists.
|
||||||
|
|
||||||
|
For each selected location, generate 3 wide-angle environment descriptions.
|
||||||
|
Each description should:
|
||||||
|
- start with location name in brackets: "[Location Name] ..."
|
||||||
|
- describe spatial layout, depth layers, major objects, and lighting direction
|
||||||
|
- remain environment-only (no named protagonist actions)
|
||||||
|
- use concise, production-ready English
|
||||||
|
|
||||||
|
Output format (JSON only):
|
||||||
|
{
|
||||||
|
"locations": [
|
||||||
|
{
|
||||||
|
"name": "location_name",
|
||||||
|
"summary": "short usage summary",
|
||||||
|
"has_crowd": false,
|
||||||
|
"crowd_description": "",
|
||||||
|
"descriptions": [
|
||||||
|
"[location_name] description 1",
|
||||||
|
"[location_name] description 2",
|
||||||
|
"[location_name] description 3"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
Strict constraints:
|
||||||
|
1. JSON only.
|
||||||
|
2. If no valid location exists, return: {"locations":[]}.
|
||||||
|
|
||||||
135
lib/prompts/novel-promotion/select_location.zh.txt
Normal file
135
lib/prompts/novel-promotion/select_location.zh.txt
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
你是"场景资产建立师"。请基于我提供的文本(可能是小说、剧本、或混合格式),筛选【需要制作画面的场景】,生成用于出图与后续生产的资产 JSON。
|
||||||
|
|
||||||
|
【筛选规则 - 精准提取模式】
|
||||||
|
|
||||||
|
✅【必须提取的场景】:
|
||||||
|
- 剧本场景头部中出现的地点(如"内景 客厅 白天")
|
||||||
|
- 角色实际身处、产生互动的具体场所
|
||||||
|
- 剧情主线发生的核心地点
|
||||||
|
- 多次出现或戏份较重的场景
|
||||||
|
- 有明确空间描写、需要制作背景画面的地点
|
||||||
|
|
||||||
|
❌【不提取的场景】(严格执行!):
|
||||||
|
- 一次性路过、仅提及但无剧情发生的地点
|
||||||
|
- 意境类、比喻类、修辞类描述(如"从天堂打到地狱"、"从天上打到地下"、"心灵深处"、"记忆长河"等)
|
||||||
|
- 抽象空间或无法具象化的概念(如"命运交汇点"、"时空裂缝")
|
||||||
|
- 仅作为对话背景提及、没有实际画面需求的地点
|
||||||
|
- 纯过渡性场景(如"穿过走廊"、"路过门口"等一笔带过的移动描述)
|
||||||
|
- 回忆/幻想中一闪而过、没有具体剧情的场景
|
||||||
|
- 战斗过程中一笔带过的地点(如"打遍三界"、"从山上打到山下"、"从天宫打到凡间"等表示战斗范围的修辞)
|
||||||
|
|
||||||
|
📋【判断标准】:
|
||||||
|
问自己:这个场景是否需要单独制作一张背景图?角色是否在此场景有实际戏份?
|
||||||
|
如果只是一句话带过的地点,则不提取。
|
||||||
|
如果是表示"打斗范围"的修辞(如从天堂到地狱),则不提取。
|
||||||
|
|
||||||
|
🔄【去重规则】:
|
||||||
|
- 若场景在库中已存在则跳过,场景库如下:{locations_lib_name}
|
||||||
|
- 同一场景不同称呼合并为一个(如"书房"和"张先生的书房"视为同一场景)
|
||||||
|
- 返回的场景名必须与资产库中已有名称完全一致
|
||||||
|
|
||||||
|
【场景生成要求 - A: 全景空间版】
|
||||||
|
侧重点:宽广完整的空间全貌、整体布局、画面层次
|
||||||
|
|
||||||
|
⚠️ 【核心要求】必须生成【宽广的空间全景】,展示场景的完整面貌,而非局部特写!
|
||||||
|
- 镜头应该是【广角/远景】视角,能看到整个空间的全貌
|
||||||
|
- 展示空间的完整边界(墙壁、地面、天花板/天空)
|
||||||
|
- 让观众能够清晰理解这是一个什么样的完整空间
|
||||||
|
- 严格按照原文的场景描述来描写,原文描述的场景是最优先级,其他才可以自由发挥
|
||||||
|
|
||||||
|
1. **开头必须明确写明场景名称**:
|
||||||
|
- 每条描述开头必须以「场景名」的形式标注空间属性
|
||||||
|
- 示例:「皇宫」殿内铺设着... / 「客厅」窗外阳光透过... / 「卧室」床边放着...
|
||||||
|
- 这样AI在生成图片时能明确理解这是什么类型的空间
|
||||||
|
|
||||||
|
2. 每个场景生成 3 条中文环境描述(用于AI图片生成),供用户选择
|
||||||
|
|
||||||
|
3. 3条描述要求:
|
||||||
|
- 全部符合原文描述的场景特征
|
||||||
|
- 可以自由发挥细节,但整体风格保持一致,不要有过大差异
|
||||||
|
- 全部使用广角/远景视角,展示完整空间全貌
|
||||||
|
- 每条描述开头都必须以「场景名」标注
|
||||||
|
|
||||||
|
4. 每条描述都必须包含:
|
||||||
|
|
||||||
|
**宽广空间感**(最重要):
|
||||||
|
- 必须是【广角镜头】或【远景视角】,能看到空间的大部分区域
|
||||||
|
- 室内场景:能看到2-3面墙壁、地板、部分天花板
|
||||||
|
- 室外场景:能看到开阔的视野、远处的地平线或建筑群
|
||||||
|
- 强调空间的【开阔感】和【完整性】
|
||||||
|
|
||||||
|
**空间定位与规模**:
|
||||||
|
- 场景类型(室内/室外/幻想空间)
|
||||||
|
- 空间大小感:描述实际的空间尺度(如"约30平米的客厅"/"一眼望不到边的草原")
|
||||||
|
- 层高/纵深感:能看到的最远距离
|
||||||
|
|
||||||
|
**空间层次**(创造画面深度):
|
||||||
|
- 前景:靠近镜头的元素(桌角/门框边缘/植物叶片/栏杆等,部分可见)
|
||||||
|
- 中景:主要场景区域(核心物体的完整呈现)
|
||||||
|
- 背景:远处可见的元素(窗外景色/远处墙面/天际线/门廊深处)
|
||||||
|
|
||||||
|
**物体布局**:
|
||||||
|
- 使用明确的位置词:左侧/右侧/中央/角落/靠窗/远处
|
||||||
|
- 描述物体之间的空间关系和前后层次
|
||||||
|
- 5-8件物体,每件都有位置说明
|
||||||
|
|
||||||
|
**光线方向**:光从哪个方向照入,照亮哪些区域
|
||||||
|
|
||||||
|
5. 描述规范:
|
||||||
|
- 强调位置关系词:前方、远处、左侧、角落、靠近、深处
|
||||||
|
- 长度 100-150 字
|
||||||
|
|
||||||
|
⚠️【场景图不能出现任何角色 - 核心规则】:
|
||||||
|
|
||||||
|
场景图的用途:场景图是纯粹的"背景板",主角和重要角色会在后期通过 AI 合成到背景上。
|
||||||
|
因此,场景描述中**绝对不能出现任何有名有姓的角色**。
|
||||||
|
|
||||||
|
❌ 错误示例(包含了角色):
|
||||||
|
- "两只猴王持棒对峙" → 错!猴王是角色,不能出现
|
||||||
|
- "张三站在门口迎接" → 错!张三是角色,不能出现
|
||||||
|
- "孙悟空和六耳猕猴在街上打斗" → 错!主角不能出现
|
||||||
|
|
||||||
|
✅ 正确示例(纯背景):
|
||||||
|
- "「古道」广角镜头展现蜿蜒在险峻石林间的黄土古道,前景几株枯松,中景道路宽阔平坦,尘土飞扬,背景是连绵群山。"
|
||||||
|
- "「宴会厅」大厅远处三两宾客交谈" → 可以!这是无名背景群众
|
||||||
|
- "「集市」街道上行人往来" → 可以!这是模糊的路人群众
|
||||||
|
|
||||||
|
📋 什么情况可以写人群?
|
||||||
|
- 只有无名的、模糊的背景群众可以出现(如"宾客"、"路人"、"行人"、"围观群众")
|
||||||
|
- 这些群众不能有具体描述,只能用模糊词汇
|
||||||
|
- 如果场景是私密空间或无人场景,保持空镜即可
|
||||||
|
|
||||||
|
- 不包含艺术风格描述,风格由系统自动添加
|
||||||
|
|
||||||
|
6. 场景命名规则:中文 "地点_时间/状态"
|
||||||
|
- 示例:"客厅_白天"/"空间站_夜间"/"仙宫_黄昏"/"森林_迷雾中"
|
||||||
|
|
||||||
|
7. 剧情中出现的关键元素必须在场景中体现(如椅子、桌子等)
|
||||||
|
|
||||||
|
8.如无特殊要求,使用用户输入的语言来进行场景生成,例如输入英文输出偏西方场景,中文则输出偏中国场景,但是原则要按照文字剧本里实际发生的地点为准,
|
||||||
|
|
||||||
|
【输出规范(只允许以下 JSON 结构;字段名中文;不得输出任何多余文字)】
|
||||||
|
{
|
||||||
|
"locations": [
|
||||||
|
{
|
||||||
|
"name": "场景_时间",
|
||||||
|
"summary": "场景简要说明(用途/人物关联,如:张三居住的主卧室、公司高层会议室等)",
|
||||||
|
"has_crowd": true/false,
|
||||||
|
"crowd_description": "人群类型描述(仅当has_crowd为true时填写,如:宴会宾客、集市人群、学生们等)",
|
||||||
|
"descriptions": [
|
||||||
|
"「场景名」场景环境描述1(如has_crowd为true则包含人群元素)",
|
||||||
|
"「场景名」场景环境描述2",
|
||||||
|
"「场景名」场景环境描述3"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
【严格性】
|
||||||
|
- 若无符合条件的场景,locations数组返回 []。
|
||||||
|
- 只返回上述 JSON;不得输出markdown代码块标记、如```json注释或解释;不得添加未定义字段。
|
||||||
|
- 每条描述必须遵守长度限制(100-150字);发现超长请自行截断。
|
||||||
|
- 禁止在字符串里出现未转义的直引号 "。如需表示英寸或引号优先用数值字段(推荐),若必须用直引号,必须转义为 \
|
||||||
|
|
||||||
|
【原文内容如下】
|
||||||
|
{input}
|
||||||
27
lib/prompts/novel-promotion/single_panel_image.en.txt
Normal file
27
lib/prompts/novel-promotion/single_panel_image.en.txt
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
You are a professional storyboard image artist.
|
||||||
|
Generate exactly one high-quality image for one panel.
|
||||||
|
|
||||||
|
Absolute constraints:
|
||||||
|
1. No text in the image.
|
||||||
|
2. No subtitles, labels, numbers, watermarks, or symbols.
|
||||||
|
3. Do not create collage or multi-frame output.
|
||||||
|
4. Output exactly one frame.
|
||||||
|
|
||||||
|
Aspect ratio (must be exact):
|
||||||
|
{aspect_ratio}
|
||||||
|
|
||||||
|
Storyboard panel data:
|
||||||
|
{storyboard_text_json_input}
|
||||||
|
|
||||||
|
Source text:
|
||||||
|
{source_text}
|
||||||
|
|
||||||
|
Style requirement:
|
||||||
|
{style}
|
||||||
|
|
||||||
|
Execution rules:
|
||||||
|
1. Respect panel composition, character placement, and action logic.
|
||||||
|
2. Use reference images for style/identity consistency only.
|
||||||
|
3. Repaint the background according to shot type and angle.
|
||||||
|
4. If storyboard conflicts with source text, keep narrative logic from source text.
|
||||||
|
5. Keep final visual style consistent with provided references.
|
||||||
65
lib/prompts/novel-promotion/single_panel_image.zh.txt
Normal file
65
lib/prompts/novel-promotion/single_panel_image.zh.txt
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
你是一位专业的分镜画师。请根据以下分镜数据生成单张高质量的镜头图片。
|
||||||
|
|
||||||
|
【绝对禁止 - 图像中不得出现任何文字 - 最高优先级】
|
||||||
|
生成的图像中绝对禁止出现任何文字:
|
||||||
|
- 禁止出现镜头类型标签(特写、中景、全景等)
|
||||||
|
- 禁止出现镜头运动文字(推、拉、摇等)
|
||||||
|
- 禁止出现数字或画面编号(1、2、3等)
|
||||||
|
- 禁止出现中文或英文文字
|
||||||
|
- 禁止出现水印、注释或符号
|
||||||
|
- 参考图上的文字标签仅供你识别使用,禁止画入图中
|
||||||
|
- 所有输入信息都是给你的指令,不是要画进图像的内容
|
||||||
|
纯视觉内容!纯视觉内容!纯视觉内容!
|
||||||
|
- 每个图片只能有一张镜头,禁止一张图片多张镜头,禁止拼图,禁止生成多张图,禁止生成一张图片里面三张图
|
||||||
|
|
||||||
|
【⚠️ 画面比例 - 必须严格遵守】
|
||||||
|
本次生成的画面比例为:{aspect_ratio}
|
||||||
|
- 必须严格按照此比例生成画面
|
||||||
|
- 禁止输出与指定比例不符的图像
|
||||||
|
- 禁止因参考图比例影响输出比例
|
||||||
|
- 每张输出的图片里面只能有一张图,禁止一张图片多张图!
|
||||||
|
|
||||||
|
【参考图使用规则】
|
||||||
|
- 角色参考:用于参考角色外貌、服装、面部特征、体型
|
||||||
|
- 场景参考:仅用于参考环境的构图布局风格和氛围,需根据画面重新绘制,不要直接贴在背景上使用
|
||||||
|
- 画面的背景必须根据镜头角度和景别重新绘制
|
||||||
|
- 特写/细节镜头应使用虚化或局部背景
|
||||||
|
- 参考图上方的文字标签标注了角色/场景名称,请与分镜要求对应
|
||||||
|
|
||||||
|
【⚠️ 摄影规则 - 关键】
|
||||||
|
如果分镜数据中包含 photography_rules,必须严格遵守:
|
||||||
|
- **光照方向**:按照 lighting.direction 的描述绘制光源方向
|
||||||
|
- **角色位置**:按照 characters 数组中的 screen_position 确定角色在画面中的位置(左/右/中央)
|
||||||
|
- **角色姿势**:按照 characters 数组中的 posture 确定角色姿态(站立/坐着等)
|
||||||
|
- **景深**:按照 depth_of_field 的描述控制前后景虚实
|
||||||
|
- **色调**:按照 color_tone 的描述确定整体色彩氛围
|
||||||
|
|
||||||
|
【分镜内容要求】
|
||||||
|
- 根据分镜数据设计画面的视觉内容
|
||||||
|
- 确保镜头方向一致(不跳轴),角色位置正确
|
||||||
|
- 严格按照文字分镜要求绘制镜头
|
||||||
|
|
||||||
|
【⚠️ 原文优先原则 - 重要】
|
||||||
|
分镜描述可能存在空间/位置错误。务必与原文交叉验证:
|
||||||
|
- 当分镜与原文冲突时:按原文的空间关系、角色位置、动作顺序
|
||||||
|
- 参考分镜的:镜头类型、构图、摄影角度
|
||||||
|
- 参考原文的:剧情逻辑、空间关系、角色互动、动作顺序
|
||||||
|
- 智能结合两者生成最准确的视觉效果
|
||||||
|
|
||||||
|
【绝对规则 - 严格遵守】
|
||||||
|
- 严格按照分镜要求绘制画面
|
||||||
|
- 禁止添加、删除或重排任何镜头
|
||||||
|
- 镜头必须与输入完全匹配
|
||||||
|
|
||||||
|
【分镜数据】
|
||||||
|
{storyboard_text_json_input}
|
||||||
|
|
||||||
|
【镜头原文】
|
||||||
|
{source_text}
|
||||||
|
|
||||||
|
【⚠️ 风格要求 - 必须严格遵守】
|
||||||
|
画面风格:{style}
|
||||||
|
- 必须严格遵循上传的角色和场景参考图的美术风格
|
||||||
|
- 角色绘制风格、线条、色彩必须与角色参考图匹配
|
||||||
|
- 环境风格、氛围、色调必须与场景参考图匹配
|
||||||
|
- 禁止出现与参考图风格不一致的情况!
|
||||||
12
lib/prompts/novel-promotion/storyboard_edit.en.txt
Normal file
12
lib/prompts/novel-promotion/storyboard_edit.en.txt
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
You are an expert storyboard image editor.
|
||||||
|
Edit a single panel image or a panel set according to user instruction.
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
1. Do not add any text overlay, subtitle, or technical labels.
|
||||||
|
2. If user uploaded reference images, use them as primary visual guidance.
|
||||||
|
3. Keep identity and scene continuity unless user requests a change.
|
||||||
|
|
||||||
|
User instruction:
|
||||||
|
{user_input}
|
||||||
|
|
||||||
|
Return only the edited image result.
|
||||||
12
lib/prompts/novel-promotion/storyboard_edit.zh.txt
Normal file
12
lib/prompts/novel-promotion/storyboard_edit.zh.txt
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
你是一个修改编辑图片的大师,你需要根据用户的指令来编辑单个镜头或整组分镜图片,编辑时需要遵守以下规则:
|
||||||
|
1:不要添加任何多余标识符文字,如字幕,景别信息等
|
||||||
|
2:如果用户上传的有图片,那么就按照图片来进行参考修改
|
||||||
|
|
||||||
|
用户的编辑指令如下:{user_input}
|
||||||
|
|
||||||
|
请根据指令和原图,输出修改后的图片
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
37
lib/prompts/novel-promotion/voice_analysis.en.txt
Normal file
37
lib/prompts/novel-promotion/voice_analysis.en.txt
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
You are a dialogue voice-line analyzer.
|
||||||
|
Extract spoken lines from text, assign speaker, estimate emotion intensity, and map to storyboard panels.
|
||||||
|
|
||||||
|
Output format (JSON array only):
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"lineIndex": 1,
|
||||||
|
"speaker": "Speaker name",
|
||||||
|
"content": "Dialogue line",
|
||||||
|
"emotionStrength": 0.3,
|
||||||
|
"matchedPanel": {
|
||||||
|
"storyboardId": "storyboard_id",
|
||||||
|
"panelIndex": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
Input text:
|
||||||
|
{input}
|
||||||
|
|
||||||
|
Character library:
|
||||||
|
{characters_lib_name}
|
||||||
|
|
||||||
|
Character introductions:
|
||||||
|
{characters_introduction}
|
||||||
|
|
||||||
|
Storyboard JSON:
|
||||||
|
{storyboard_json}
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
1. Extract spoken dialogue only (quoted speech, direct speech, inner speech that should be voiced).
|
||||||
|
2. Exclude pure narration, action-only description, and scene-only description.
|
||||||
|
3. emotionStrength must be between 0.1 and 0.5.
|
||||||
|
4. Match panel by order + speaker consistency + semantic relevance.
|
||||||
|
5. If no reliable panel match exists, set "matchedPanel": null.
|
||||||
|
6. Use canonical names from character library when possible.
|
||||||
|
7. Return strict JSON only, no markdown.
|
||||||
115
lib/prompts/novel-promotion/voice_analysis.zh.txt
Normal file
115
lib/prompts/novel-promotion/voice_analysis.zh.txt
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
你是"台词发言人分析大师"。
|
||||||
|
任务:从文本中提取需要配音的**对话台词**,分析情绪强度,并匹配对应的视频镜头。
|
||||||
|
|
||||||
|
输出格式(只返回JSON,禁止markdown标记):
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"lineIndex": 1,
|
||||||
|
"speaker": "发言人名称",
|
||||||
|
"content": "台词内容",
|
||||||
|
"emotionStrength": 0.5,
|
||||||
|
"matchedPanel": {
|
||||||
|
"storyboardId": "分镜组ID",
|
||||||
|
"panelIndex": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
分析规则:
|
||||||
|
|
||||||
|
1. 【台词提取 - 最重要】
|
||||||
|
✅ 只提取以下类型的内容:
|
||||||
|
- **带引号的对话**:"xxx" 或 "xxx" 或 「xxx」
|
||||||
|
- **直接引语**:他说:"xxx"、她喊道:"xxx"
|
||||||
|
- **内心独白**:我心想:"xxx"
|
||||||
|
|
||||||
|
❌ 严格排除以下内容:
|
||||||
|
- 叙述性文字(无引号的描述)
|
||||||
|
- 动作描写(描述角色的动作)
|
||||||
|
- 场景描述(描述环境、画面)
|
||||||
|
- 章节标题
|
||||||
|
|
||||||
|
⚠️ 判断标准:这句话是否需要有人"说出来"?如果只是描述画面动作,不要提取。
|
||||||
|
|
||||||
|
2. 【情绪强度 emotionStrength】
|
||||||
|
根据台词的情绪激烈程度,输出0.1-0.5之间的数值(⚠️ 注意:最高不超过0.5,保持语音自然平稳):
|
||||||
|
|
||||||
|
| 情绪类型 | 强度范围 | 示例 |
|
||||||
|
|---------|---------|------|
|
||||||
|
| 平静/陈述 | 0.1-0.15 | "好的,我知道了" |
|
||||||
|
| 普通对话 | 0.15-0.2 | "你今天怎么来了?" |
|
||||||
|
| 疑惑/好奇 | 0.2-0.25 | "这是怎么回事?" |
|
||||||
|
| 惊讶/意外 | 0.25-0.3 | "什么?!你说真的?" |
|
||||||
|
| 生气/愤怒 | 0.3-0.35 | "你给我滚出去!" |
|
||||||
|
| 悲伤/哭泣 | 0.25-0.35 | "为什么要这样对我..." |
|
||||||
|
| 狂喜/激动 | 0.35-0.4 | "太好了!我们成功了!" |
|
||||||
|
| 咆哮/嘶吼 | 0.4-0.5 | "我要杀了你!!!" |
|
||||||
|
|
||||||
|
3. 【发言人识别】
|
||||||
|
- 对话内容:识别说话者,如"他说"、"她喊道"
|
||||||
|
- 无引导词的引号内容:根据上下文推断发言人
|
||||||
|
- 如果无法确定发言人,设为"旁白"
|
||||||
|
|
||||||
|
4. 【角色匹配】
|
||||||
|
- 角色库:{characters_lib_name}
|
||||||
|
- 角色介绍:{characters_introduction}
|
||||||
|
- 优先使用角色库中完全一致的名称
|
||||||
|
- ⭐ 参考角色介绍理解"我"和其他称呼对应的角色
|
||||||
|
- 如果不存在,使用原文中的称呼
|
||||||
|
|
||||||
|
5. 【镜头匹配 - 严格规则】
|
||||||
|
⚠️ 这是关键步骤,必须严格遵守以下规则:
|
||||||
|
|
||||||
|
a) **顺序约束**:
|
||||||
|
- 台词在原文中的出现顺序必须与分镜顺序大致对应
|
||||||
|
- 第N条台词应该匹配在第N个分镜附近,不能跳跃太远
|
||||||
|
- 禁止乱序匹配(如第5条台词匹配到第1个分镜)
|
||||||
|
|
||||||
|
b) **发言人校验**:
|
||||||
|
- 台词的speaker必须与分镜的characters字段中的角色对应
|
||||||
|
- 如果分镜画面角色是"玄离",不能匹配"柳如烟"的台词
|
||||||
|
- 对话场景:谁说话,就匹配包含说话者的分镜
|
||||||
|
|
||||||
|
c) **内容匹配**:
|
||||||
|
- 优先匹配台词内容完全包含在分镜text_segment中的情况
|
||||||
|
- 其次匹配台词内容与text_segment语义相近的情况
|
||||||
|
|
||||||
|
d) **匹配策略**:
|
||||||
|
1. 首先根据text_segment精确匹配台词内容
|
||||||
|
2. 验证分镜角色是否包含台词发言人
|
||||||
|
3. 验证顺序是否合理(前后3个分镜范围内)
|
||||||
|
4. 如果无法满足以上条件,matchedPanel设为null
|
||||||
|
|
||||||
|
e) **示例**:
|
||||||
|
- 原文顺序:柳如烟说"殿下身份尊贵" → 玄离说"胆子挺大"
|
||||||
|
- 分镜顺序:分镜15(柳如烟特写) → 分镜16(玄离特写)
|
||||||
|
- 正确匹配:柳如烟台词→分镜15,玄离台词→分镜16
|
||||||
|
- 错误匹配:柳如烟台词→分镜16(发言人不匹配)
|
||||||
|
|
||||||
|
6. 【多音字处理 - 重要】
|
||||||
|
为确保TTS语音合成发音正确,对于容易被误读的多音字,需要替换为**读音完全相同(包括声调)的单音字**。
|
||||||
|
|
||||||
|
处理原则:
|
||||||
|
a) **识别多音字**:找出台词中的多音字(如:还、行、了、乐、朝、重、都等)
|
||||||
|
b) **判断正确读音**:根据上下文语义判断该字在此处的正确读音
|
||||||
|
c) **选择替换字**:找一个读音完全相同(声母、韵母、声调都一致)的常用单音字替换
|
||||||
|
d) **验证替换**:确保替换后的字读音与原意读音完全一致
|
||||||
|
|
||||||
|
替换示例思路:
|
||||||
|
- "还(huán)给我" → 用"环"替换,因为"环"只读huán
|
||||||
|
- "银行(háng)" → 用"航"替换,因为"航"只读háng
|
||||||
|
- "了(liǎo)解" → 用"聊"替换,因为"聊"只读liáo(接近liǎo)
|
||||||
|
- "快乐(lè)" → 用"乐"保持原字,因为TTS通常能正确读常见词
|
||||||
|
- "重(zhòng)量" → 用"众"替换,因为"众"只读zhòng
|
||||||
|
|
||||||
|
⚠️ 注意事项:
|
||||||
|
- 必须确保替换字的读音与目标读音完全一致,不要用读音相近但不同的字
|
||||||
|
- 例如:"勒"读lè或lēi,不能用来替换读le的字
|
||||||
|
- 常见词组(如"了解"、"快乐"、"音乐")TTS通常能正确读,可以保持原字
|
||||||
|
- 只有当多音字在特定语境下容易被TTS误读时才需要替换
|
||||||
|
|
||||||
|
分镜数据如下:
|
||||||
|
{storyboard_json}
|
||||||
|
|
||||||
|
原文如下:
|
||||||
|
{input}
|
||||||
18
messages/en/actions.json
Normal file
18
messages/en/actions.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"storyboard": "Storyboard",
|
||||||
|
"storyboard_candidate": "Storyboard Candidate",
|
||||||
|
"character": "Character",
|
||||||
|
"location": "Location",
|
||||||
|
"video": "Video",
|
||||||
|
"analyze": "Analyze",
|
||||||
|
"analyze_character": "Character Analysis",
|
||||||
|
"analyze_location": "Location Analysis",
|
||||||
|
"clips": "Clip Splitting",
|
||||||
|
"storyboard_text_plan": "Storyboard Planning",
|
||||||
|
"storyboard_text_detail": "Storyboard Details",
|
||||||
|
"tts": "Text-to-Speech",
|
||||||
|
"regenerate": "Regenerate",
|
||||||
|
"voice-generate": "Voice Generation",
|
||||||
|
"voice-design": "Voice Design",
|
||||||
|
"lip-sync": "Lip Sync"
|
||||||
|
}
|
||||||
107
messages/en/apiConfig.json
Normal file
107
messages/en/apiConfig.json
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
{
|
||||||
|
"title": "API Configuration",
|
||||||
|
"saving": "Saving...",
|
||||||
|
"saved": "Saved",
|
||||||
|
"saveFailed": "Save failed",
|
||||||
|
"connected": "Connected",
|
||||||
|
"notConfigured": "Not configured",
|
||||||
|
"configure": "Configure",
|
||||||
|
"connect": "Connect",
|
||||||
|
"show": "Show",
|
||||||
|
"hide": "Hide",
|
||||||
|
"capability": "Models",
|
||||||
|
"default": "Default",
|
||||||
|
"delete": "Delete",
|
||||||
|
"add": "Add",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"save": "Save",
|
||||||
|
"comingSoon": "Coming soon",
|
||||||
|
"priceInput": "Input {amount}",
|
||||||
|
"priceOutput": "Output {amount}",
|
||||||
|
"priceUnavailable": "N/A",
|
||||||
|
"fillComplete": "Please fill in all fields",
|
||||||
|
"fillPricing": "Please fill in pricing information",
|
||||||
|
"pricingInputLabel": "Input price",
|
||||||
|
"pricingOutputLabel": "Output price",
|
||||||
|
"modelIdExists": "Model ID already exists",
|
||||||
|
"modelDisplayName": "Display Name (for your reference)",
|
||||||
|
"modelActualId": "Actual Model ID (API parameter)",
|
||||||
|
"noModelsForProvider": "No models configured for this provider",
|
||||||
|
"defaultModels": "Default Model Configuration",
|
||||||
|
"textDefault": "Text Model",
|
||||||
|
"characterDefault": "Character Model",
|
||||||
|
"locationDefault": "Location Model",
|
||||||
|
"storyboardDefault": "Storyboard Model",
|
||||||
|
"editDefault": "Edit Model",
|
||||||
|
"videoDefault": "Video Model",
|
||||||
|
"lipsyncDefault": "Lip Sync Model",
|
||||||
|
"selectDefault": "Select",
|
||||||
|
"providerPool": "Provider Pool",
|
||||||
|
"providerIdExists": "Provider ID already exists",
|
||||||
|
"presetProviderCannotDelete": "Preset providers cannot be deleted",
|
||||||
|
"confirmDeleteProvider": "Are you sure you want to delete this provider?",
|
||||||
|
"presetModelCannotDelete": "Preset models cannot be deleted",
|
||||||
|
"confirmDeleteModel": "Are you sure you want to delete this model?",
|
||||||
|
"addGeminiProvider": "Add Model Provider",
|
||||||
|
"baseUrl": "Base URL",
|
||||||
|
"configureBaseUrl": "Configure URL",
|
||||||
|
"addModel": "Add Model",
|
||||||
|
"batchModeHalfPrice": "Batch mode (50% price)",
|
||||||
|
"typeText": "Text",
|
||||||
|
"typeImage": "Image",
|
||||||
|
"typeVideo": "Video",
|
||||||
|
"typeAudio": "Audio",
|
||||||
|
"apiKeyLabel": "API Key",
|
||||||
|
"apiType": "API Type",
|
||||||
|
"apiTypeGeminiCompatible": "Gemini Compatible",
|
||||||
|
"apiTypeOpenAICompatible": "OpenAI Compatible",
|
||||||
|
"apiTypeGeminiHint": "Uses Google SDK",
|
||||||
|
"otherProviders": "Other Settings",
|
||||||
|
"audioCategory": "Audio",
|
||||||
|
"audioAndLipsync": "Audio & Lip Sync",
|
||||||
|
"configureApiKey": "Configure API Key",
|
||||||
|
"enterApiKey": "Enter API Key",
|
||||||
|
"tabs": {
|
||||||
|
"llm": "Text Models",
|
||||||
|
"image": "Image Models",
|
||||||
|
"video": "Video Models",
|
||||||
|
"audio": "Audio Models",
|
||||||
|
"other": "Other"
|
||||||
|
},
|
||||||
|
"sections": {
|
||||||
|
"llmApiKeys": "Text Model API Keys",
|
||||||
|
"imageApiKeys": "Image Model API Keys",
|
||||||
|
"videoApiKeys": "Video Model API Keys",
|
||||||
|
"audioApiKey": "Audio Model API Key",
|
||||||
|
"lipsyncApiKey": "Lip Sync API Key"
|
||||||
|
},
|
||||||
|
"defaultModel": {
|
||||||
|
"title": "Default Model",
|
||||||
|
"hint": "New projects and Asset Hub will use this default configuration",
|
||||||
|
"notSelected": "Not selected",
|
||||||
|
"analysis": "Analysis Model",
|
||||||
|
"image": "Image Generation",
|
||||||
|
"video": "Video Generation",
|
||||||
|
"resolution": "Image Resolution"
|
||||||
|
},
|
||||||
|
"viewTutorial": "View Tutorial",
|
||||||
|
"tutorial": {
|
||||||
|
"button": "Tutorial",
|
||||||
|
"title": "Setup Guide",
|
||||||
|
"subtitle": "Follow these steps to complete the configuration",
|
||||||
|
"close": "Got it",
|
||||||
|
"openLink": "Open link",
|
||||||
|
"steps": {
|
||||||
|
"ark_step1": "Go to the Volcano Engine console to create an API Key",
|
||||||
|
"ark_step2": "On the model management page, click 'Enable All Models' button in the top right corner",
|
||||||
|
"openrouter_step1": "Go to OpenRouter platform and create an API Key (must select models with image capabilities)",
|
||||||
|
"fal_step1": "Go to FAL platform and create an API Key",
|
||||||
|
"google_step1": "Go to Google AI Studio and create an API Key",
|
||||||
|
"minimax_step1": "Go to MiniMax platform and get an API Key",
|
||||||
|
"vidu_step1": "Go to the Vidu platform and click 'Create API Key'",
|
||||||
|
"openai_compatible_step1": "Enter any OpenAI-compatible service Base URL and API key",
|
||||||
|
"gemini_compatible_step1": "Enter any Gemini-compatible service Base URL and API key",
|
||||||
|
"qwen_step1": "Go to Alibaba Cloud Bailian platform and get an API Key"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
9
messages/en/apiTypes.json
Normal file
9
messages/en/apiTypes.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"image": "Image Generation",
|
||||||
|
"video": "Video Generation",
|
||||||
|
"text": "Text Analysis",
|
||||||
|
"tts": "Text-to-Speech",
|
||||||
|
"voice": "Voice Dubbing",
|
||||||
|
"voice_design": "Voice Design",
|
||||||
|
"lip_sync": "Lip Sync"
|
||||||
|
}
|
||||||
101
messages/en/assetHub.json
Normal file
101
messages/en/assetHub.json
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
{
|
||||||
|
"title": "Asset Hub",
|
||||||
|
"description": "Manage your global character and location assets",
|
||||||
|
"modelHint": "Asset Hub uses default models. To change settings, go to",
|
||||||
|
"modelHintLink": "API Settings",
|
||||||
|
"modelHintSuffix": "",
|
||||||
|
"folders": "Folders",
|
||||||
|
"noFolders": "No folders yet",
|
||||||
|
"allAssets": "All Assets",
|
||||||
|
"characters": "Characters",
|
||||||
|
"locations": "Locations",
|
||||||
|
"voices": "Voices",
|
||||||
|
"addCharacter": "Add Character",
|
||||||
|
"addLocation": "Add Location",
|
||||||
|
"addVoice": "Add Voice",
|
||||||
|
"newFolder": "New Folder",
|
||||||
|
"editFolder": "Edit Folder",
|
||||||
|
"deleteFolder": "Delete Folder",
|
||||||
|
"folderName": "Folder Name",
|
||||||
|
"folderNamePlaceholder": "Enter folder name",
|
||||||
|
"emptyState": "No assets yet",
|
||||||
|
"emptyStateHint": "Click the buttons above to add characters or locations",
|
||||||
|
"generate": "Generate",
|
||||||
|
"generating": "Generating...",
|
||||||
|
"regenerate": "Regenerate",
|
||||||
|
"undo": "Undo",
|
||||||
|
"delete": "Delete",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"save": "Save",
|
||||||
|
"create": "Create",
|
||||||
|
"confirmDeleteFolder": "Delete this folder? Assets inside will be moved to uncategorized.",
|
||||||
|
"confirmDeleteCharacter": "Delete this character? This action cannot be undone.",
|
||||||
|
"confirmDeleteLocation": "Delete this location? This action cannot be undone.",
|
||||||
|
"confirmDeleteVoice": "Delete this voice? This action cannot be undone.",
|
||||||
|
"voiceName": "Voice Name",
|
||||||
|
"voiceNamePlaceholder": "Enter voice name",
|
||||||
|
"voiceNameRequired": "Please enter a voice name",
|
||||||
|
"voicePickerTitle": "Select from Voice Library",
|
||||||
|
"voicePickerEmpty": "No voices yet. Please create a voice first.",
|
||||||
|
"voicePickerConfirm": "Confirm Selection",
|
||||||
|
"pagination": {
|
||||||
|
"previous": "Previous",
|
||||||
|
"next": "Next"
|
||||||
|
},
|
||||||
|
"common": {
|
||||||
|
"cancel": "Cancel"
|
||||||
|
},
|
||||||
|
"generateFailed": "Generation failed",
|
||||||
|
"selectFailed": "Selection failed",
|
||||||
|
"uploadFailed": "Upload failed",
|
||||||
|
"editFailed": "Edit failed",
|
||||||
|
"saveVoiceFailed": "Failed to save voice",
|
||||||
|
"saveVoiceFailedDetail": "Failed to save voice: {error}",
|
||||||
|
"bindVoiceFailed": "Failed to bind voice",
|
||||||
|
"bindVoiceFailedDetail": "Failed to bind voice: {error}",
|
||||||
|
"voiceDesignSaved": "AI-designed voice has been set for {name}",
|
||||||
|
"appearanceLabel": "Appearance {index}",
|
||||||
|
"voiceSettings": {
|
||||||
|
"title": "Voice",
|
||||||
|
"noVoice": "No voice",
|
||||||
|
"previewFailed": "Preview failed: {error}",
|
||||||
|
"uploadFailed": "Upload audio failed: {error}",
|
||||||
|
"uploading": "Uploading...",
|
||||||
|
"uploaded": "Uploaded",
|
||||||
|
"uploadAudio": "Upload Audio",
|
||||||
|
"aiDesign": "AI Design",
|
||||||
|
"voiceLibrary": "Voice Library",
|
||||||
|
"pause": "Pause",
|
||||||
|
"preview": "Preview Voice"
|
||||||
|
},
|
||||||
|
"modal": {
|
||||||
|
"newCharacter": "New Character",
|
||||||
|
"confirm": "Confirm",
|
||||||
|
"processing": "Processing...",
|
||||||
|
"newLocation": "New Location",
|
||||||
|
"addCharacter": "Create Character",
|
||||||
|
"addLocation": "Create Location",
|
||||||
|
"adding": "Creating...",
|
||||||
|
"aiDesign": "AI Design",
|
||||||
|
"aiDesignPlaceholder": "e.g., A beautiful woman in a red traditional dress with flowing long hair",
|
||||||
|
"aiDesignLocationPlaceholder": "e.g., A classical Chinese garden with rockery and pavilions",
|
||||||
|
"aiDesignTip": "AI will generate a detailed description based on your input. You can edit it after generation.",
|
||||||
|
"aiDesignLocationTip": "AI will generate a detailed scene description based on your input",
|
||||||
|
"generate": "Generate",
|
||||||
|
"generating": "Generating...",
|
||||||
|
"nameLabel": "Character Name",
|
||||||
|
"namePlaceholder": "Enter character name",
|
||||||
|
"descLabel": "Character Description",
|
||||||
|
"descPlaceholder": "Describe the character's appearance, clothing, hairstyle, etc...",
|
||||||
|
"locationNameLabel": "Location Name",
|
||||||
|
"locationNamePlaceholder": "Enter location name",
|
||||||
|
"locationSummaryLabel": "Location Description",
|
||||||
|
"locationSummaryPlaceholder": "Describe the environment, atmosphere, features, etc...",
|
||||||
|
"referenceUpload": "Upload Reference",
|
||||||
|
"referenceUploadTip": "Upload a character image, AI will convert it to a three-view design sheet",
|
||||||
|
"convertToCharacter": "Convert to 3-View",
|
||||||
|
"converting": "Converting...",
|
||||||
|
"dropOrClick": "Drop image or click to upload",
|
||||||
|
"supportedFormats": "JPG, PNG supported"
|
||||||
|
}
|
||||||
|
}
|
||||||
14
messages/en/assetLibrary.json
Normal file
14
messages/en/assetLibrary.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"title": "Asset Library",
|
||||||
|
"button": "Assets",
|
||||||
|
"characters": "Characters",
|
||||||
|
"locations": "Locations",
|
||||||
|
"noCharacters": "No characters",
|
||||||
|
"noLocations": "No locations",
|
||||||
|
"addCharacter": "Add Character",
|
||||||
|
"addLocation": "Add Location",
|
||||||
|
"generateImage": "Generate Image",
|
||||||
|
"regenerateImage": "Regenerate",
|
||||||
|
"analyzeAssets": "Analyze Assets",
|
||||||
|
"analyzing": "Analyzing..."
|
||||||
|
}
|
||||||
78
messages/en/assetModal.json
Normal file
78
messages/en/assetModal.json
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
{
|
||||||
|
"character": {
|
||||||
|
"title": "New Character",
|
||||||
|
"name": "Character Name",
|
||||||
|
"namePlaceholder": "Enter character name",
|
||||||
|
"modeReference": "Reference Image",
|
||||||
|
"modeDescription": "Description",
|
||||||
|
"isSubAppearance": "This is a sub-appearance",
|
||||||
|
"isSubAppearanceHint": "Add a new appearance state for an existing character",
|
||||||
|
"uploadReference": "Upload Reference",
|
||||||
|
"pasteHint": "Ctrl+V to paste",
|
||||||
|
"dropOrClick": "Click to upload or drag image",
|
||||||
|
"supportedFormats": "Supports JPG, PNG formats",
|
||||||
|
"nameRequired": "Please enter character name first to use reference conversion",
|
||||||
|
"convertToSheet": "Convert to standard character sheet",
|
||||||
|
"referenceTip": "Upload any character image, AI will generate a standard character sheet",
|
||||||
|
"description": "Character Description",
|
||||||
|
"modifyDescription": "Modify Description",
|
||||||
|
"descPlaceholder": "Enter character appearance description...",
|
||||||
|
"modifyDescriptionPlaceholder": "Describe how to modify the primary appearance, e.g. formal outfit, post-battle injuries, add a cloak...",
|
||||||
|
"selectMainCharacter": "Select Main Character",
|
||||||
|
"selectCharacterPlaceholder": "Please select a character...",
|
||||||
|
"appearancesCount": "{count} appearances",
|
||||||
|
"changeReason": "Appearance Change Reason",
|
||||||
|
"changeReasonPlaceholder": "e.g. injured after battle, changed into formal wear for a banquet...",
|
||||||
|
"defaultDescription": "{name}'s character profile",
|
||||||
|
"generationMode": "Generation Mode",
|
||||||
|
"directGenerate": "Direct Generate",
|
||||||
|
"extractPrompt": "Extract Prompt",
|
||||||
|
"extractFirst": "Extract Description First",
|
||||||
|
"directGenerateDesc": "Directly generate character sheet from reference (img2img)",
|
||||||
|
"extractPromptDesc": "Extract description from image first, then generate (txt2img)",
|
||||||
|
"maxReferenceImages": "Up to 5 reference images",
|
||||||
|
"selectedCount": "Selected {count}/5 images",
|
||||||
|
"extractDescription": "Extract Description",
|
||||||
|
"extracting": "Extracting...",
|
||||||
|
"extractedDescription": "Extracted Description (Editable)",
|
||||||
|
"reExtract": "Re-extract",
|
||||||
|
"editHint": "Edit the description, then click below to generate",
|
||||||
|
"generateFromDescription": "Generate from Description",
|
||||||
|
"textToImageTip": "Text-to-image mode: Generate from extracted description",
|
||||||
|
"pleaseExtractFirst": "Please extract character description first"
|
||||||
|
},
|
||||||
|
"location": {
|
||||||
|
"title": "New Location",
|
||||||
|
"name": "Location Name",
|
||||||
|
"namePlaceholder": "Enter location name",
|
||||||
|
"description": "Location Description",
|
||||||
|
"descPlaceholder": "Enter location description..."
|
||||||
|
},
|
||||||
|
"artStyle": {
|
||||||
|
"title": "Art Style"
|
||||||
|
},
|
||||||
|
"aiDesign": {
|
||||||
|
"title": "AI Design",
|
||||||
|
"placeholder": "Describe the character you want...",
|
||||||
|
"placeholderLocation": "Describe the scene atmosphere...",
|
||||||
|
"generating": "Designing...",
|
||||||
|
"generate": "Generate",
|
||||||
|
"tip": "Enter a simple description, AI will generate detailed settings"
|
||||||
|
},
|
||||||
|
"common": {
|
||||||
|
"creating": "Creating...",
|
||||||
|
"create": "Create",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"adding": "Adding...",
|
||||||
|
"add": "Add",
|
||||||
|
"optional": "(Optional)"
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"uploadFailed": "Upload failed",
|
||||||
|
"extractDescriptionFailed": "Failed to extract description",
|
||||||
|
"createFailed": "Creation failed",
|
||||||
|
"aiDesignFailed": "AI design failed",
|
||||||
|
"addSubAppearanceFailed": "Failed to add sub-appearance",
|
||||||
|
"insufficientBalance": "Insufficient balance"
|
||||||
|
}
|
||||||
|
}
|
||||||
18
messages/en/assetPicker.json
Normal file
18
messages/en/assetPicker.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"selectCharacter": "Select Character from Asset Hub",
|
||||||
|
"selectLocation": "Select Location from Asset Hub",
|
||||||
|
"selectVoice": "Select Voice from Asset Hub",
|
||||||
|
"searchPlaceholder": "Search by name or folder...",
|
||||||
|
"noAssets": "No assets in Asset Hub",
|
||||||
|
"createInAssetHub": "Please create characters/locations/voices in Asset Hub first",
|
||||||
|
"noSearchResults": "No matching assets found",
|
||||||
|
"appearances": "appearances",
|
||||||
|
"images": "images",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"confirmCopy": "Confirm Copy",
|
||||||
|
"copyFromGlobal": "Copy from Asset Hub",
|
||||||
|
"copySuccess": "Copy successful",
|
||||||
|
"copyFailed": "Copy failed",
|
||||||
|
"preview": "Preview",
|
||||||
|
"stop": "Stop"
|
||||||
|
}
|
||||||
321
messages/en/assets.json
Normal file
321
messages/en/assets.json
Normal file
@@ -0,0 +1,321 @@
|
|||||||
|
{
|
||||||
|
"stage": {
|
||||||
|
"title": "Assets Confirmation",
|
||||||
|
"characters": "Characters",
|
||||||
|
"locations": "Locations",
|
||||||
|
"analyze": "Analyze Assets",
|
||||||
|
"analyzing": "Analyzing...",
|
||||||
|
"generateAll": "Generate All",
|
||||||
|
"noCharacters": "No characters",
|
||||||
|
"noLocations": "No locations",
|
||||||
|
"confirmProfiles": "Character Profiles to Confirm",
|
||||||
|
"confirmHint": "Please confirm these profiles before generating descriptions",
|
||||||
|
"confirmAll": "Confirm All ({count})",
|
||||||
|
"assetsTitle": "Asset Analysis",
|
||||||
|
"characterAssets": "Character Assets",
|
||||||
|
"locationAssets": "Location Assets",
|
||||||
|
"counts": "{characterCount} Characters, {appearanceCount} Appearances",
|
||||||
|
"locationCounts": "{count} Locations",
|
||||||
|
"undoFailed": "Undo failed",
|
||||||
|
"undoFailedError": "Undo failed: {error}",
|
||||||
|
"undoSuccess": "Reverted to previous version",
|
||||||
|
"editFailed": "Edit failed",
|
||||||
|
"editFailedError": "Image edit failed: {error}",
|
||||||
|
"updateSuccess": "Description updated successfully"
|
||||||
|
},
|
||||||
|
"character": {
|
||||||
|
"add": "Add Character",
|
||||||
|
"edit": "Edit Character",
|
||||||
|
"delete": "Delete Character",
|
||||||
|
"deleteConfirm": "Delete this character?",
|
||||||
|
"deleteAppearanceConfirm": "Delete this appearance?",
|
||||||
|
"deleteFailed": "Delete failed: {error}",
|
||||||
|
"deleteWhole": "Delete Whole Character",
|
||||||
|
"deleteOptions": "Delete Options",
|
||||||
|
"name": "Character Name",
|
||||||
|
"description": "Appearance Description",
|
||||||
|
"generateImage": "Generate Profile",
|
||||||
|
"regenerateImage": "Regenerate",
|
||||||
|
"generate": "Generate",
|
||||||
|
"regenerating": "Generating...",
|
||||||
|
"profile": "Profile",
|
||||||
|
"voiceSettings": "Voice Settings",
|
||||||
|
"speaker": "Speaker",
|
||||||
|
"selectSpeaker": "Select Speaker",
|
||||||
|
"noSpeaker": "Not Set",
|
||||||
|
"primary": "Primary",
|
||||||
|
"secondary": "Secondary",
|
||||||
|
"generateFromPrimary": "Generate from Primary",
|
||||||
|
"selectPrimaryFirst": "Select primary first",
|
||||||
|
"editing": "Editing...",
|
||||||
|
"confirming": "Confirming...",
|
||||||
|
"assetCount": "{count} Appearances",
|
||||||
|
"characterCount": "{count} Characters",
|
||||||
|
"updateFailed": "Update description failed",
|
||||||
|
"addFailed": "Add character failed",
|
||||||
|
"copyFromGlobal": "Copy from Asset Hub"
|
||||||
|
},
|
||||||
|
"location": {
|
||||||
|
"add": "Add Location",
|
||||||
|
"edit": "Edit Location",
|
||||||
|
"delete": "Delete Location",
|
||||||
|
"deleteConfirm": "Delete this location?",
|
||||||
|
"deleteFailed": "Delete failed: {error}",
|
||||||
|
"name": "Location Name",
|
||||||
|
"summary": "Summary",
|
||||||
|
"summaryPlaceholder": "Usage/associations, e.g.: John's master bedroom",
|
||||||
|
"description": "Location Description",
|
||||||
|
"generateImage": "Generate Image",
|
||||||
|
"regenerateImage": "Regenerate",
|
||||||
|
"updateFailed": "Update description failed",
|
||||||
|
"addFailed": "Add location failed"
|
||||||
|
},
|
||||||
|
"image": {
|
||||||
|
"upload": "Upload Image",
|
||||||
|
"uploadReplace": "Upload Replacement",
|
||||||
|
"uploadFailed": "Upload Failed",
|
||||||
|
"uploadFailedError": "Upload failed: {error}",
|
||||||
|
"uploadSuccess": "Upload Success!",
|
||||||
|
"edit": "Edit Image",
|
||||||
|
"editPrompt": "Edit Prompt",
|
||||||
|
"undo": "Undo to Previous Version",
|
||||||
|
"undoConfirm": "Are you sure you want to undo to the previous version? Current version will be deleted.",
|
||||||
|
"regenerateGroup": "Regenerate Group",
|
||||||
|
"regenerateStuck": "Click to regenerate (if stuck)",
|
||||||
|
"selectTip": "Once selected and confirmed, you can edit and modify the image",
|
||||||
|
"selectFirst": "Please select an image first",
|
||||||
|
"useThis": "Use this option",
|
||||||
|
"optionAlt": "{name} - Option {number}",
|
||||||
|
"optionNumber": "Option {number}",
|
||||||
|
"optionSelected": "Selected Option {number}",
|
||||||
|
"confirmOption": "Confirm Option {number}",
|
||||||
|
"deleteOthersHint": "(delete others)",
|
||||||
|
"confirmSuccess": "Selection confirmed",
|
||||||
|
"confirmFailed": "Confirm selection failed: {error}",
|
||||||
|
"selectFailed": "Select image failed: {error}",
|
||||||
|
"cancelSelection": "Cancel Selection",
|
||||||
|
"deleteThis": "Delete this appearance",
|
||||||
|
"undoFailed": "Undo failed",
|
||||||
|
"undoSuccess": "✓ Reverted to previous version",
|
||||||
|
"editFailed": "Image edit failed",
|
||||||
|
"editSuccess": "Image edit successful",
|
||||||
|
"regenerateFailed": "Regenerate failed: {error}"
|
||||||
|
},
|
||||||
|
"modal": {
|
||||||
|
"newCharacter": "New Character",
|
||||||
|
"addSubAppearance": "Add Sub-Appearance",
|
||||||
|
"aiDesign": "AI Design",
|
||||||
|
"aiDesigning": "Designing...",
|
||||||
|
"designInstruction": "Please enter design instruction",
|
||||||
|
"enterNameDesc": "Please enter character name and description",
|
||||||
|
"selectCharacter": "Please select a character",
|
||||||
|
"enterChangeReason": "Please enter change reason",
|
||||||
|
"enterSubDesc": "Please enter appearance description",
|
||||||
|
"insufficientBalance": "Insufficient Balance\n\n{error}",
|
||||||
|
"designFailed": "AI Design Failed: {error}",
|
||||||
|
"addFailed": "Add Failed: {error}",
|
||||||
|
"aiDesignPlaceholderNew": "e.g. A 20-year-old female mage, blonde hair, blue eyes...",
|
||||||
|
"aiDesignPlaceholderSub": "e.g. Changed into black combat gear...",
|
||||||
|
"aiTipNew": "Describe the character, AI will generate details",
|
||||||
|
"aiTipSub": "Describe the new state, AI will generate sub-appearance description",
|
||||||
|
"nameLabel": "Character Name",
|
||||||
|
"namePlaceholder": "Enter name...",
|
||||||
|
"descLabel": "Appearance Description",
|
||||||
|
"descPlaceholder": "Enter description...",
|
||||||
|
"selectLabel": "Select Character",
|
||||||
|
"selectPlaceholder": "-- Select Character --",
|
||||||
|
"existingAppearances": "Existing:",
|
||||||
|
"reasonLabel": "Change Reason",
|
||||||
|
"reasonPlaceholder": "e.g. After changing clothes, Injured...",
|
||||||
|
"reasonTip": "Briefly describe the difference from primary appearance",
|
||||||
|
"subDescPlaceholder": "Describe only the changes...",
|
||||||
|
"subDescTip": "Only describe changes (clothes, state), face/body inherits from primary",
|
||||||
|
"adding": "Adding...",
|
||||||
|
"insufficientBalanceDefault": "Insufficient balance, please top up to continue",
|
||||||
|
"addFailedGeneric": "Add Failed",
|
||||||
|
"appearancesCount": "Appearances",
|
||||||
|
"addCharacter": "Add Character",
|
||||||
|
"addLocation": "Add Location",
|
||||||
|
"aiDesignTip": "Describe the scene you want, AI will generate name and details",
|
||||||
|
"designing": "AI designing...",
|
||||||
|
"saveName": "Save Name",
|
||||||
|
"saveOnly": "Save Only",
|
||||||
|
"sceneDescription": "Scene Description",
|
||||||
|
"scenePrompt": "Scene Description Prompt",
|
||||||
|
"appearancePrompt": "Appearance Description Prompt",
|
||||||
|
"smartModify": "Smart Modify",
|
||||||
|
"modifyPlaceholder": "e.g.: Change to night, add moonlight, add curtains...",
|
||||||
|
"modifyPlaceholderCharacter": "e.g.: Change hair to blonde, height to 180cm, wear black suit...",
|
||||||
|
"modifying": "Smart modifying...",
|
||||||
|
"modifyFailed": "Modification failed",
|
||||||
|
"editCharacter": "Edit Character",
|
||||||
|
"editLocation": "Edit Location",
|
||||||
|
"saveAndGenerate": "Save and Generate",
|
||||||
|
"generatingAutoClose": "Generating image, will close automatically when done...",
|
||||||
|
"aiLocationTip": "Enter what you want to modify, AI will adjust the scene description",
|
||||||
|
"aiDesignPlaceholderLocation": "e.g. An ancient magical library, towering bookshelves, dim candlelight, mysterious atmosphere...",
|
||||||
|
"artStyle": "Art Style",
|
||||||
|
"generate": "Generate",
|
||||||
|
"introduction": "Character Introduction",
|
||||||
|
"introductionPlaceholder": "e.g.: The protagonist; 'I' refers to her. Others call her 'Snow' or 'Sister Snow'...",
|
||||||
|
"introductionTip": "Describe the character's role in the story, narrative perspective (who 'I' refers to), how others address them",
|
||||||
|
"saveIntroduction": "Save Introduction"
|
||||||
|
},
|
||||||
|
"toolbar": {
|
||||||
|
"filter": "Filter",
|
||||||
|
"viewAll": "View All",
|
||||||
|
"showGenerated": "Generated",
|
||||||
|
"showPending": "Pending",
|
||||||
|
"assetManagement": "Asset Management",
|
||||||
|
"assetCount": "{total} assets ({appearances} character appearances + {locations} locations)",
|
||||||
|
"globalAnalyze": "Global Analysis",
|
||||||
|
"globalAnalyzing": "Performing global asset analysis...",
|
||||||
|
"globalAnalyzingHint": "Please don't refresh. Results will appear automatically when complete",
|
||||||
|
"globalAnalyzingTip": "Analyzing all episodes, extracting characters and locations...",
|
||||||
|
"globalAnalyzeHint": "Analyze all episodes to extract characters and locations",
|
||||||
|
"globalAnalyzeSuccess": "Global analysis complete: {characters} new characters, {locations} new locations",
|
||||||
|
"globalAnalyzeFailed": "Global analysis failed",
|
||||||
|
"generateAll": "Generate All Images",
|
||||||
|
"generateAllNoop": "All assets already have images, nothing to generate",
|
||||||
|
"generating": "Generating ({current}/{total})",
|
||||||
|
"regenerateAll": "Regenerate All",
|
||||||
|
"regenerateAllConfirm": "Regenerate images for all assets? This will overwrite existing images.",
|
||||||
|
"noAssetsToGenerate": "No assets available for generation",
|
||||||
|
"regenerateAllHint": "Regenerate all asset images (overwrite existing)"
|
||||||
|
},
|
||||||
|
"common": {
|
||||||
|
"actions": "Actions",
|
||||||
|
"add": "Add",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"confirm": "Confirm",
|
||||||
|
"copy": "Copy",
|
||||||
|
"delete": "Delete",
|
||||||
|
"download": "Download",
|
||||||
|
"edit": "Edit",
|
||||||
|
"generate": "Generate",
|
||||||
|
"generateFailed": "Generation Failed",
|
||||||
|
"loading": "Loading...",
|
||||||
|
"none": "None",
|
||||||
|
"preview": "Preview",
|
||||||
|
"refresh": "Refresh",
|
||||||
|
"regenerate": "Regenerate",
|
||||||
|
"save": "Save",
|
||||||
|
"status": "Status",
|
||||||
|
"submitFailed": "Submit Failed",
|
||||||
|
"upload": "Upload",
|
||||||
|
"unknownError": "Unknown error"
|
||||||
|
},
|
||||||
|
"video": {
|
||||||
|
"panelCard": {
|
||||||
|
"generating": "Generating...",
|
||||||
|
"editPrompt": "Edit Prompt"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"smartImport": {
|
||||||
|
"preview": {
|
||||||
|
"saving": "Saving..."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"storyboard": {
|
||||||
|
"group": {
|
||||||
|
"generating": "Generating..."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"saveFailed": "Save Failed, please retry",
|
||||||
|
"failed": "failed, please retry",
|
||||||
|
"insufficientBalance": "Insufficient balance",
|
||||||
|
"aiDesignFailed": "AI design failed",
|
||||||
|
"createFailed": "Creation failed"
|
||||||
|
},
|
||||||
|
"assetLibrary": {
|
||||||
|
"button": "Asset Library",
|
||||||
|
"title": "Asset Library",
|
||||||
|
"copySuccessCharacter": "Character appearance copied successfully",
|
||||||
|
"copySuccessLocation": "Location image copied successfully",
|
||||||
|
"copySuccessVoice": "Voice copied successfully",
|
||||||
|
"copyFailed": "Copy failed: {error}"
|
||||||
|
},
|
||||||
|
"tts": {
|
||||||
|
"voiceDesignSaved": "AI-designed voice has been set for {name}",
|
||||||
|
"saveVoiceDesignFailed": "Failed to save voice design: {error}",
|
||||||
|
"title": "Voice",
|
||||||
|
"noVoice": "No voice",
|
||||||
|
"previewFailed": "Preview failed: {error}",
|
||||||
|
"uploadFailed": "Upload audio failed: {error}",
|
||||||
|
"uploading": "Uploading...",
|
||||||
|
"uploaded": "Uploaded",
|
||||||
|
"uploadAudio": "Upload Audio",
|
||||||
|
"pause": "Pause",
|
||||||
|
"preview": "Preview Voice"
|
||||||
|
},
|
||||||
|
"characterProfile": {
|
||||||
|
"importance": {
|
||||||
|
"S": "S-Level - Main Protagonist",
|
||||||
|
"A": "A-Level - Core Supporting",
|
||||||
|
"B": "B-Level - Important Supporting",
|
||||||
|
"C": "C-Level - Minor Character",
|
||||||
|
"D": "D-Level - Extra"
|
||||||
|
},
|
||||||
|
"costumeLevel": {
|
||||||
|
"5": "Royal/Luxury",
|
||||||
|
"4": "Noble/Elite",
|
||||||
|
"3": "Professional/Quality",
|
||||||
|
"2": "Casual/Normal",
|
||||||
|
"1": "Plain/Uniform"
|
||||||
|
},
|
||||||
|
"importanceLevel": "Character Importance Level",
|
||||||
|
"characterArchetype": "Character Archetype",
|
||||||
|
"archetypePlaceholder": "e.g.: Domineering CEO, Schemer",
|
||||||
|
"personalityTags": "Personality Tags",
|
||||||
|
"addTagPlaceholder": "Add tag",
|
||||||
|
"costumeLevelLabel": "Costume Level",
|
||||||
|
"suggestedColors": "Suggested Colors",
|
||||||
|
"colorPlaceholder": "e.g.: Navy blue, Gold",
|
||||||
|
"primaryMarker": "Primary Identifier",
|
||||||
|
"markerNote": "(Recommended for S/A level)",
|
||||||
|
"markingsPlaceholder": "e.g.: Tear-shaped mole, Silver earring",
|
||||||
|
"visualKeywords": "Visual Keywords",
|
||||||
|
"keywordsPlaceholder": "e.g.: Elite aura, Ascetic style",
|
||||||
|
"editDialogTitle": "Edit Character Profile - {name}",
|
||||||
|
"confirmAndGenerate": "Confirm & Generate",
|
||||||
|
"useExisting": "Use Existing",
|
||||||
|
"editProfile": "Edit Profile",
|
||||||
|
"delete": "Delete Character",
|
||||||
|
"summary": {
|
||||||
|
"gender": "Gender:",
|
||||||
|
"age": "Age:",
|
||||||
|
"era": "Era:",
|
||||||
|
"class": "Class:",
|
||||||
|
"occupation": "Occupation:",
|
||||||
|
"personality": "Personality:",
|
||||||
|
"costume": "Costume:",
|
||||||
|
"identifier": "Identifier:"
|
||||||
|
},
|
||||||
|
"parseFailed": "Failed to parse profile data",
|
||||||
|
"confirmSuccessGenerating": "✓ Profile confirmed. Visual description generation started",
|
||||||
|
"confirmFailed": "Confirm failed: {error}",
|
||||||
|
"noPendingCharacters": "No pending characters to confirm",
|
||||||
|
"batchConfirmPrompt": "Generate visual descriptions for {count} characters?",
|
||||||
|
"batchConfirmSuccess": "✓ Visual descriptions generated for {count} characters",
|
||||||
|
"batchConfirmFailed": "Batch confirmation failed: {error}",
|
||||||
|
"deleteConfirm": "Delete this character? This action cannot be undone.",
|
||||||
|
"deleteSuccess": "✓ Character deleted",
|
||||||
|
"deleteFailed": "Delete failed: {error}"
|
||||||
|
},
|
||||||
|
"imageEdit": {
|
||||||
|
"editCharacterImage": "Edit Character Image",
|
||||||
|
"editLocationImage": "Edit Location Image",
|
||||||
|
"characterLabel": "Character: {name}",
|
||||||
|
"locationLabel": "Location: {name}",
|
||||||
|
"editInstruction": "Edit Instruction",
|
||||||
|
"subtitle": "Enter an edit instruction and optionally upload reference images",
|
||||||
|
"characterPlaceholder": "Describe what you want to change, e.g.: Change hair to blonde, add glasses, change to casual clothes...",
|
||||||
|
"locationPlaceholder": "Describe what you want to change, e.g.: Add more trees, change to night scene...",
|
||||||
|
"storyboardPlaceholder": "Describe what you want to change, e.g.: Change background color, adjust character expression...",
|
||||||
|
"noAssetHint": "No assets, click \"Add Asset\" to select",
|
||||||
|
"referenceImages": "Reference Images",
|
||||||
|
"referenceImagesHint": "(optional, paste supported)",
|
||||||
|
"startEditing": "Start Editing"
|
||||||
|
}
|
||||||
|
}
|
||||||
29
messages/en/auth.json
Normal file
29
messages/en/auth.json
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"welcomeBack": "Welcome Back",
|
||||||
|
"loginTo": "Sign in to waoowaoo",
|
||||||
|
"createAccount": "Create Account",
|
||||||
|
"joinPlatform": "Join waoowaoo",
|
||||||
|
"phoneNumber": "Username",
|
||||||
|
"password": "Password",
|
||||||
|
"confirmPassword": "Confirm Password",
|
||||||
|
"phoneNumberPlaceholder": "Enter your username",
|
||||||
|
"passwordPlaceholder": "Enter your password",
|
||||||
|
"passwordMinPlaceholder": "Enter password (at least 6 characters)",
|
||||||
|
"confirmPasswordPlaceholder": "Re-enter your password",
|
||||||
|
"loginButton": "Sign In",
|
||||||
|
"loginButtonLoading": "Signing in...",
|
||||||
|
"signupButton": "Sign Up",
|
||||||
|
"signupButtonLoading": "Signing up...",
|
||||||
|
"noAccount": "Don't have an account?",
|
||||||
|
"hasAccount": "Already have an account?",
|
||||||
|
"signupNow": "Sign Up Now",
|
||||||
|
"signinNow": "Sign In Now",
|
||||||
|
"backToHome": "← Back to Home",
|
||||||
|
"loginFailed": "Login failed, please check your phone number and password",
|
||||||
|
"loginError": "An error occurred during login",
|
||||||
|
"passwordMismatch": "Passwords do not match",
|
||||||
|
"passwordTooShort": "Password must be at least 6 characters",
|
||||||
|
"signupSuccess": "Registration successful! Redirecting to login page...",
|
||||||
|
"signupFailed": "Registration failed",
|
||||||
|
"signupError": "An error occurred during registration"
|
||||||
|
}
|
||||||
15
messages/en/billing.json
Normal file
15
messages/en/billing.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"transactionType": "Transaction Type",
|
||||||
|
"startDate": "Start Date",
|
||||||
|
"endDate": "End Date",
|
||||||
|
"all": "All",
|
||||||
|
"income": "Income",
|
||||||
|
"expense": "Expense",
|
||||||
|
"reset": "Reset",
|
||||||
|
"filter": "Filter",
|
||||||
|
"noRecords": "No records",
|
||||||
|
"accountRecharge": "Account Recharge",
|
||||||
|
"serviceConsumption": "Service Consumption",
|
||||||
|
"balance": "Balance",
|
||||||
|
"allTypes": "All Types"
|
||||||
|
}
|
||||||
134
messages/en/common.json
Normal file
134
messages/en/common.json
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
{
|
||||||
|
"appName": "waoowaoo",
|
||||||
|
"betaVersion": "Beta v0.1",
|
||||||
|
"loading": "Loading...",
|
||||||
|
"save": "Save",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"confirm": "Confirm",
|
||||||
|
"delete": "Delete",
|
||||||
|
"edit": "Edit",
|
||||||
|
"search": "Search",
|
||||||
|
"clear": "Clear",
|
||||||
|
"close": "Close",
|
||||||
|
"back": "Back",
|
||||||
|
"next": "Next",
|
||||||
|
"previous": "Previous",
|
||||||
|
"submit": "Submit",
|
||||||
|
"reset": "Reset",
|
||||||
|
"generate": "Generate",
|
||||||
|
"regenerate": "Regenerate",
|
||||||
|
"preview": "Preview",
|
||||||
|
"download": "Download",
|
||||||
|
"upload": "Upload",
|
||||||
|
"select": "Select",
|
||||||
|
"add": "Add",
|
||||||
|
"remove": "Remove",
|
||||||
|
"refresh": "Refresh",
|
||||||
|
"expand": "Expand",
|
||||||
|
"collapse": "Collapse",
|
||||||
|
"all": "All",
|
||||||
|
"none": "None",
|
||||||
|
"success": "Success",
|
||||||
|
"error": "Error",
|
||||||
|
"warning": "Warning",
|
||||||
|
"info": "Info",
|
||||||
|
"copy": "Copy",
|
||||||
|
"paste": "Paste",
|
||||||
|
"apply": "Apply",
|
||||||
|
"autoSave": "Auto-save",
|
||||||
|
"saved": "Saved",
|
||||||
|
"episode": "Episode",
|
||||||
|
"project": "Project",
|
||||||
|
"editEpisodeName": "Edit Episode Name",
|
||||||
|
"deleteEpisode": "Delete Episode",
|
||||||
|
"deleteEpisodeConfirm": "Confirm Delete",
|
||||||
|
"newEpisode": "New Episode",
|
||||||
|
"optional": "(Optional)",
|
||||||
|
"rename": "Rename",
|
||||||
|
"dragToReorder": "Drag to reorder",
|
||||||
|
"episodeNamePlaceholder": "Enter episode name...",
|
||||||
|
"cancelSelection": "Cancel selection",
|
||||||
|
"referenceImage": "Reference image",
|
||||||
|
"previewLarge": "Preview large",
|
||||||
|
"viewOriginal": "View original",
|
||||||
|
"schemeN": "Scheme {n}",
|
||||||
|
"insufficientBalance": "Insufficient Balance",
|
||||||
|
"insufficientBalanceDetail": "Insufficient account balance, please recharge to continue",
|
||||||
|
"operationFailed": "Operation failed",
|
||||||
|
"pleaseRetry": "Please retry",
|
||||||
|
"recommended": "Recommended",
|
||||||
|
"language": {
|
||||||
|
"select": "Select language",
|
||||||
|
"zh": "Chinese",
|
||||||
|
"en": "English",
|
||||||
|
"switchConfirmTitle": "Switch language?",
|
||||||
|
"switchConfirmMessage": "Switching to {targetLanguage} will update not only interface text, but also end-to-end prompts, script generation, and workflow outputs. Continue?",
|
||||||
|
"switchConfirmAction": "Switch now"
|
||||||
|
},
|
||||||
|
"taskStatus": {
|
||||||
|
"intent": {
|
||||||
|
"generate": {
|
||||||
|
"running": {
|
||||||
|
"image": "Generating",
|
||||||
|
"video": "Generating",
|
||||||
|
"audio": "Generating",
|
||||||
|
"text": "Generating"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"regenerate": {
|
||||||
|
"running": {
|
||||||
|
"image": "Regenerating",
|
||||||
|
"video": "Regenerating",
|
||||||
|
"audio": "Regenerating",
|
||||||
|
"text": "Regenerating"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"modify": {
|
||||||
|
"running": {
|
||||||
|
"image": "Modifying",
|
||||||
|
"video": "Modifying",
|
||||||
|
"audio": "Modifying",
|
||||||
|
"text": "Modifying"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"analyze": {
|
||||||
|
"running": {
|
||||||
|
"image": "Analyzing",
|
||||||
|
"video": "Analyzing",
|
||||||
|
"audio": "Analyzing",
|
||||||
|
"text": "Analyzing"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"build": {
|
||||||
|
"running": {
|
||||||
|
"image": "Building",
|
||||||
|
"video": "Building",
|
||||||
|
"audio": "Building",
|
||||||
|
"text": "Building"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"convert": {
|
||||||
|
"running": {
|
||||||
|
"image": "Converting",
|
||||||
|
"video": "Converting",
|
||||||
|
"audio": "Converting",
|
||||||
|
"text": "Converting"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"process": {
|
||||||
|
"running": {
|
||||||
|
"image": "Processing",
|
||||||
|
"video": "Processing",
|
||||||
|
"audio": "Processing",
|
||||||
|
"text": "Processing"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"failed": {
|
||||||
|
"image": "Failed",
|
||||||
|
"video": "Failed",
|
||||||
|
"audio": "Failed",
|
||||||
|
"text": "Failed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
31
messages/en/configModal.json
Normal file
31
messages/en/configModal.json
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"title": "Project Global Configuration",
|
||||||
|
"saved": "Saved",
|
||||||
|
"autoSave": "Auto-save",
|
||||||
|
"visualStyle": "Visual Style",
|
||||||
|
"modelParams": "Model Parameters",
|
||||||
|
"aspectRatio": "Aspect Ratio",
|
||||||
|
"ttsSettings": "TTS Settings",
|
||||||
|
"loadingModels": "Loading model list...",
|
||||||
|
"analysisModel": "Analysis Model",
|
||||||
|
"characterModel": "Character Model",
|
||||||
|
"locationModel": "Location Model",
|
||||||
|
"storyboardModel": "Storyboard Model",
|
||||||
|
"editModel": "Edit Model",
|
||||||
|
"videoModel": "Video Model",
|
||||||
|
"videoResolution": "Video Resolution",
|
||||||
|
"ttsVoice": "TTS Voice",
|
||||||
|
"ttsRate": "Speech Rate",
|
||||||
|
"fetchModelsFailed": "Failed to fetch user model list",
|
||||||
|
"placeholder": "Please enter...",
|
||||||
|
"description": "Description",
|
||||||
|
"hint": "Hint",
|
||||||
|
"pleaseSelect": "Please select...",
|
||||||
|
"selectModel": "Select Model",
|
||||||
|
"paramConfig": "Parameters",
|
||||||
|
"fixed": "Fixed",
|
||||||
|
"noParams": "No configurable parameters",
|
||||||
|
"confirm": "Confirm",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"delete": "Delete"
|
||||||
|
}
|
||||||
19
messages/en/errors.json
Normal file
19
messages/en/errors.json
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"UNAUTHORIZED": "Please log in first",
|
||||||
|
"FORBIDDEN": "Access denied",
|
||||||
|
"NOT_FOUND": "Resource not found",
|
||||||
|
"INSUFFICIENT_BALANCE": "Insufficient API balance. Please top up and retry",
|
||||||
|
"RATE_LIMIT": "Too many requests. Please retry in {retryAfter} seconds",
|
||||||
|
"QUOTA_EXCEEDED": "Quota exceeded. Please try again later",
|
||||||
|
"GENERATION_FAILED": "Generation failed. Please retry",
|
||||||
|
"GENERATION_TIMEOUT": "Generation timed out. Please retry",
|
||||||
|
"SENSITIVE_CONTENT": "Content may contain sensitive material",
|
||||||
|
"INVALID_PARAMS": "Invalid parameters",
|
||||||
|
"MISSING_CONFIG": "Please complete model configuration first",
|
||||||
|
"INTERNAL_ERROR": "Server error. Please try again later",
|
||||||
|
"NETWORK_ERROR": "Network error. Please check your connection",
|
||||||
|
"EXTERNAL_ERROR": "External service temporarily unavailable. Please retry later",
|
||||||
|
"TASK_NOT_READY": "Task is still processing",
|
||||||
|
"NO_RESULT": "Task has no result",
|
||||||
|
"CONFLICT": "Resource state conflict"
|
||||||
|
}
|
||||||
26
messages/en/landing.json
Normal file
26
messages/en/landing.json
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"title": "waoowaoo",
|
||||||
|
"subtitle": "AI Film & TV Studio",
|
||||||
|
"enterWorkspace": "Enter Workspace",
|
||||||
|
"getStarted": "Get Started",
|
||||||
|
"learnMore": "Learn More",
|
||||||
|
"features": {
|
||||||
|
"title": "Unleash Infinite Creativity",
|
||||||
|
"subtitle": "Full-process AI assistance, from script to final cut",
|
||||||
|
"character": {
|
||||||
|
"title": "Character Workshop",
|
||||||
|
"description": "Create unique anime characters with high consistency"
|
||||||
|
},
|
||||||
|
"storyboard": {
|
||||||
|
"title": "Smart Storyboard",
|
||||||
|
"description": "One-click text to storyboard, precise narrative control"
|
||||||
|
},
|
||||||
|
"world": {
|
||||||
|
"title": "World Building",
|
||||||
|
"description": "Immersive scene generation to build grand story backgrounds"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"copyright": "2026 waoowaoo AI. All rights reserved."
|
||||||
|
}
|
||||||
|
}
|
||||||
4
messages/en/layout.json
Normal file
4
messages/en/layout.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"title": "AI Anime Production Platform",
|
||||||
|
"description": "Create professional anime content with cutting-edge AI technology"
|
||||||
|
}
|
||||||
27
messages/en/modelSection.json
Normal file
27
messages/en/modelSection.json
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"llmModels": "Text Model List",
|
||||||
|
"imageModels": "Image Model List",
|
||||||
|
"videoModels": "Video Model List",
|
||||||
|
"price": "Price",
|
||||||
|
"pricePerMillion": "Per million tokens",
|
||||||
|
"pricePerImage": "Per image",
|
||||||
|
"pricePerVideo": "Per video",
|
||||||
|
"name": "Name",
|
||||||
|
"modelId": "Model ID",
|
||||||
|
"modelName": "Model Name",
|
||||||
|
"provider": "Provider",
|
||||||
|
"resolution": "Resolution",
|
||||||
|
"add": "Add",
|
||||||
|
"addModel": "Add Model",
|
||||||
|
"addNewModel": "Add New Model",
|
||||||
|
"selectPreset": "Select Preset Model",
|
||||||
|
"customModel": "Custom Model",
|
||||||
|
"confirmAdd": "Confirm",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"done": "Done",
|
||||||
|
"fillComplete": "Please fill in all fields",
|
||||||
|
"noModels": "No models yet, click the button above to add",
|
||||||
|
"noApiKey": "Configure API Key",
|
||||||
|
"batchMode": "Batch",
|
||||||
|
"batchModeTooltip": "Offline inference, 50% cheaper, completes within 24 hours"
|
||||||
|
}
|
||||||
8
messages/en/nav.json
Normal file
8
messages/en/nav.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"workspace": "Workspace",
|
||||||
|
"assetHub": "Asset Hub",
|
||||||
|
"profile": "Settings",
|
||||||
|
"signin": "Sign In",
|
||||||
|
"signup": "Sign Up",
|
||||||
|
"logout": "Logout"
|
||||||
|
}
|
||||||
139
messages/en/novel-promotion.json
Normal file
139
messages/en/novel-promotion.json
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
{
|
||||||
|
"stages": {
|
||||||
|
"story": "Story",
|
||||||
|
"script": "Script",
|
||||||
|
"storyboard": "Storyboard",
|
||||||
|
"video": "Video",
|
||||||
|
"editor": "AI Editor",
|
||||||
|
"editorComingSoon": "Coming soon, follow us for updates"
|
||||||
|
},
|
||||||
|
"buttons": {
|
||||||
|
"assetLibrary": "Asset Library",
|
||||||
|
"settings": "Settings",
|
||||||
|
"refreshData": "Refresh Data",
|
||||||
|
"enterVideoGeneration": "Enter Video Generation →"
|
||||||
|
},
|
||||||
|
"smartImport": {
|
||||||
|
"title": "Start Your Creative Journey",
|
||||||
|
"subtitle": "First, choose your creation method",
|
||||||
|
"manualCreate": {
|
||||||
|
"title": "Create from Episode 1",
|
||||||
|
"description": "Start from episode 1, suitable for episodic creation or single short videos",
|
||||||
|
"button": "Start Creating"
|
||||||
|
},
|
||||||
|
"smartImport": {
|
||||||
|
"title": "Smart Import Full Book",
|
||||||
|
"description": "Upload a complete novel or script, AI engine automatically recognizes chapter structure and splits into episodes.",
|
||||||
|
"button": "Import Now",
|
||||||
|
"recommended": "Recommended"
|
||||||
|
},
|
||||||
|
"upload": {
|
||||||
|
"title": "Upload Raw Material",
|
||||||
|
"subtitle": "AI engine is ready, automatic episode splitting and formatting",
|
||||||
|
"maxWords": "(Max 30,000 words)",
|
||||||
|
"textInput": "Enter Text Content",
|
||||||
|
"documentUpload": "Upload Full Document",
|
||||||
|
"placeholder": "Paste your novel chapters or script content here...",
|
||||||
|
"filePlaceholder": "File uploaded mode",
|
||||||
|
"clickUpload": "Click to upload document",
|
||||||
|
"clearTextFirst": "Please clear left text first",
|
||||||
|
"supportedFormats": "Supports Word, TXT formats",
|
||||||
|
"preview": "Preview",
|
||||||
|
"expandPreview": "Expand More",
|
||||||
|
"collapsePreview": "Collapse",
|
||||||
|
"deleteFile": "Delete File",
|
||||||
|
"startAnalysis": "Start Smart Analysis",
|
||||||
|
"back": "Back",
|
||||||
|
"words": "words"
|
||||||
|
},
|
||||||
|
"analyzing": {
|
||||||
|
"title": "AI is Analyzing Your Story",
|
||||||
|
"description": "Recognizing chapter structure, smart splitting in progress...",
|
||||||
|
"autoSave": "Will auto-save after analysis complete"
|
||||||
|
},
|
||||||
|
"preview": {
|
||||||
|
"title": "Smart Splitting Complete",
|
||||||
|
"episodeCount": "Automatically split into {count} episodes",
|
||||||
|
"totalWords": "Total {count} words",
|
||||||
|
"autoSaved": "✓ Auto-saved",
|
||||||
|
"reanalyze": "Re-analyze",
|
||||||
|
"confirm": "Confirm Complete",
|
||||||
|
"saving": "Saving...",
|
||||||
|
"episodeList": "Episode List",
|
||||||
|
"addEpisode": "Add Episode",
|
||||||
|
"averageWords": "Average per episode",
|
||||||
|
"episodeContent": "Episode Content",
|
||||||
|
"episodePlaceholder": "Enter episode title...",
|
||||||
|
"summaryPlaceholder": "Enter plot summary...",
|
||||||
|
"newEpisode": "New Episode",
|
||||||
|
"deleteEpisode": "Delete Episode",
|
||||||
|
"deleteConfirm": {
|
||||||
|
"title": "Confirm Delete",
|
||||||
|
"message": "Are you sure you want to delete \"{title}\"?",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"confirm": "Confirm Delete"
|
||||||
|
},
|
||||||
|
"tip": {
|
||||||
|
"title": "Tip",
|
||||||
|
"content": "You can directly edit titles, summaries, and content. After clicking [Confirm Complete], episodes will be officially imported into the project"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"fileTooLarge": "File too large, please upload a file smaller than 10MB",
|
||||||
|
"docNotSupported": ".doc format not supported, please convert to .docx in Word",
|
||||||
|
"fileEmpty": "File content is empty",
|
||||||
|
"fileReadError": "File read failed, please try again",
|
||||||
|
"uploadFirst": "Please upload or paste content first",
|
||||||
|
"analyzeFailed": "Analysis failed",
|
||||||
|
"saveFailed": "Save failed"
|
||||||
|
},
|
||||||
|
"cancelConfirm": "Are you sure you want to cancel? Analyzed episodes will be cleared."
|
||||||
|
},
|
||||||
|
"storyInput": {
|
||||||
|
"currentEditing": "Currently editing: {name}",
|
||||||
|
"editingTip": "The following workflow is for this episode only. Switch episodes in the top left if needed",
|
||||||
|
"wordCount": "Word count:",
|
||||||
|
"assetLibraryTip": {
|
||||||
|
"title": "Need custom characters and locations?",
|
||||||
|
"description": "Click the 「Asset Library」 button in the top right to upload asset setting documents or manually add characters/locations. AI will prioritize using settings from the asset library for analysis."
|
||||||
|
},
|
||||||
|
"videoRatio": "Video Ratio",
|
||||||
|
"visualStyle": "Visual Style",
|
||||||
|
"moreConfig": "For more configuration options, click the 「 Settings」 button in the top right",
|
||||||
|
"narration": {
|
||||||
|
"title": "Enable Narration Voiceover",
|
||||||
|
"description": "Generate TTS voice narration to add commentary to your video"
|
||||||
|
},
|
||||||
|
"creating": "AI Creating...",
|
||||||
|
"ready": "✓ Configuration complete, ready for next step",
|
||||||
|
"pleaseInput": "Please enter script content first"
|
||||||
|
},
|
||||||
|
"execution": {
|
||||||
|
"selectEpisode": "Please select an episode first",
|
||||||
|
"fillContentFirst": "Please enter content first",
|
||||||
|
"requestAborted": "Request aborted (possibly due to page refresh)",
|
||||||
|
"analysisFailed": "Asset analysis failed",
|
||||||
|
"prepareFailed": "Preparation failed",
|
||||||
|
"generationFailed": "Generation failed",
|
||||||
|
"batchVideoFailed": "Batch video generation failed",
|
||||||
|
"updateFailed": "Update failed",
|
||||||
|
"saveFailed": "Save failed",
|
||||||
|
"storyToScriptRunning": "Story→Script V2 running",
|
||||||
|
"scriptToStoryboardRunning": "Script→Storyboard V2 running",
|
||||||
|
"storyToScriptFailed": "Story to script failed",
|
||||||
|
"scriptToStoryboardFailed": "Script to storyboard failed",
|
||||||
|
"taskStreamTimeout": "Task timed out. The task may still be running in the background — please check its status or retry"
|
||||||
|
},
|
||||||
|
"rebuildConfirm": {
|
||||||
|
"storyToScript": {
|
||||||
|
"title": "Script Flow Will Be Rebuilt",
|
||||||
|
"message": "Downstream storyboard data is detected for this episode ({storyboardCount} storyboards, {panelCount} panels). Continuing will clear and rebuild this data. Continue?"
|
||||||
|
},
|
||||||
|
"scriptToStoryboard": {
|
||||||
|
"title": "Storyboard Data Will Be Rebuilt",
|
||||||
|
"message": "Existing storyboard data is detected for this episode ({storyboardCount} storyboards, {panelCount} panels). Continuing will clear current storyboards and regenerate them. Continue?"
|
||||||
|
},
|
||||||
|
"confirm": "Continue and Clear",
|
||||||
|
"cancel": "Cancel"
|
||||||
|
}
|
||||||
|
}
|
||||||
107
messages/en/profile.json
Normal file
107
messages/en/profile.json
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
{
|
||||||
|
"user": "User",
|
||||||
|
"personalAccount": "Personal Account",
|
||||||
|
"availableBalance": "Available Balance",
|
||||||
|
"frozen": "Frozen",
|
||||||
|
"totalSpent": "Total Spent",
|
||||||
|
"apiConfig": "API Configuration",
|
||||||
|
"rechargeRecords": "Recharge Records",
|
||||||
|
"billingRecords": "Billing Records",
|
||||||
|
"logout": "Logout",
|
||||||
|
"accountTransactions": "Account Transactions",
|
||||||
|
"projectDetails": "Project Details",
|
||||||
|
"summary": "Summary",
|
||||||
|
"transactions": "Transactions",
|
||||||
|
"noTransactions": "No transaction records",
|
||||||
|
"noProjectCosts": "No project cost records",
|
||||||
|
"noDetails": "This project has no cost details",
|
||||||
|
"noRecords": "No records",
|
||||||
|
"byType": "By Type",
|
||||||
|
"byAction": "By Action",
|
||||||
|
"times": "times",
|
||||||
|
"total": "Total",
|
||||||
|
"filter": "Filter",
|
||||||
|
"allTypes": "All Types",
|
||||||
|
"recharge": "Account Recharge",
|
||||||
|
"consume": "Service Consumption",
|
||||||
|
"balanceAfter": "Balance {amount}",
|
||||||
|
"recordCount": "{count} records",
|
||||||
|
"totalCost": "Total {amount}",
|
||||||
|
"previousPage": "Previous",
|
||||||
|
"nextPage": "Next",
|
||||||
|
"pagination": "{total} items, Page {page} / {totalPages}",
|
||||||
|
"episodeLabel": "Episode {number}",
|
||||||
|
"billingDetail": {
|
||||||
|
"imageWithRes": "{count} images · {resolution}",
|
||||||
|
"image": "{count} images",
|
||||||
|
"videoWithRes": "{count} videos · {resolution}",
|
||||||
|
"video": "{count} videos",
|
||||||
|
"tokens": "{count} tokens",
|
||||||
|
"seconds": "{count}s",
|
||||||
|
"calls": "{count} calls"
|
||||||
|
},
|
||||||
|
"apiTypes": {
|
||||||
|
"image": "Image Generation",
|
||||||
|
"video": "Video Generation",
|
||||||
|
"text": "Text Analysis",
|
||||||
|
"tts": "Text-to-Speech",
|
||||||
|
"voice": "Voice Acting",
|
||||||
|
"voice_design": "Voice Design",
|
||||||
|
"lip_sync": "Lip Sync"
|
||||||
|
},
|
||||||
|
"actionTypes": {
|
||||||
|
"image_panel": "Storyboard Image",
|
||||||
|
"image_character": "Character Image",
|
||||||
|
"image_location": "Location Image",
|
||||||
|
"video_panel": "Video Generation",
|
||||||
|
"lip_sync": "Lip Sync",
|
||||||
|
"voice_line": "Voice Synthesis",
|
||||||
|
"voice_design": "Voice Design",
|
||||||
|
"asset_hub_voice_design": "Asset Hub Voice Design",
|
||||||
|
"regenerate_storyboard_text": "Regenerate Storyboard Text",
|
||||||
|
"insert_panel": "Insert Panel",
|
||||||
|
"panel_variant": "Shot Variant",
|
||||||
|
"modify_asset_image": "Modify Image",
|
||||||
|
"regenerate_group": "Batch Regenerate",
|
||||||
|
"asset_hub_image": "Asset Hub Image",
|
||||||
|
"asset_hub_modify": "Asset Hub Modify Image",
|
||||||
|
"analyze_novel": "Novel Analysis",
|
||||||
|
"story_to_script_run": "Story to Script",
|
||||||
|
"script_to_storyboard_run": "Script to Storyboard",
|
||||||
|
"clips_build": "Clips Build",
|
||||||
|
"screenplay_convert": "Screenplay Convert",
|
||||||
|
"voice_analyze": "Voice Analysis",
|
||||||
|
"analyze_global": "Global Analysis",
|
||||||
|
"ai_modify_appearance": "AI Modify Appearance",
|
||||||
|
"ai_modify_location": "AI Modify Location",
|
||||||
|
"ai_modify_shot_prompt": "AI Modify Shot Prompt",
|
||||||
|
"analyze_shot_variants": "Analyze Shot Variants",
|
||||||
|
"ai_create_character": "AI Create Character",
|
||||||
|
"ai_create_location": "AI Create Location",
|
||||||
|
"reference_to_character": "Reference to Character",
|
||||||
|
"character_profile_confirm": "Confirm Character Profile",
|
||||||
|
"character_profile_batch_confirm": "Batch Confirm Character Profiles",
|
||||||
|
"episode_split_llm": "Episode Split",
|
||||||
|
"asset_hub_ai_design_character": "Asset Hub AI Design Character",
|
||||||
|
"asset_hub_ai_design_location": "Asset Hub AI Design Location",
|
||||||
|
"asset_hub_ai_modify_character": "Asset Hub AI Modify Character",
|
||||||
|
"asset_hub_ai_modify_location": "Asset Hub AI Modify Location",
|
||||||
|
"asset_hub_reference_to_character": "Asset Hub Reference to Character",
|
||||||
|
"storyboard": "Storyboard",
|
||||||
|
"storyboard_candidate": "Storyboard Candidate",
|
||||||
|
"character": "Character Image",
|
||||||
|
"location": "Location Image",
|
||||||
|
"video": "Video",
|
||||||
|
"analyze": "Analysis",
|
||||||
|
"analyze_character": "Character Analysis",
|
||||||
|
"analyze_location": "Location Analysis",
|
||||||
|
"clips": "Clip Splitting",
|
||||||
|
"storyboard_text_plan": "Storyboard Planning",
|
||||||
|
"storyboard_text_detail": "Storyboard Detail",
|
||||||
|
"tts": "TTS",
|
||||||
|
"regenerate": "Regenerate",
|
||||||
|
"voice-generate": "Voice Generation",
|
||||||
|
"voice-design": "Voice Design",
|
||||||
|
"lip-sync": "Lip Sync"
|
||||||
|
}
|
||||||
|
}
|
||||||
136
messages/en/progress.json
Normal file
136
messages/en/progress.json
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
{
|
||||||
|
"analyzing": "Analyzing story structure...",
|
||||||
|
"splittingClips": "Splitting into clips...",
|
||||||
|
"convertingScreenplay": "Converting to screenplay...",
|
||||||
|
"submittingStoryboard": "Submitting storyboard...",
|
||||||
|
"step": "Step {current} of {total}",
|
||||||
|
"status": {
|
||||||
|
"completed": "Completed",
|
||||||
|
"failed": "Failed",
|
||||||
|
"processing": "Processing",
|
||||||
|
"queued": "Queued",
|
||||||
|
"pending": "Pending"
|
||||||
|
},
|
||||||
|
"stageCard": {
|
||||||
|
"stage": "Stage",
|
||||||
|
"realtimeStream": "Realtime Stream",
|
||||||
|
"currentStage": "Current Stage",
|
||||||
|
"outputTitle": "Live AI Output · {stage}",
|
||||||
|
"waitingModelOutput": "Waiting for model output...",
|
||||||
|
"reasoningNotProvided": "No reasoning was returned for this step"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"waitingExecution": "Waiting to start",
|
||||||
|
"taskCreated": "Task created",
|
||||||
|
"taskStarted": "Task started",
|
||||||
|
"taskCompleted": "Task completed",
|
||||||
|
"taskFailed": "Task failed",
|
||||||
|
"taskProcessing": "Task processing...",
|
||||||
|
"llm": {
|
||||||
|
"processing": "Model is processing...",
|
||||||
|
"output": "Model is generating output...",
|
||||||
|
"reasoning": "Model is reasoning...",
|
||||||
|
"completed": "Model output completed",
|
||||||
|
"failed": "Model output failed"
|
||||||
|
},
|
||||||
|
"stage": {
|
||||||
|
"llmSubmit": "Submitting model request",
|
||||||
|
"llmStreaming": "Model streaming output",
|
||||||
|
"llmFallbackNonStream": "Model fallback to non-stream mode",
|
||||||
|
"llmCompleted": "Model output completed",
|
||||||
|
"llmFailed": "Model output failed"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"taskType": {
|
||||||
|
"generic": "Task",
|
||||||
|
"imagePanel": "Storyboard image",
|
||||||
|
"imageCharacter": "Character image",
|
||||||
|
"imageLocation": "Location image",
|
||||||
|
"videoPanel": "Video generation",
|
||||||
|
"lipSync": "Lip sync",
|
||||||
|
"voiceLine": "Voice generation",
|
||||||
|
"voiceDesign": "Voice design",
|
||||||
|
"assetHubVoiceDesign": "Asset hub voice design",
|
||||||
|
"regenerateStoryboardText": "Regenerate storyboard text",
|
||||||
|
"insertPanel": "Insert storyboard panel",
|
||||||
|
"panelVariant": "Storyboard variant",
|
||||||
|
"modifyAssetImage": "Image edit",
|
||||||
|
"regenerateGroup": "Batch regenerate",
|
||||||
|
"assetHubImage": "Asset hub image",
|
||||||
|
"assetHubModify": "Asset hub edit",
|
||||||
|
"analyzeNovel": "Content analysis",
|
||||||
|
"storyToScriptRun": "Story to script",
|
||||||
|
"scriptToStoryboardRun": "Script to storyboard",
|
||||||
|
"clipsBuild": "Clip generation",
|
||||||
|
"screenplayConvert": "Screenplay conversion",
|
||||||
|
"voiceAnalyze": "Voice line analysis",
|
||||||
|
"analyzeGlobal": "Global analysis",
|
||||||
|
"aiModifyAppearance": "Character description modify",
|
||||||
|
"aiModifyLocation": "Location description modify",
|
||||||
|
"aiModifyShotPrompt": "Shot prompt modify",
|
||||||
|
"analyzeShotVariants": "Shot variant analysis",
|
||||||
|
"aiCreateCharacter": "Project character design",
|
||||||
|
"aiCreateLocation": "Project location design",
|
||||||
|
"referenceToCharacter": "Reference to character",
|
||||||
|
"characterProfileConfirm": "Character profile confirm",
|
||||||
|
"characterProfileBatchConfirm": "Character profile batch confirm",
|
||||||
|
"episodeSplitLlm": "Smart episode split",
|
||||||
|
"assetHubAiDesignCharacter": "Asset hub character design",
|
||||||
|
"assetHubAiDesignLocation": "Asset hub location design",
|
||||||
|
"assetHubAiModifyCharacter": "Asset hub character modify",
|
||||||
|
"assetHubAiModifyLocation": "Asset hub location modify",
|
||||||
|
"assetHubReferenceToCharacter": "Asset hub reference to character"
|
||||||
|
},
|
||||||
|
"stage": {
|
||||||
|
"received": "Task received",
|
||||||
|
"generateCharacterImage": "Generate character image",
|
||||||
|
"generateLocationImage": "Generate location image",
|
||||||
|
"generatePanelCandidate": "Generate panel candidate",
|
||||||
|
"generatePanelVideo": "Generate panel video",
|
||||||
|
"generateVoiceSubmit": "Submit voice task",
|
||||||
|
"generateVoicePersist": "Persist voice result",
|
||||||
|
"voiceDesignSubmit": "Submit voice design task",
|
||||||
|
"voiceDesignDone": "Voice design completed",
|
||||||
|
"submitLipSync": "Submit lip sync task",
|
||||||
|
"persistLipSync": "Persist lip sync result",
|
||||||
|
"storyboardClip": "Generate storyboard clip",
|
||||||
|
"regenerateStoryboardPrepare": "Prepare storyboard regeneration",
|
||||||
|
"regenerateStoryboardPersist": "Persist storyboard regeneration",
|
||||||
|
"storyToScriptPrepare": "Prepare story-to-script parameters",
|
||||||
|
"storyToScriptStep": "Execute story-to-script step",
|
||||||
|
"storyToScriptPersist": "Persist story-to-script output",
|
||||||
|
"storyToScriptPersistDone": "Story-to-script output persisted",
|
||||||
|
"scriptToStoryboardPrepare": "Prepare script-to-storyboard parameters",
|
||||||
|
"scriptToStoryboardStep": "Execute script-to-storyboard step",
|
||||||
|
"scriptToStoryboardPersist": "Persist script-to-storyboard output",
|
||||||
|
"scriptToStoryboardPersistDone": "Storyboard and voice output persisted",
|
||||||
|
"insertPanelGenerateText": "Generate inserted panel text",
|
||||||
|
"insertPanelPersist": "Persist inserted panel",
|
||||||
|
"pollingExternal": "Waiting for external service",
|
||||||
|
"enqueueFailed": "Task enqueue failed",
|
||||||
|
"llmProxySubmit": "Submit LLM task",
|
||||||
|
"llmProxyExecute": "Execute LLM task",
|
||||||
|
"llmProxyPersist": "Persist LLM result"
|
||||||
|
},
|
||||||
|
"runConsole": {
|
||||||
|
"storyToScript": "Story to Script",
|
||||||
|
"scriptToStoryboard": "Script to Storyboard",
|
||||||
|
"storyToScriptRunning": "Story→Script running",
|
||||||
|
"scriptToStoryboardRunning": "Script→Storyboard running",
|
||||||
|
"storyToScriptSubtitle": "Story To Script V2",
|
||||||
|
"scriptToStoryboardSubtitle": "Script To Storyboard V2",
|
||||||
|
"stop": "Stop",
|
||||||
|
"minimize": "Minimize"
|
||||||
|
},
|
||||||
|
"streamStep": {
|
||||||
|
"analyzeCharacters": "Analyze characters",
|
||||||
|
"analyzeLocations": "Analyze locations",
|
||||||
|
"splitClips": "Split clips",
|
||||||
|
"screenplayConversion": "Convert screenplay",
|
||||||
|
"storyboardPlan": "Plan storyboard",
|
||||||
|
"cinematographyRules": "Generate cinematography rules",
|
||||||
|
"actingDirection": "Generate acting direction",
|
||||||
|
"storyboardDetailRefine": "Refine storyboard details",
|
||||||
|
"voiceAnalyze": "Analyze voice lines"
|
||||||
|
}
|
||||||
|
}
|
||||||
7
messages/en/providerSection.json
Normal file
7
messages/en/providerSection.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"addProvider": "+ Add Provider",
|
||||||
|
"name": "Name",
|
||||||
|
"add": "Add",
|
||||||
|
"save": "Save",
|
||||||
|
"fillRequired": "Please fill in required fields"
|
||||||
|
}
|
||||||
70
messages/en/scriptView.json
Normal file
70
messages/en/scriptView.json
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
{
|
||||||
|
"title": "Script View",
|
||||||
|
"scriptBreakdown": "Script Breakdown",
|
||||||
|
"splitCount": "{count} clips split",
|
||||||
|
"noClips": "No clips yet, please generate from story view",
|
||||||
|
"segment": {
|
||||||
|
"title": "Clip {index}",
|
||||||
|
"selected": "(Selected)"
|
||||||
|
},
|
||||||
|
"inSceneAssets": "In-Scene Assets",
|
||||||
|
"currentSelected": "Selected: Clip {number}",
|
||||||
|
"assetView": {
|
||||||
|
"allClips": "All Clips",
|
||||||
|
"viewingClip": "Viewing Clip {number}"
|
||||||
|
},
|
||||||
|
"asset": {
|
||||||
|
"generateCharacter": "Click to generate character →",
|
||||||
|
"generateLocation": "Click to generate location →",
|
||||||
|
"removeCharacterConfirm": "Are you sure you want to remove this character from current clip?",
|
||||||
|
"removeLocationConfirm": "Are you sure you want to remove this location from current clip?",
|
||||||
|
"removeFromClip": "Remove from current clip",
|
||||||
|
"noAudio": "No audio",
|
||||||
|
"playing": "Playing",
|
||||||
|
"listen": "Listen",
|
||||||
|
"activeCharacters": "Active Characters",
|
||||||
|
"activeLocations": "Active Locations",
|
||||||
|
"selectCharacter": "Select character/appearance to add",
|
||||||
|
"selectLocation": "Select location to add",
|
||||||
|
"loadingAssets": "Loading assets...",
|
||||||
|
"appearanceCount": "{count} appearances",
|
||||||
|
"added": "Added",
|
||||||
|
"primary": "Primary",
|
||||||
|
"subAppearance": "Sub appearance",
|
||||||
|
"defaultAppearance": "Default appearance",
|
||||||
|
"clickToRemove": "Click to remove {name}",
|
||||||
|
"clickToAdd": "Click to add {name}"
|
||||||
|
},
|
||||||
|
"screenplay": {
|
||||||
|
"scene": "Scene {number}",
|
||||||
|
"location": "Location:",
|
||||||
|
"locationTime": "Time:",
|
||||||
|
"day": "Day",
|
||||||
|
"night": "Night",
|
||||||
|
"dawn": "Dawn",
|
||||||
|
"dusk": "Dusk",
|
||||||
|
"dialogue": "Dialogue",
|
||||||
|
"action": "Action",
|
||||||
|
"narration": "Narration",
|
||||||
|
"content": "Original Content",
|
||||||
|
"noContent": "No content yet",
|
||||||
|
"clickToEdit": "Click to edit",
|
||||||
|
"interior": "INT",
|
||||||
|
"exterior": "EXT",
|
||||||
|
"characters": "Characters",
|
||||||
|
"noCharacter": "No character info",
|
||||||
|
"noLocation": "No active locations",
|
||||||
|
"noCharacterInClip": "No active characters"
|
||||||
|
},
|
||||||
|
"confirm": {
|
||||||
|
"removeCharacter": "Are you sure you want to remove this character from current clip?",
|
||||||
|
"removeLocation": "Are you sure you want to remove this location from current clip?"
|
||||||
|
},
|
||||||
|
"generate": {
|
||||||
|
"missingAssets": "{count} assets missing images",
|
||||||
|
"missingAssetsTip": "Please generate images for all characters and locations in",
|
||||||
|
"missingAssetsTipLink": "first",
|
||||||
|
"generating": "Generating...",
|
||||||
|
"startGenerate": "Confirm and Start Drawing →"
|
||||||
|
}
|
||||||
|
}
|
||||||
168
messages/en/smartImport.json
Normal file
168
messages/en/smartImport.json
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
{
|
||||||
|
"title": "Start Your Creative Journey",
|
||||||
|
"subtitle": "First, choose your creation method",
|
||||||
|
"manualCreate": {
|
||||||
|
"title": "Create from Episode 1",
|
||||||
|
"description": "Start from episode 1, suitable for episodic creation or single short videos",
|
||||||
|
"button": "Start Creating"
|
||||||
|
},
|
||||||
|
"manualDesc": "Start from the first episode, suitable for serialized or single short video production",
|
||||||
|
"startCreate": "Start Creating",
|
||||||
|
"smartImport": {
|
||||||
|
"title": "Smart Text Split",
|
||||||
|
"description": "Upload a complete novel or script, AI engine automatically recognizes chapter structure and splits into episodes.",
|
||||||
|
"button": "Import Now",
|
||||||
|
"recommended": "Recommended"
|
||||||
|
},
|
||||||
|
"markerDetection": {
|
||||||
|
"enable": "Use Markers (Episode X / 第X集)",
|
||||||
|
"tooltip": "Auto-detect [Episode X], [Chapter X], [第X集/章] markers, free & fast"
|
||||||
|
},
|
||||||
|
"smartImportDesc": "Upload your novel or script, AI engine automatically identifies chapter structure for one-click smart episode splitting.",
|
||||||
|
"recommended": "Recommended",
|
||||||
|
"importNow": "Import Now",
|
||||||
|
"uploadTitle": "Upload Source Material",
|
||||||
|
"uploadSubtitle": "AI engine ready, one-click auto-split and format",
|
||||||
|
"maxWords": "Max 30,000 words",
|
||||||
|
"textInput": "Enter Text Content",
|
||||||
|
"textPlaceholder": "Paste your novel chapter or script content here...",
|
||||||
|
"uploadDoc": "Upload Complete Document",
|
||||||
|
"clickUpload": "Click to Upload",
|
||||||
|
"clearText": "Please clear left text first",
|
||||||
|
"supportFormat": "Supports Word, TXT formats",
|
||||||
|
"fileMax": "Max 30,000 words",
|
||||||
|
"words": "words",
|
||||||
|
"startAnalyzing": "Start Analysis",
|
||||||
|
"analyzing": {
|
||||||
|
"title": "AI is Analyzing Your Story",
|
||||||
|
"description": "Recognizing chapter structure, smart splitting in progress...",
|
||||||
|
"autoSave": "Will auto-save after analysis complete"
|
||||||
|
},
|
||||||
|
"analyzingDesc": "Identifying chapter structure, smart splitting...",
|
||||||
|
"autoSave": "Will auto-save after analysis",
|
||||||
|
"splitComplete": "Smart Split Complete",
|
||||||
|
"splitResult": "Auto-split into {count} episodes, total {words} words",
|
||||||
|
"saved": "Auto-saved",
|
||||||
|
"reAnalyze": "Re-analyze",
|
||||||
|
"confirmComplete": "Confirm Complete",
|
||||||
|
"saving": "Saving...",
|
||||||
|
"episodeList": "Episode List",
|
||||||
|
"episodes": "episodes",
|
||||||
|
"episode": "Episode {num}",
|
||||||
|
"addEpisode": "Add Episode",
|
||||||
|
"newEpisode": "New Episode",
|
||||||
|
"avgWords": "Average per episode",
|
||||||
|
"episodeContent": "Episode Content",
|
||||||
|
"plotSummary": "Plot Summary",
|
||||||
|
"enterTitle": "Enter episode title...",
|
||||||
|
"enterSummary": "Enter plot summary...",
|
||||||
|
"confirmDelete": "Confirm Delete",
|
||||||
|
"deleteConfirmMsg": "Are you sure you want to delete \"{title}\"?",
|
||||||
|
"preview": {
|
||||||
|
"title": "Smart Splitting Complete",
|
||||||
|
"episodeCount": "Automatically split into {count} episodes",
|
||||||
|
"totalWords": "Total {count} words",
|
||||||
|
"autoSaved": "✓ Auto-saved",
|
||||||
|
"reanalyze": "Re-analyze",
|
||||||
|
"confirm": "Confirm Complete",
|
||||||
|
"saving": "Saving...",
|
||||||
|
"episodeList": "Episode List",
|
||||||
|
"addEpisode": "Add Episode",
|
||||||
|
"averageWords": "Average per episode",
|
||||||
|
"episodeContent": "Episode Content",
|
||||||
|
"episodePlaceholder": "Enter episode title...",
|
||||||
|
"summaryPlaceholder": "Enter plot summary...",
|
||||||
|
"newEpisode": "New Episode",
|
||||||
|
"deleteEpisode": "Delete Episode",
|
||||||
|
"deleteConfirm": {
|
||||||
|
"title": "Confirm Delete",
|
||||||
|
"message": "Are you sure you want to delete \"{title}\"?",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"confirm": "Confirm Delete"
|
||||||
|
},
|
||||||
|
"tip": {
|
||||||
|
"title": "Tip",
|
||||||
|
"content": "You can directly edit titles, summaries, and content. After clicking [Confirm Complete], episodes will be officially imported into the project"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"collapsePreview": "Collapse Preview",
|
||||||
|
"expandMore": "Expand More",
|
||||||
|
"deleteFile": "Delete File",
|
||||||
|
"fileTooLarge": "File size cannot exceed 10MB",
|
||||||
|
"docNotSupported": ".doc format not supported. Please save as .docx or .txt and try again",
|
||||||
|
"fileEmpty": "File content is empty",
|
||||||
|
"fileReadError": "File read failed, please ensure correct format",
|
||||||
|
"uploadFirst": "Please upload a file or paste text first",
|
||||||
|
"analyzeFailed": "Analysis failed",
|
||||||
|
"saveFailed": "Save failed",
|
||||||
|
"cancelConfirm": "Are you sure you want to cancel? Analyzed episodes will be cleared.",
|
||||||
|
"deleteEpisode": "Delete Episode",
|
||||||
|
"upload": {
|
||||||
|
"title": "Upload Raw Material",
|
||||||
|
"subtitle": "AI engine is ready, automatic episode splitting and formatting",
|
||||||
|
"maxWords": "(Max 30,000 words)",
|
||||||
|
"textInput": "Enter Text Content",
|
||||||
|
"documentUpload": "Upload Full Document",
|
||||||
|
"placeholder": "Paste your novel chapters or script content here...",
|
||||||
|
"filePlaceholder": "File uploaded mode",
|
||||||
|
"clickUpload": "Click to upload document",
|
||||||
|
"clearTextFirst": "Please clear left text first",
|
||||||
|
"supportedFormats": "Supports Word, TXT formats",
|
||||||
|
"preview": "Preview",
|
||||||
|
"expandPreview": "Expand More",
|
||||||
|
"collapsePreview": "Collapse",
|
||||||
|
"deleteFile": "Delete File",
|
||||||
|
"startAnalysis": "Start Analysis",
|
||||||
|
"back": "Back",
|
||||||
|
"words": "words"
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"fileTooLarge": "File too large, please upload a file smaller than 10MB",
|
||||||
|
"docNotSupported": ".doc format not supported, please convert to .docx in Word",
|
||||||
|
"fileEmpty": "File content is empty",
|
||||||
|
"fileReadError": "File read failed, please try again",
|
||||||
|
"uploadFirst": "Please upload or paste content first",
|
||||||
|
"analyzeFailed": "Analysis failed",
|
||||||
|
"saveFailed": "Save failed",
|
||||||
|
"analysisModelNotConfigured": "Please configure an analysis model in settings first"
|
||||||
|
},
|
||||||
|
"common": {
|
||||||
|
"edit": "Edit",
|
||||||
|
"delete": "Delete",
|
||||||
|
"save": "Save",
|
||||||
|
"cancel": "Cancel"
|
||||||
|
},
|
||||||
|
"markerDetected": {
|
||||||
|
"title": "Episode Markers Detected",
|
||||||
|
"description": "Detected {count} \"{type}\" format episode markers",
|
||||||
|
"preview": "Preview Split Result",
|
||||||
|
"useMarker": "Use Marker Split",
|
||||||
|
"useMarkerDesc": "Fast & Free",
|
||||||
|
"useAI": "Use AI Smart Split",
|
||||||
|
"useAIDesc": "Intelligent analysis, uses credits",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"totalCount": "{count} episodes total",
|
||||||
|
"markerTypes": {
|
||||||
|
"episode": "Episode X (Chinese)",
|
||||||
|
"chapter": "Chapter X (Chinese)",
|
||||||
|
"act": "Act X (Chinese)",
|
||||||
|
"scene": "X-Y [Scene]",
|
||||||
|
"numbered": "Numbered",
|
||||||
|
"numberedEscaped": "Numbered (Escaped)",
|
||||||
|
"numberedDirect": "Number + Chinese",
|
||||||
|
"episodeEn": "Episode X",
|
||||||
|
"chapterEn": "Chapter X",
|
||||||
|
"boldNumber": "**Number**",
|
||||||
|
"pureNumber": "Pure Number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"globalAnalysis": {
|
||||||
|
"title": "Global Asset Analysis",
|
||||||
|
"description": "Extract all characters and locations from the full book to ensure consistency across episodes",
|
||||||
|
"startButton": "Analyze Now",
|
||||||
|
"analyzing": "Analyzing...",
|
||||||
|
"success": "Analysis complete: {characters} new characters, {locations} new locations",
|
||||||
|
"failed": "Global analysis failed",
|
||||||
|
"confirmAndAnalyze": "Confirm & Analyze Assets"
|
||||||
|
}
|
||||||
|
}
|
||||||
7
messages/en/stages.json
Normal file
7
messages/en/stages.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"config": "1. Config",
|
||||||
|
"assets": "2. Asset Analysis",
|
||||||
|
"storyboard": "3. Storyboard Edit",
|
||||||
|
"videos": "4. Video Generation",
|
||||||
|
"voice": "5. Voice Generation"
|
||||||
|
}
|
||||||
374
messages/en/storyboard.json
Normal file
374
messages/en/storyboard.json
Normal file
@@ -0,0 +1,374 @@
|
|||||||
|
{
|
||||||
|
"phases": {
|
||||||
|
"planning": "Planning Storyboard",
|
||||||
|
"cinematography": "Cinematography Design",
|
||||||
|
"acting": "Acting Direction",
|
||||||
|
"detail": "Adding Details"
|
||||||
|
},
|
||||||
|
"prompts": {
|
||||||
|
"imagePrompt": "Image Prompt",
|
||||||
|
"aiInstruction": "AI Modify Instruction",
|
||||||
|
"supportReference": "(Support @ referencing asset library)",
|
||||||
|
"instructionPlaceholder": "e.g. Change location to @Hospital_Day, character to @ProtagonistA",
|
||||||
|
"selectAsset": "Select Asset",
|
||||||
|
"character": "Character",
|
||||||
|
"location": "Location",
|
||||||
|
"referencedAssets": "Referenced Assets:",
|
||||||
|
"removeAsset": "Remove Asset",
|
||||||
|
"aiModify": "AI Modify & Generate",
|
||||||
|
"aiModifying": "Modifying...",
|
||||||
|
"aiModifyTip": "Click to auto-save prompt and generate new image",
|
||||||
|
"save": "Save",
|
||||||
|
"currentPrompt": "Current Prompt",
|
||||||
|
"enterInstruction": "Please enter instruction",
|
||||||
|
"modifyFailed": "Operation Failed: {error}",
|
||||||
|
"updateFailed": "Update Failed: {error}",
|
||||||
|
"enterContinuation": "Please enter content to append",
|
||||||
|
"appendTitle": "Continue Content",
|
||||||
|
"appendDescription": "Enter new SRT content. The system will split and generate new shots, then append them to the end.",
|
||||||
|
"appendSubmit": "Append and Generate Shots",
|
||||||
|
"appendSuccess": "Append succeeded. New shots were added to the end of the list.",
|
||||||
|
"appendFailed": "Append failed: {error}",
|
||||||
|
"customStyle": "Custom Style"
|
||||||
|
},
|
||||||
|
"group": {
|
||||||
|
"generating": "Generating...",
|
||||||
|
"hasSynced": "✓ Generated",
|
||||||
|
"failed": "Failed",
|
||||||
|
"retry": "Retry",
|
||||||
|
"regenerate": "Regenerate All",
|
||||||
|
"generateAll": "Generate All",
|
||||||
|
"expand": "Expand",
|
||||||
|
"collapse": "Collapse",
|
||||||
|
"addPanel": "Add Panel",
|
||||||
|
"regenerating": "Regenerating...",
|
||||||
|
"aiAnalyzing": "AI Analyzing...",
|
||||||
|
"regenerateText": "Regenerate Text",
|
||||||
|
"generateMissingImages": "Generate all panels without images in this segment",
|
||||||
|
"segment": "Segment",
|
||||||
|
"addAtStart": "Add new storyboard group at the start",
|
||||||
|
"insertHere": "Insert new storyboard group here"
|
||||||
|
},
|
||||||
|
"header": {
|
||||||
|
"title": "Storyboard Editing",
|
||||||
|
"panels": "Panels",
|
||||||
|
"submit": "Submit Generation",
|
||||||
|
"submitting": "Submitting...",
|
||||||
|
"storyboardPanel": "Storyboard Panel",
|
||||||
|
"segments": "segments",
|
||||||
|
"segmentsCount": "Total {count} segments,",
|
||||||
|
"panelsCount": "{count} panels",
|
||||||
|
"generatingStatus": "({count} generating)",
|
||||||
|
"generateAllPanels": "Generate All Panels",
|
||||||
|
"generatePendingPanels": "Generate {count} panels without images",
|
||||||
|
"downloadAll": "Download All",
|
||||||
|
"downloading": "Packing...",
|
||||||
|
"noImages": "No images to download",
|
||||||
|
"downloadAllImages": "Download all images",
|
||||||
|
"generateVideo": "Generate Video →",
|
||||||
|
"back": "← Back",
|
||||||
|
"concurrencyLimit": "Concurrency limit {count}"
|
||||||
|
},
|
||||||
|
"panel": {
|
||||||
|
"shotType": "Shot Type:",
|
||||||
|
"duration": "seconds",
|
||||||
|
"location": "Location:",
|
||||||
|
"characters": "Characters:",
|
||||||
|
"description": "Description:",
|
||||||
|
"text": "Corresponding Text:",
|
||||||
|
"regenerate": "Regenerate",
|
||||||
|
"delete": "Delete",
|
||||||
|
"insertBefore": "Insert Before",
|
||||||
|
"insertAfter": "Insert After",
|
||||||
|
"moveUp": "Move Up",
|
||||||
|
"moveDown": "Move Down",
|
||||||
|
"plot": "Plot:",
|
||||||
|
"summary": "Summary:",
|
||||||
|
"pov": "POV:",
|
||||||
|
"focus": "Focus:",
|
||||||
|
"mode": "Mode:",
|
||||||
|
"shot": "Shot",
|
||||||
|
"segment": "Segment",
|
||||||
|
"stylePrompt": "Style/Prompt",
|
||||||
|
"shotMode": "Shot/Mode",
|
||||||
|
"regenerateImage": "Regenerate Image",
|
||||||
|
"generateImage": "Generate Image",
|
||||||
|
"cardView": "Card View",
|
||||||
|
"tableView": "Table View",
|
||||||
|
"shotTypeLabel": "Shot Type",
|
||||||
|
"cameraMove": "Camera Move",
|
||||||
|
"sourceText": "Source Text",
|
||||||
|
"sceneDescription": "Scene Description",
|
||||||
|
"videoPrompt": "Video Prompt",
|
||||||
|
"videoPromptHint": "Describe subject movement, environment, and camera language",
|
||||||
|
"locationLabel": "Location",
|
||||||
|
"editLocation": "Edit Location",
|
||||||
|
"characterLabel": "Character",
|
||||||
|
"characterLabelWithCount": "Characters ({count})",
|
||||||
|
"editCharacter": "Edit Characters",
|
||||||
|
"select": "+ Select",
|
||||||
|
"add": "+ Add",
|
||||||
|
"noLocation": "No location selected",
|
||||||
|
"locationNotEdited": "Location not edited yet",
|
||||||
|
"noCharacters": "No characters selected",
|
||||||
|
"charactersNotEdited": "Characters not edited yet",
|
||||||
|
"shotTypePlaceholder": "Overhead medium shot...",
|
||||||
|
"cameraMovePlaceholder": "Slow push, static...",
|
||||||
|
"videoPromptPlaceholder": "Prompt for video generation...",
|
||||||
|
"sceneDescriptionPlaceholder": "Describe subject, composition, lighting, and mood",
|
||||||
|
"selectCharacter": "Select Character",
|
||||||
|
"selectLocation": "Select Location",
|
||||||
|
"noCharacterAssets": "No character assets",
|
||||||
|
"noLocationAssets": "No location assets",
|
||||||
|
"selected": "Selected",
|
||||||
|
"defaultAppearance": "Default appearance",
|
||||||
|
"newPanelDescription": "New shot description",
|
||||||
|
"noShotType": "Shot type not set"
|
||||||
|
},
|
||||||
|
"image": {
|
||||||
|
"generating": "Generating...",
|
||||||
|
"regenerate": "Regenerate",
|
||||||
|
"edit": "Edit",
|
||||||
|
"editImage": "Edit Image",
|
||||||
|
"candidate": "Candidate",
|
||||||
|
"selectCandidate": "Select Candidate",
|
||||||
|
"variants": "Variants",
|
||||||
|
"generateVariants": "Generate Variants",
|
||||||
|
"forceRegenerate": "Force Regenerate",
|
||||||
|
"failed": "Generation Failed",
|
||||||
|
"clickToPreview": "Click to preview",
|
||||||
|
"enlargePreview": "Enlarge Preview",
|
||||||
|
"candidateCount": "Candidate {count}",
|
||||||
|
"candidateGenerating": "{count} generating",
|
||||||
|
"selectingCandidate": "Selecting candidate...",
|
||||||
|
"confirmCandidate": "Confirm Selection",
|
||||||
|
"cancelSelection": "Cancel Selection",
|
||||||
|
"noValidCandidates": "No valid candidates",
|
||||||
|
"selectCount": "Select count",
|
||||||
|
"generateMultiple": "Generate multiple candidates",
|
||||||
|
"generateCount": "Generate {count}",
|
||||||
|
"undoShort": "Back"
|
||||||
|
},
|
||||||
|
"candidate": {
|
||||||
|
"title": "Select Candidate Image",
|
||||||
|
"select": "Select",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"noImages": "No candidate images",
|
||||||
|
"original": "Original"
|
||||||
|
},
|
||||||
|
"variant": {
|
||||||
|
"title": "Image Variants",
|
||||||
|
"generate": "Generate Variants",
|
||||||
|
"select": "Use This Image",
|
||||||
|
"close": "Close",
|
||||||
|
"shotTitle": "Shot Variant - Based on #{number}",
|
||||||
|
"originalDescription": "Original Shot Description",
|
||||||
|
"noDescription": "No description",
|
||||||
|
"noImage": "No image",
|
||||||
|
"shotNum": "Shot {number}",
|
||||||
|
"aiRecommend": "AI Recommended Variants",
|
||||||
|
"reanalyze": "Re-analyze",
|
||||||
|
"shotType": "Shot type:",
|
||||||
|
"cameraMove": "Camera move:",
|
||||||
|
"generating": "Generating",
|
||||||
|
"clickToAnalyze": "Click Re-analyze to get AI recommendations",
|
||||||
|
"customInstruction": "Or custom instruction",
|
||||||
|
"customPlaceholder": "Enter the shot effect you want, e.g.: switch to reverse shot, focus on another character's expression...",
|
||||||
|
"includeCharacter": "Include character reference",
|
||||||
|
"includeLocation": "Include location reference",
|
||||||
|
"customVariant": "Custom variant",
|
||||||
|
"defaultShotType": "Medium Shot",
|
||||||
|
"defaultCameraMove": "Static",
|
||||||
|
"useCustomGenerate": "Generate with custom",
|
||||||
|
"analyzeFailed": "Analysis failed",
|
||||||
|
"creativeScore": "Creativity {score}/5"
|
||||||
|
},
|
||||||
|
"insert": {
|
||||||
|
"title": "Insert New Panel",
|
||||||
|
"position": "Insert Position",
|
||||||
|
"before": "Before Panel {number}",
|
||||||
|
"after": "After Panel {number}",
|
||||||
|
"content": "Panel Content",
|
||||||
|
"shotType": "Shot Type",
|
||||||
|
"location": "Location",
|
||||||
|
"characters": "Characters",
|
||||||
|
"description": "Description",
|
||||||
|
"text": "Corresponding Text",
|
||||||
|
"placeholder": {
|
||||||
|
"shotType": "Select shot type...",
|
||||||
|
"location": "Enter location...",
|
||||||
|
"characters": "Enter characters, comma separated",
|
||||||
|
"description": "Describe the scene...",
|
||||||
|
"text": "Corresponding script text..."
|
||||||
|
},
|
||||||
|
"insert": "Insert",
|
||||||
|
"cancel": "Cancel"
|
||||||
|
},
|
||||||
|
"common": {
|
||||||
|
"actions": "Actions",
|
||||||
|
"add": "Add",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"confirm": "Confirm",
|
||||||
|
"copy": "Copy",
|
||||||
|
"delete": "Delete",
|
||||||
|
"download": "Download",
|
||||||
|
"edit": "Edit",
|
||||||
|
"generate": "Generate",
|
||||||
|
"loading": "Loading...",
|
||||||
|
"none": "None",
|
||||||
|
"unknownError": "Unknown error",
|
||||||
|
"preview": "Preview",
|
||||||
|
"refresh": "Refresh",
|
||||||
|
"regenerate": "Regenerate",
|
||||||
|
"deleting": "Deleting",
|
||||||
|
"editing": "Editing",
|
||||||
|
"saving": "Saving...",
|
||||||
|
"saveFailed": "Save failed, changes not synced",
|
||||||
|
"retrySave": "Retry save",
|
||||||
|
"save": "Save",
|
||||||
|
"status": "Status",
|
||||||
|
"submitFailed": "Submit Failed",
|
||||||
|
"upload": "Upload"
|
||||||
|
},
|
||||||
|
"confirm": {
|
||||||
|
"deletePanel": "Delete this shot? This action cannot be undone.",
|
||||||
|
"deleteGroup": "Delete this storyboard group? This will remove all {count} shots in this segment. This action cannot be undone."
|
||||||
|
},
|
||||||
|
"messages": {
|
||||||
|
"episodeNotFound": "Episode information not found",
|
||||||
|
"downloadFailed": "Download failed: {error}",
|
||||||
|
"panelNotFound": "Shot information not found",
|
||||||
|
"modifyFailed": "Modify failed: {error}",
|
||||||
|
"selectCandidateFailed": "Select candidate failed: {error}",
|
||||||
|
"insertPanelFailed": "Insert shot failed: {error}",
|
||||||
|
"addPanelFailed": "Add shot failed: {error}",
|
||||||
|
"deletePanelFailed": "Delete shot failed: {error}",
|
||||||
|
"deleteGroupFailed": "Delete storyboard group failed: {error}",
|
||||||
|
"regenerateGroupFailed": "Regenerate storyboard failed: {error}",
|
||||||
|
"addGroupFailed": "Add storyboard group failed: {error}",
|
||||||
|
"moveGroupFailed": "Move storyboard group failed: {error}",
|
||||||
|
"batchGenerateCompleted": "Batch generation completed:\nSucceeded: {succeeded}\nFailed: {failed}\n\nSample errors: {errors}",
|
||||||
|
"batchGenerateFailed": "Batch generation failed: {error}"
|
||||||
|
},
|
||||||
|
"canvas": {
|
||||||
|
"emptyTitle": "No storyboard data yet",
|
||||||
|
"emptyDescription": "Generate clips and storyboard text first, or add a storyboard group above"
|
||||||
|
},
|
||||||
|
"imageEdit": {
|
||||||
|
"title": "Edit Storyboard Image",
|
||||||
|
"subtitle": "Enter a modify instruction and optionally upload reference images or assets",
|
||||||
|
"promptPlaceholder": "Describe what to modify, e.g. change background color or adjust expression...",
|
||||||
|
"referenceImagesLabel": "Reference Images",
|
||||||
|
"referenceImagesHint": "(optional, paste supported)",
|
||||||
|
"start": "Start Editing",
|
||||||
|
"selectAsset": "Select Assets",
|
||||||
|
"selectedAssetsLabel": "Referenced Assets",
|
||||||
|
"selectedAssetsCount": "{count}",
|
||||||
|
"addAsset": "Add Asset",
|
||||||
|
"noAssets": "No assets selected. Click \"Add Asset\" to choose."
|
||||||
|
},
|
||||||
|
"screenplay": {
|
||||||
|
"tabs": {
|
||||||
|
"formatted": "Screenplay",
|
||||||
|
"original": "Original"
|
||||||
|
},
|
||||||
|
"scene": "Scene {number}",
|
||||||
|
"characters": "Characters",
|
||||||
|
"voiceover": "Voiceover",
|
||||||
|
"parseFailedTitle": "Failed to parse screenplay format",
|
||||||
|
"parseFailedDescription": "Please check the original content"
|
||||||
|
},
|
||||||
|
"assets": {
|
||||||
|
"character": {
|
||||||
|
"confirming": "Confirming...",
|
||||||
|
"editing": "Editing..."
|
||||||
|
},
|
||||||
|
"image": {
|
||||||
|
"undo": "Undo to Previous Version"
|
||||||
|
},
|
||||||
|
"location": {
|
||||||
|
"generateImage": "Generate Image"
|
||||||
|
},
|
||||||
|
"stage": {
|
||||||
|
"analyzing": "Analyzing..."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"video": {
|
||||||
|
"toolbar": {
|
||||||
|
"showPending": "Pending"
|
||||||
|
},
|
||||||
|
"panelCard": {
|
||||||
|
"forceRegenerate": "Force Regenerate (if stuck)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"smartImport": {
|
||||||
|
"errors": {
|
||||||
|
"analyzeFailed": "Analysis Failed"
|
||||||
|
},
|
||||||
|
"preview": {
|
||||||
|
"reanalyze": "Re-analyze"
|
||||||
|
},
|
||||||
|
"smartImport": {
|
||||||
|
"recommended": "Recommended"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"aiData": {
|
||||||
|
"title": "AI Data Editor",
|
||||||
|
"subtitle": "Panel {number} - Complete data sent to image generation AI",
|
||||||
|
"basicData": "Storyboard Basic Data",
|
||||||
|
"shotType": "Shot Type",
|
||||||
|
"cameraMove": "Camera Movement",
|
||||||
|
"shotTypePlaceholder": "Overhead, wide shot, eye-level, medium shot...",
|
||||||
|
"cameraMovePlaceholder": "Slow push, static, follow...",
|
||||||
|
"scene": "Scene (Read-only)",
|
||||||
|
"notSelected": "Not selected",
|
||||||
|
"summary": "Scene Summary",
|
||||||
|
"characters": "Characters (Read-only)",
|
||||||
|
"plot": "Plot",
|
||||||
|
"summarize": "Summary",
|
||||||
|
"visualDescription": "Visual Description",
|
||||||
|
"videoPrompt": "Video Prompt",
|
||||||
|
"negativePrompt": "Negative Prompt",
|
||||||
|
"save": "Save",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"lightingDirection": "Lighting Direction",
|
||||||
|
"lightingQuality": "Lighting Quality",
|
||||||
|
"depthOfField": "Depth of Field",
|
||||||
|
"colorTone": "Color Tone",
|
||||||
|
"characterPosition": "Character Position Rules",
|
||||||
|
"position": "Position",
|
||||||
|
"posture": "Posture",
|
||||||
|
"facing": "Facing",
|
||||||
|
"photographyRules": "Photography Rules",
|
||||||
|
"viewData": "View Data",
|
||||||
|
"jsonPreview": "JSON Preview",
|
||||||
|
"actingNotes": "Acting Direction (acting_notes)",
|
||||||
|
"actingTitle": "Acting Direction",
|
||||||
|
"actingDescription": "Performance Notes",
|
||||||
|
"noActingData": "No acting data"
|
||||||
|
},
|
||||||
|
"insertModal": {
|
||||||
|
"insertBetween": "Insert between #{before} and #{after}",
|
||||||
|
"panel": "Panel",
|
||||||
|
"noImage": "No image",
|
||||||
|
"insertAtEnd": "End",
|
||||||
|
"aiAnalyze": "AI Auto-analyze",
|
||||||
|
"analyzing": "AI analyzing...",
|
||||||
|
"insert": "Insert",
|
||||||
|
"inserting": "Inserting...",
|
||||||
|
"placeholder": "Optional: Add notes, e.g. add a reaction shot..."
|
||||||
|
},
|
||||||
|
"panelActions": {
|
||||||
|
"insertPanel": "Insert Panel",
|
||||||
|
"panelVariant": "Panel Variant",
|
||||||
|
"insertHere": "Insert panel here",
|
||||||
|
"generateVariant": "Generate variant based on this panel",
|
||||||
|
"needImage": "Need to generate image first",
|
||||||
|
"deleteShot": "Delete Shot",
|
||||||
|
"pasteSrtPlaceholder": "Paste new SRT content..."
|
||||||
|
},
|
||||||
|
"firstLastFrame": {
|
||||||
|
"placeholder": "Enter first/last frame video prompt...",
|
||||||
|
"modelTitle": "First/Last Frame Model"
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user