diff --git a/README.md b/README.md index 0d0d25b..fdc6228 100644 --- a/README.md +++ b/README.md @@ -193,7 +193,10 @@ Oxfmt 不负责代码质量,Oxlint 不负责 Vue 的完整类型推导;Vue S | GET | `/storyboard-shots/:shotId/videos` | 单镜头视频历史 | | POST | `/storyboard-shots/:shotId/videos/generate-quality` | 单镜头严格质量候选;后台自动校验与有限修复 | | PUT | `/storyboard-shots/:shotId/videos/:videoId/primary` | 切换主视频 | -| POST | `/projects/:id/production/start` | 高级串联入口;重复预检、费用确认,仅传两种 Provider,不生成首帧、不等待成片 | +| GET | `/projects/:id/production/plan` | 无消耗执行计划、分阶段数量与阻塞原因 | +| GET | `/projects/:id/production/status` | 真实主资产有效性与制作完成状态 | +| GET | `/storyboard-shots/:id/provider-input-spec?provider=...` | 指定模型的最终首帧与授权素材,只读检查 | +| POST | `/projects/:id/production/start` | 高级串联入口;按执行计划再次预检、费用确认,body 为 {},包含首帧质量流程,不等待异步视频完成 | API 层也提供 `state` 与 `breakdown/latest` 方法。页面通过共享 checkpoint 查询取得阶段状态,避免重复拉取同样内容。 @@ -327,3 +330,4 @@ server { 本轮新增测试覆盖风格 JSON/锁定/图片 DELETE、身份归并/前置条件/尺寸/显式参考图/母版切换、部分失败、旧响应、草稿保护与费用确认。模型调用均使用模拟 API。当前执行环境未能通过浏览器访问本地开发服务,**未完成浏览器视觉验收及真实模型联调**,请按上述步骤在本地验收。 自动测试使用模拟 API,覆盖既有流程、分镜接口与竞态,以及形态图片自动读取、正式 ID 生图、主图 PUT、费用确认、尺寸校验、候选图选择、批量部分失败和图片加载错误。不等同于真实模型、MySQL 或浏览器视觉联调。请勿用正式项目随意制造失败以测试恢复。 + diff --git a/docs/api-contract-sync.md b/docs/api-contract-sync.md index 18472be..3aee5e1 100644 --- a/docs/api-contract-sync.md +++ b/docs/api-contract-sync.md @@ -30,3 +30,17 @@ ## 回归验证范围 测试使用不包含旧内部字段的公开 DTO,覆盖页面加载、身份保存/选角、图片主图、Prompt 正式 ID、质量会话隔离、输入变体禁用、失败重试和缺失历史不误报过期。全部 API 使用模拟响应,不触发真实模型。此次不以自动化 DOM 测试代替浏览器视觉验收或真实后端联调。 + + +## 2026-09-14 增量同步 + +核对后端 dev `c2142681b16798ae5ff865a2eb648a9f45221af2` 的路由、Production Plan/Status/State、主体生图及视频输入编译实现。历史文档中的 `f0321c2` 已无法通过 GitHub compare 解析,本轮以当前源码为准。 + +- 接入制作执行计划和真实制作状态,删除“串联不会生成首帧”和必须先有全部主图的旧限制。 +- 补齐首帧质量回执、视频阻塞/进行中、dispatchCompleted;Provider 启动参数保持空对象。 +- 人工阻塞与请求错误分开,允许其余 ready 主体部分成功;提交前重新预检,项目归属/活动任务校验保留。 +- 批量生图默认包含过期主图,但新候选不会自动替换已有主图,页面明确提醒确认主图。 +- 指定模型最终输入支持只读检查授权素材与实际首帧,通用规格改为明确未指定模型的展示。授权资产绑定管理和实验探针不在本轮新增范围。 +- 浅色主画布统一白色,输入框使用浅灰,Tab 导航、辅助区和图库使用统一主题变量,首屏 theme-color 与应用一致。深色视频播放背景保留。 + +验证使用模拟 API,不调用真实生成模型。浏览器无法访问当前本地预览地址,尚未完成浏览器截图验收或真实后端联调。 diff --git a/docs/backend-coverage.md b/docs/backend-coverage.md index 94ea608..272d5e6 100644 --- a/docs/backend-coverage.md +++ b/docs/backend-coverage.md @@ -31,14 +31,19 @@ ## 高级串联生产边界 -实际后端顺序:形态正式提示词 → 形态图片 → 视频提示词 → 视频任务提交。**没有视觉风格、身份、导演设计、即时状态或首帧生成节点,也不会等待成片。** +实际后端顺序:形态正式提示词 → 形态图片 → 首帧质量生成/校验/有限修复 → 视频提示词 → 视频任务提交。视觉风格、身份母版、导演设计和即时状态需提前准备。 -- 仅剧本 `completed` 可启动;先检查项目/工作流任务、形态活动图片、当前母版引用、镜头规格、主首帧有效性和活动视频。 -- 为避免中途生成主图使既有首帧失效,前端要求先确认全部形态主图并补齐有效主首帧;因此该入口通常跳过形态生图,只串联仍缺少的文本与视频任务。 -- 点击预检只有 GET;付费确认后再次 GET 校验。查询失败或条件变更不提交 POST。单次项目锁仅覆盖本浏览器,不能代替服务器事务和跨客户端互斥。 -- 图片与视频模型由后端统一配置,前端不传请求级 Provider。旧串联路由仍解析默认字段,但实际执行节点已改用统一配置;不据此显示虚假模型选择。后端内部文字并发 3、图片与视频并发 2,不接受普通批量面板的覆盖、上限和尺寸配置。 -- 图工作流 `completed` 仅说明经过 finalize,可能仍有错误;`needsManualReview`、`stopReason` 和所有 `errors` 原样显示。视频统计用“已提交”,不标“成片已完成”。 -- 此工作流没有持久 checkpoint/恢复/取消接口。网络断开不能推断后端停止,禁止自动重发。已提交的长请求回执按原项目隔离;页面刷新后需从资产状态核对。 +- 通过 GET `/projects/:id/production/plan` 展示可执行、跳过、前置完成后可执行和人工阻塞数量,并保留逐项后端原因。缺少可自动生成的主图或首帧不再被前端直接拦截。 +- GET `/projects/:id/production/status` 展示真实资产有效数量与完成状态。计划是当前快照,前置阶段执行后后端还会重新检查。 +- 剧本未完成、活动任务、资产归属或列表不一致、没有可执行工作时不提交。人工阻塞独立提示,允许其余 ready 形态部分成功;不能保证后续阶段继续。 +- 点击检查只有 GET;费用确认后再次读取计划与状态,条件变化时不提交 POST。POST body 为 `{}`,图片/视频模型与并发读取后端配置,不消费普通批量面板参数。 +- `dispatchCompleted` 表示同步分发结束;`completed` 由后端核对所有有效主体主图、首帧、主视频后给出。回执展示首帧 passed/repairFailed/failed/blocked 及视频 inProgress/blocked,不把提交成功显示为制作完成。 +- 批量主体生图会处理缺失及过期主图,已有过期主图仍须用户确认新候选为主图。前端不读取 rawJson 猜测时效。 +- 单次项目锁仅覆盖本浏览器;长请求不自动重发,回执按原项目隔离。刷新后通过只读制作状态重新核对。 + +## 视频实际输入 + +生成规格弹窗提供 GET `/storyboard-shots/:id/provider-input-spec?provider=...`,显式填写目标模型,查看实际首帧、变体原因和授权素材。查询失败保留后端错误;切换镜头或模型清除旧结果。通用生成规格不宣称是指定模型最终输入。本轮未增加授权资产绑定管理或探针生成入口。 ## 观测指标边界 @@ -71,3 +76,4 @@ - 自动测试全部模拟 API,覆盖正式 ID/参数、提示词与图片职责分离、批量部分失败、数量和尺寸校验、候选不覆盖、未完成剧本禁用、费用确认、预检后条件变化、项目切换与旧响应隔离。 - 已通过 lint、格式、TypeScript、Vitest(含质量链路及菜单/折叠交互回归)和生产构建;定位回归见 `src/features/subject-images/asset-impact.test.ts`。测试不触发真实模型或生产任务。 - 当前环境无法打开应用预览,未进行浏览器视觉验收;需在可访问的开发环境检查浅/暗主题和窄屏布局,并由用户明确批准后进行真实小批量模型联调。 + diff --git a/docs/frontend-simplification.md b/docs/frontend-simplification.md index dece738..036e413 100644 --- a/docs/frontend-simplification.md +++ b/docs/frontend-simplification.md @@ -24,7 +24,7 @@ ### 模型配置 -图片与视频实际使用后端 `IMAGE_*`、`VIDEO_*` 配置。身份/选角/形态/首帧/视频请求不再发送固定 `provider`;串联生产也不再传固定 `imageProvider`、`videoProvider`。旧串联路由仍保留默认字段,但新执行节点不再消费这些请求级模型值,因此界面不能把它们当实际模型展示。 +图片与视频实际使用后端 `IMAGE_*`、`VIDEO_*` 配置。身份/选角/形态/首帧/视频请求不再发送固定 `provider`;串联生产也不再传固定 `imageProvider`、`videoProvider`。串联启动路由已移除请求级模型字段;指定模型的只读输入检查独立传入 provider,不影响生产配置。 质量面板或资产规格弹窗可按需读取 `/image-providers/capabilities` 和当前镜头的 `keyframe-provider-capability`。此入口只读,不提供会被后端忽略的“前端切换模型”。未声明参考图上限显示“上限未声明”,不声称无限制。 @@ -64,3 +64,4 @@ ## 验证边界 单元与组件测试全部使用模拟 API,验证参数、正式 ID、禁用、费用确认、范围、归属、回执隔离和菜单访问,不触发真实模型任务。浏览器视觉与实际模型联调需要在可访问的开发环境进行;不将 DOM 测试视为视觉验收。 + diff --git a/index.html b/index.html index bb94a62..0d32723 100644 --- a/index.html +++ b/index.html @@ -3,7 +3,7 @@ - + + diff --git a/src/composables/useTheme.ts b/src/composables/useTheme.ts index 8b3d544..2c4567c 100644 --- a/src/composables/useTheme.ts +++ b/src/composables/useTheme.ts @@ -36,7 +36,7 @@ export function useTheme() { const disabledColor = dark ? '#303030' : '#e4e4e4' const disabledText = dark ? '#808080' : '#8c8c8c' const accentText = dark ? '#5cd693' : '#087c42' - const body = dark ? '#111111' : '#ededed' + const body = dark ? '#111111' : '#ffffff' const surface = dark ? '#191919' : '#ffffff' const subtle = dark ? '#232323' : '#f7f7f7' const text = dark ? '#d9d9d9' : '#191919' @@ -254,7 +254,7 @@ export function useTheme() { document.documentElement.style.colorScheme = isDark.value ? 'dark' : 'light' document .querySelector('meta[name="theme-color"]') - ?.setAttribute('content', isDark.value ? '#111111' : '#ededed') + ?.setAttribute('content', isDark.value ? '#111111' : '#ffffff') try { localStorage.setItem(storageKey, preference.value) } catch { @@ -263,3 +263,4 @@ export function useTheme() { }) return { preference, isDark, theme, overrides } } + diff --git a/src/features/production/api.ts b/src/features/production/api.ts index cdeca2a..6f706f8 100644 --- a/src/features/production/api.ts +++ b/src/features/production/api.ts @@ -10,6 +10,9 @@ import type { ProjectVideoStatus, PromptReadiness, ProductionPipelineResult, + ProductionPlan, + ProviderInputSpec, + ProjectProductionStatus, RetryVideosResult, ShotKeyframe, ShotVideo, @@ -32,6 +35,17 @@ function shotPath(id: string) { /** 生产链 API;生成请求不设置客户端短超时,也不自动重试。 */ export const productionApi = { + /** 根据显式指定的模型查询最终输入,不能用通用规格推断授权素材是否生效。 */ + providerInputSpec: (shotId: string, provider: string, signal?: AbortSignal) => + request(`${shotPath(shotId)}/provider-input-spec?provider=${encodeURIComponent(provider)}`, { + signal + }), + /** 查询当前计划,不生成图片、视频或提示词。 */ + plan: (projectId: string, signal?: AbortSignal) => + request(`${projectPath(projectId)}/production/plan`, { signal }), + /** 读取真实主资产完成状态,不能用任务提交回执代替。 */ + status: (projectId: string, signal?: AbortSignal) => + request(`${projectPath(projectId)}/production/status`, { signal }), startPipeline: (projectId: string) => request(`${projectPath(projectId)}/production/start`, { method: 'POST', diff --git a/src/features/production/components/AdvancedProduction.vue b/src/features/production/components/AdvancedProduction.vue index 8fb9c90..902cd08 100644 --- a/src/features/production/components/AdvancedProduction.vue +++ b/src/features/production/components/AdvancedProduction.vue @@ -4,6 +4,7 @@ import { computed, ref, watch } from 'vue' import ConfirmAction from '../../workflows/ConfirmAction.vue' import { downloadText, formatDate } from '../../../lib/format' import { useAdvancedProduction } from '../useAdvancedProduction' +import ProductionPlanPanel from './ProductionPlanPanel.vue' /** 高级入口明确真实流程边界,默认折叠且不在挂载时触发预检或生成。 */ const { id, operation, session, checking, error, check, blocked, preflight, start } = useAdvancedProduction() @@ -31,12 +32,10 @@ function exportReceipt() { 依次补齐形态提示词、形态参考图、视频提示词并提交视频任务。不会生成视觉风格、身份母版、导演设计、即时状态或首帧。请先在各阶段完成验收。依次补齐形态提示词、参考图、首帧质量校验与修复、视频提示词,再提交视频任务。视觉风格、身份母版、导演设计和即时状态仍需提前准备。

- 范围固定为全项目,跳过已有结果;不使用上方并发、覆盖、数量上限和尺寸配置。后端固定文字并发 - 3、图片和视频并发 2,可能产生模型费用。此流程没有持久运行锁或恢复 - checkpoint;请确认其他页面/终端没有任务。断网后先查资产,勿直接重试。 + 范围固定为全项目,跳过已有结果;不使用上方并发、覆盖、数量上限和尺寸配置。可能产生文字、图片、首帧校验与修复、视频模型费用。模型与并发由服务端配置决定。请确认其他页面没有任务;断网后先刷新制作状态,勿直接重试。

@@ -63,8 +62,9 @@ function exportReceipt() {
  • {{ issue }}
-

前置检查通过,提交前仍会再次校验。

+

可以执行计划中的任务,提交前仍会再次校验。

+

请求进行中,请勿重复提交。

@@ -73,7 +73,13 @@ function exportReceipt() {

串联生产回执 · {{ - receipt.needsManualReview ? '需要人工处理' : receipt.completed ? '流程已返回' : '流程未完成' + receipt.needsManualReview + ? '需要人工处理' + : receipt.completed + ? '制作已完成' + : receipt.dispatchCompleted + ? '任务已分发,等待视频完成' + : '本轮未完成' }}

导出回执 @@ -87,11 +93,23 @@ function exportReceipt() { 失败 {{ row.result.failed }}

+

+ 首帧质量: +

视频任务:

+import { computed } from 'vue' +import { NAlert, NCollapse, NCollapseItem, NScrollbar } from 'naive-ui' +import type { ProductionPlan, ProjectProductionStatus } from '../types' + +/** 展示后端只读执行计划与真实资产状态,不自行推断阻塞是否可自动解除。 */ +const props = defineProps<{ plan: ProductionPlan; status: ProjectProductionStatus }>() +const rows = computed(() => [ + { label: '主体参考图', stage: props.plan.subjectImages }, + { label: '首帧质量', stage: props.plan.keyframes }, + { label: '视频提示词', stage: props.plan.videoPrompts }, + { label: '视频任务', stage: props.plan.videos } +]) +const stageNames = { subject_images: '主体参考图', keyframes: '首帧', videos: '视频', completed: '全部完成' } +/** 保留全部后端问题,包括预计可由前置阶段解决的问题,便于核对计划。 */ +const problems = computed(() => [ + ...props.plan.details.subjectImages + .filter(item => item.issues.length) + .map(item => ({ + key: `form:${item.subjectFormId}`, + label: `主体参考图 · ${item.subjectName} / ${item.formName}(${item.subjectRef})`, + issues: item.issues + })), + ...[ + { label: '首帧', key: 'keyframe', items: props.plan.details.keyframes }, + { label: '视频提示词', key: 'prompt', items: props.plan.details.videoPrompts }, + { label: '视频', key: 'video', items: props.plan.details.videos } + ].flatMap(group => + group.items + .filter(item => item.issues.length) + .map(item => ({ + key: `${group.key}:${item.shotId}`, + label: `${group.label} · 镜头 ${item.shotNo}(${item.shotId})`, + issues: item.issues + })) + ) +]) + + + + + diff --git a/src/features/production/components/ProviderInputInspector.vue b/src/features/production/components/ProviderInputInspector.vue new file mode 100644 index 0000000..140e6a9 --- /dev/null +++ b/src/features/production/components/ProviderInputInspector.vue @@ -0,0 +1,89 @@ + + + diff --git a/src/features/production/components/ShotProductionAssets.vue b/src/features/production/components/ShotProductionAssets.vue index e5c518f..a7bd2fb 100644 --- a/src/features/production/components/ShotProductionAssets.vue +++ b/src/features/production/components/ShotProductionAssets.vue @@ -1,4 +1,5 @@