- 前端负责创建任务、展示任务状态、展示历史记录、取消任务和预览结果。
- 后端负责 OpenAI 请求转发、密钥管理、任务执行和本地持久化。
- 生产环境使用一个端口,同时提供前端静态资源和数据 API。
- 图片生成可能耗时较长,因此后端使用可取消的异步任务,前端不直接等待远程请求完成。
Vue 前端
-> /api/tasks
-> /api/tasks/edit
-> /api/tasks/animation
-> /api/tasks/codex-pet
-> /api/config/public
-> /api/assets/{asset_id}
Python FastAPI 后端
-> SQLite 任务数据库
-> 本地图片文件
-> Provider Token 数据库
-> OpenAI-compatible Images API
-> dist/ 静态文件托管
backend/app/config.py 环境变量和路径配置
backend/app/auth.py 登录、注册、密码修改、管理员改名、管理员重置密码、token 签发和失效校验
backend/app/models.py API schema 和任务状态定义
backend/app/storage.py SQLite 持久化和资源路径校验
backend/app/workflows.py 工作流 JSON 模板保存、读取、删除
backend/app/animation.py 连续帧动画任务的帧 prompt、manifest 和参考图策略
backend/app/codex_pet.py Codex Pet row-strip prompt、layout guide、切帧和 atlas 打包
backend/app/pet_sprite_artifacts.py 桌宠预览、final 输出、manifest artifact 字段和 TaskOutput 重建
backend/app/openai_images.py OpenAI Images API 客户端
backend/app/task_manager.py 异步任务生命周期和取消逻辑
backend/app/provider_tokens.py Provider URL/Key 持久化、启停、测试和额度刷新
backend/app/main.py FastAPI 路由和前端静态文件托管
queued -> running -> succeeded
queued -> running -> failed
queued -> running -> cancelled
queued -> cancelled
取消任务采用本地优先策略。后端会取消当前 asyncio 任务,并关闭本地等待中的 HTTP 请求。如果 OpenAI 服务端已经接收并开始处理请求,后端无法保证远端算力一定停止,但本地任务状态、等待和资源占用会立即结束。
image.generate:文本生成图片,使用/images/generations。image.edit:图生图/文身图等图片编辑任务,使用/images/edits,支持上传图片、复用历史输出、mask 和多来源图。image.animation:连续帧动画任务。前端提交基础 prompt、参考图、参考策略和多条帧 prompt;后端创建一个父任务,按帧串行调用图片编辑接口,并把每帧输出保存为frame_001.png、frame_002.png等资源。pet.sprite:Codex Pet 桌宠任务。前端提交角色 prompt 和参考图;后端按 9 个动作行分别生成 row strip,再本地切帧、抠 chroma key、合成 1536x1872 spritesheet/contact sheet,并输出pet.json。
动画任务不是视频生成器,也不依赖外部桌宠工具。当前实现采用“连续图生图帧生成器”方案:第一帧使用用户参考图,后续帧根据 reference_mode 选择上一帧、原始参考图或二者混合,确保前端刷新后仍能从任务记录和 manifest 恢复状态。
前端提供独立“桌宠”任务入口用于完整 row-strip 打包;“动画”里的“桌宠模板”仍可作为动作草案快捷入口。具体操作见 docs/ANIMATION_TASK_GUIDE.md。
动画参考策略:
previous_frame:第一帧使用原始参考图,后续帧只使用上一帧输出,适合连续动作。reference:每一帧都使用原始参考图,适合保持身份但帧间连续性较弱。hybrid:后续帧同时使用上一帧和原始参考图,在连续性和身份稳定之间折中。
每个动画任务保存 params.animation manifest,并同步写入任务图片目录下的 animation_manifest.json。manifest 记录基础 prompt、参考策略、来源资源、帧数量和每帧的 queued/running/succeeded/failed 状态、开始时间、结束时间和耗时。任一帧失败时父任务进入 failed,已完成帧保留在输出列表和 manifest 中;取消时父任务进入 cancelled。全部帧成功后,后端会尝试生成 animation_preview.gif 并作为普通输出追加到任务结果中;旧动画任务也可通过 /api/tasks/{task_id}/animation-preview 用已有帧本地重新合成 GIF,不会重新调用远程生图。
动画任务支持从指定帧重新生成:POST /api/tasks/{task_id}/animation/rerun-from 会把指定帧及后续帧重置为 queued,保留之前成功帧,并按原任务保存的 reference_mode 继续执行。previous_frame 会使用指定帧前最后一张成功帧作为接力参考,reference 继续使用原始参考图,hybrid 继续同时使用上一帧和原始参考图。继续运行和重新生成完成后,后端会从 manifest 和本地文件系统重建完整输出列表,避免只显示本次继续运行生成的新帧。
每个桌宠任务保存 params.pet_sprite manifest,并同步写入任务图片目录下的 pet_sprite_manifest.json。任务目录会包含 pet_layout_guides/、pet_rows/、pet_frames/、pet_previews/ 和 pet_final/。远程生图按动作行并行执行,每行请求会附带用户参考图和对应 layout guide;row strip 使用专用 1536x1024 输出尺寸,并按动作帧数选择网格布局:8 帧为 4x2,6 帧为 3x2,5 帧为 3x2 且最后一格留空,4 帧为 2x2。本地后处理使用 chroma key 删除背景,并按该网格槽位切成 192x208 单元格。每行动作成功切帧后会本地合成 pet_previews/{state}.gif 循环预览,并写入对应 row 的 preview_asset_id 和 preview_duration_ms;不同动作使用不同默认帧时长,例如 idle/waiting 较慢、running 较快。layout guide 和提示词会要求每个槽位保留安全边距、居中摆放、相邻姿势之间保持纯背景隔离,并强调每个 pose 是独立贴纸而不是连续插画;头发、披风、武器、特效等都不能跨槽或贴边。新任务会根据参考图主色自动选择一个尽量不撞色的 chroma key,并写入 manifest 的 chroma_key 字段;行级重新生成会继续使用同一个颜色。成功输出包含 pet_final/spritesheet.png、pet_final/spritesheet.webp、pet_final/contact_sheet.png 和 pet_final/pet.json。其中 pet_final/pet.json 是桌宠运行器使用的配置清单,记录 pet id、显示名、描述和 spritesheet.webp 路径,不是图片预览;前端任务详情会把它显示在附件区。
桌宠任务支持行级重新生成:POST /api/tasks/{task_id}/pet-sprite/rows/{state}/rerun 会只重跑指定动作行,然后重新切帧、合成 spritesheet/contact sheet,并重写 pet.json。适合处理某一行动作不好、背景没有纯色或切割不理想的情况。
桌宠任务也支持从已有切帧本地重建动作 GIF:POST /api/tasks/{task_id}/pet-sprite/previews 会读取 pet_frames/*/*.png,生成 pet_previews/*.gif 并回写 manifest。该操作用于补齐旧任务或 GIF 文件丢失的任务,不会重新调用远程生图。
如果只需要从已有 pet_frames/*/*.png 重建 final 产物,可以调用 POST /api/tasks/{task_id}/pet-sprite/final。它会重新生成 spritesheet、contact sheet 和 pet.json,不会重新调用远程生图,也不会重建每行动作 GIF。
- SQLite 数据库:保存任务元数据、状态、参数、输出引用、时间戳、错误信息和
owner_user_id。 - 图片文件:普通生成/编辑结果保存到
data/images/{user_id}/{task_id}/result_*.png等路径;动画帧保存到同一任务目录的frame_001.*、frame_002.*等路径。 - 删除任务:
DELETE /api/tasks/{task_id}会在校验当前用户和任务状态后,永久删除该任务记录及data/images/{user_id}/{task_id}/;新删除不会复制到或移动到回收目录。旧版本遗留的recycle.sqlite3/recycle_images/只由管理员磁盘清理兼容处理,不是新删除的备份目标。 - 缩略图:每个图片输出会额外生成
_thumbs/*.webp小图,并在TaskOutput.thumbnail_asset_id中返回。任务列表优先加载缩略图,详情预览仍加载原图。旧任务没有缩略图字段时前端会回退到原图。 - 工作流模块:当前前端只保留“开发中”入口,暂不提供创建、运行和节点编辑。后续会作为独立大模块承载节点编排、右侧节点参数调整、步骤调试和多阶段任务流转。
- 认证状态:保存到
data/auth.json,保存用户名、用户 UUID、密码哈希、盐和 token 版本号,不保存明文密码。 - JWT 签名密钥:保存到
data/auth_secret.txt,由后端首次启动自动生成。 - 初始管理员密码:仅在缺少
data/auth.json时生成到data/admin_bootstrap_password.txt,随后以auth.json中的哈希为准。 - 前端访问:通过
/api/assets/{user_id}/{task_id}/image_1.png获取图片,后端按 HttpOnly Cookie 或 Authorization Bearer 的user_id校验访问权限;资源和 ZIP 下载接口不接受 URL 查询令牌。
服务启动时会把中断前仍处于 queued 或 running 的任务标记为 failed,因为进程重启后内存中的请求句柄无法恢复。
GET /api/health
GET /api/config/public
POST /api/auth/login
GET /api/auth/session
POST /api/auth/register
POST /api/auth/password
POST /api/auth/username # 保留兼容,始终返回 403
POST /api/auth/logout
GET /api/admin/overview
PATCH /api/admin/users/{username}
POST /api/admin/users/{username}/rename
POST /api/admin/users/{username}/reset-password
POST /api/admin/users/{username}/disk-cleanup
POST /api/admin/registration-keys
DELETE /api/admin/registration-keys/{key_id}
GET /api/admin/provider-tokens/public-key
POST /api/admin/provider-tokens
PATCH /api/admin/provider-tokens/{token_id}
PATCH /api/admin/settings
POST /api/admin/provider-tokens/{token_id}/test
POST /api/admin/provider-tokens/{token_id}/credits
POST /api/admin/provider-tokens/credits
GET /api/admin/provider-tokens/daily-usage
POST /api/tasks
POST /api/tasks/edit
POST /api/tasks/animation
POST /api/tasks/codex-pet
GET /api/tasks
GET /api/tasks/{task_id}
POST /api/tasks/{task_id}/cancel
POST /api/tasks/{task_id}/continue
POST /api/tasks/{task_id}/animation-preview
POST /api/tasks/{task_id}/animation/rerun-from
POST /api/tasks/{task_id}/pet-sprite/rows/{state}/rerun
POST /api/tasks/{task_id}/pet-sprite/previews
POST /api/tasks/{task_id}/pet-sprite/final
GET /api/tasks/{task_id}/download.zip
DELETE /api/tasks/{task_id}
GET /api/assets/{asset_id}
GET /api/workflows
POST /api/workflows
GET /api/workflows/{workflow_id}
PUT /api/workflows/{workflow_id}
DELETE /api/workflows/{workflow_id}
OPENAI_IMAGE_MODEL=gpt-image-2
GPT_IMAGE_DATA_DIR=backend/data
GPT_IMAGE_WORKFLOWS_DIR=backend/data/workflows
GPT_IMAGE_AUTH_STATE_PATH=backend/data/auth.json
GPT_IMAGE_AUTH_TOKEN_TTL_SECONDS=604800
GPT_IMAGE_REQUEST_TIMEOUT_SECONDS=0
GPT_IMAGE_MAX_CONCURRENT_TASKS=0
GPT_IMAGE_PET_SPRITE_MAX_CONCURRENT_ROWS=3
PORT=3100
GPT_IMAGE_REQUEST_TIMEOUT_SECONDS=0 表示后端不设置硬超时。即使不设置硬超时,用户仍然可以在前端点击取消任务。
服务商 Provider URL/Key 由管理员面板写入后端 Provider Token 数据库,不从 .env、启动脚本或前端代码读取。
Provider TOKEN 支持启用/禁用和并发上限。max_concurrent=0 表示不限,active_count 记录当前远程请求占用。任务执行前选择启用且未超过并发上限的 TOKEN,并优先选择占用数更低的条目;请求结束、失败或取消后释放占用。全局 GPT_IMAGE_MAX_CONCURRENT_TASKS=0 表示不额外限制,由各 TOKEN 的并发量负责限流。
GET /api/config/public 会返回可选模型和 model_capabilities,前端优先使用该结构决定模型可用尺寸、背景、moderation、input_fidelity、流式能力和 responses 多轮能力控件。当前模型列表包括 gpt-image-2、gpt-image-1.5、gpt-image-1 和 gpt-image-1-mini。后端仍保留兜底归一化,例如不支持透明背景的模型会把 transparent 降级为 auto。当前 gpt-image-2 不展示透明背景和保真度,但标记支持 streaming/responses multiturn;gpt-image-1 展示 low/high 保真度。
动画任务和桌宠任务的新增字段都写入现有 params_json,不会改变任务表结构。服务启动时 TaskStorage.initialize() 会创建缺失表/索引,并对老库执行轻量列检查,自动补齐 owner_user_id、source_image_asset_id 和 source_image_asset_ids_json 等历史兼容列;当前不需要单独手动执行数据库 schema 升级脚本。
任务输出缩略图保存为任务图片目录下的 _thumbs/*.webp,对应元数据写入现有 outputs_json 的可选字段,不新增列。新任务会自动写入缩略图字段;旧任务可执行 backend/backfill_thumbnails.py 回填。建议先运行 uv run --with-requirements backend/requirements.txt python backend/backfill_thumbnails.py --dry-run 查看影响范围,再运行不带 --dry-run 的命令写入。
资源下载接口 /api/assets/{asset_id} 使用 FastAPI FileResponse 从磁盘直出,并按当前用户校验 asset_id 所属目录。后端不会把点击过的图片长期保存在进程内存;可能增长的是浏览器自己的 HTTP 图片缓存和当前页面 DOM 中仍显示的图片解码缓存。列表页优先使用缩略图并开启 lazy loading,详情页和灯箱才加载原始图片。
动画任务内部按帧串行执行,因为后续帧可能依赖上一帧。桌宠任务的动作行彼此独立,可以在同一个父任务内并行执行;默认最多同时跑 3 行,可用 GPT_IMAGE_PET_SPRITE_MAX_CONCURRENT_ROWS 调整。整个父任务仍受全局 GPT_IMAGE_MAX_CONCURRENT_TASKS 限制;每个并行动作行发起远程请求前仍会占用 Provider TOKEN 并发池。这样可以提升桌宠生成速度,同时仍让管理员通过 TOKEN 并发、桌宠行并发和全局任务数三层控制后台压力。
独立额度查询页面位于 /credits。该页面由浏览器直接请求用户输入的 Provider URL 和 Key,不经过后端,也不会保存到 TOKEN 数据库。页面支持用本地口令把多个查询配置加密保存到当前浏览器的 localStorage;只保存 AES-GCM 密文,不保存解锁口令或明文 Key。若服务商未允许浏览器跨域请求,页面可能受到 CORS 限制。
用户只能修改自己的密码,不能修改自己的用户名。修改密码、管理员改名、管理员重置密码都会让对应用户 token 版本号加一,旧 token 会被 /api/* 和 /api/assets/* 统一拒绝。管理员可在管理面板修改普通用户用户名,也可为普通用户生成随机临时密码;临时密码只在重置响应中显示一次,管理员应通过安全渠道交付并建议用户立即修改。认证固定启用,不提供单用户免登录模式。管理员账号固定为 admin;首次缺少 data/auth.json 时,后端生成一次性初始密码到 data/admin_bootstrap_password.txt,并把哈希写入 data/auth.json。JWT 签名密钥持久化到 data/auth_secret.txt。登录令牌默认有效期为 604800 秒(7 天)。
当前正式前端的登录、注册和修改密码不发送用户输入的原始明文密码:前端使用 WebCrypto/PBKDF2 派生 password_secret,后端接收派生值,并再次使用服务端 PBKDF2 保存哈希。登录 schema 仍保留旧客户端的可选 password 字段,用于兼容历史账号迁移;新客户端不会使用它。派生值以及兼容字段都属于敏感登录凭据,若公网使用 HTTP 而不是 HTTPS,仍可能被窃取并重放;公网部署必须使用 HTTPS,本设计不能替代传输层加密。
前端构建使用官网版 Node.js / npm。Windows 下运行 build.bat 时脚本会先通过 where npm.cmd 查找 npm,再兜底查找 C:\Program Files\nodejs\npm.cmd 和 C:\Program Files (x86)\nodejs\npm.cmd。脚本用 npm 安装依赖,然后直接调用同目录的 node.exe 执行 node_modules/vite/bin/vite.js build,避免 Windows 下 npm shim 权限问题。手动构建时执行 npm install 和 node .\node_modules\vite\bin\vite.js build。