feat: 同步角色选角与首帧身份约束
This commit is contained in:
@@ -75,6 +75,12 @@ const videoRunning = computed(
|
||||
(query.data.value?.videoStatus.queued ?? 0) +
|
||||
(query.data.value?.videoStatus.running ?? 0)
|
||||
)
|
||||
const identityBlocked = computed(
|
||||
() =>
|
||||
(query.data.value?.keyframes.missingIdentity ?? 0) +
|
||||
(query.data.value?.keyframes.missingIdentityAnchor ?? 0) +
|
||||
(query.data.value?.keyframes.identityUnlocked ?? 0)
|
||||
)
|
||||
|
||||
/** 父页查询包含整个项目的就绪统计,单镜头变更后立即刷新。 */
|
||||
function refreshProject() {
|
||||
@@ -149,6 +155,11 @@ function refreshProject() {
|
||||
<p v-if="videoRunning" class="alert mt-4" role="status">
|
||||
当前有 {{ videoRunning }} 个视频任务等待或生成中。后端正在轮询 Provider;同一镜头不会重复提交活动任务。
|
||||
</p>
|
||||
<p v-if="identityBlocked" class="alert mt-4">
|
||||
当前有 {{ identityBlocked }} 项角色身份前置问题。Character 必须生成
|
||||
Identity、确认演员母版并锁定后,首帧才能就绪。
|
||||
<RouterLink :to="`/projects/${id}/subject-identity`" class="text-button ml-2">前往角色选角 →</RouterLink>
|
||||
</p>
|
||||
<ProductionReceipt v-if="session.receipt" :receipt="session.receipt" />
|
||||
|
||||
<div v-if="query.data.value" class="production-pipeline mt-5">
|
||||
@@ -191,6 +202,11 @@ function refreshProject() {
|
||||
<dd>{{ stage.data?.blocked ?? 0 }}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
<p v-if="stage.key === 'keyframes' && identityBlocked" class="mt-3 text-[11px] leading-5 text-danger">
|
||||
身份缺失 {{ query.data.value?.keyframes.missingIdentity ?? 0 }} · 母版缺失
|
||||
{{ query.data.value?.keyframes.missingIdentityAnchor ?? 0 }} · 未锁定
|
||||
{{ query.data.value?.keyframes.identityUnlocked ?? 0 }}
|
||||
</p>
|
||||
<ConfirmAction
|
||||
class="mt-4"
|
||||
:label="stage.action"
|
||||
|
||||
@@ -387,6 +387,16 @@ async function inspectSpecs() {
|
||||
<div v-if="keyframeSpec">
|
||||
<h5 class="font-medium">首帧 Prompt · {{ keyframeSpec.references.length }} 张参考图</h5>
|
||||
<p class="mt-3 whitespace-pre-wrap leading-6 text-muted">{{ keyframeSpec.prompt }}</p>
|
||||
<ul v-if="keyframeSpec.references.length" class="mt-3 space-y-1 text-[11px] text-muted">
|
||||
<li
|
||||
v-for="reference in keyframeSpec.references"
|
||||
:key="`${reference.role}:${reference.imageId}`"
|
||||
>
|
||||
{{ reference.subjectRef }} ·
|
||||
{{ reference.role === 'identity-anchor' ? '演员身份母版' : '形态主图' }} ·
|
||||
<code>{{ reference.imageId }}</code>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div v-if="videoSpec">
|
||||
<h5 class="font-medium">视频 Prompt · {{ videoSpec.durationSeconds }}s</h5>
|
||||
|
||||
@@ -43,6 +43,9 @@ export function issueLabel(code: ProductionIssueCode): string {
|
||||
invalid_generation_spec: '生成规格不完整',
|
||||
missing_visual_style: '缺少视觉风格',
|
||||
missing_reference: '缺少主体参考图',
|
||||
missing_identity: '缺少角色身份',
|
||||
missing_identity_anchor: '缺少演员母版',
|
||||
identity_unlocked: '演员身份未锁定',
|
||||
missing_prompt: '缺少视频提示词',
|
||||
missing_keyframe: '缺少主首帧'
|
||||
}
|
||||
|
||||
@@ -51,6 +51,8 @@ describe('镜头生产数据契约', () => {
|
||||
it('就绪问题和异步任务状态提供中文标签', () => {
|
||||
expect(issueLabel('missing_keyframe')).toBe('缺少主首帧')
|
||||
expect(issueLabel('invalid_generation_spec')).toBe('生成规格不完整')
|
||||
expect(issueLabel('missing_identity_anchor')).toBe('缺少演员母版')
|
||||
expect(issueLabel('identity_unlocked')).toBe('演员身份未锁定')
|
||||
expect(productionStatusLabel('in_progress')).toBe('任务进行中')
|
||||
expect(productionStatusLabel('unknown')).toBe('unknown')
|
||||
})
|
||||
|
||||
@@ -6,6 +6,9 @@ export type ProductionIssueCode =
|
||||
| 'invalid_generation_spec'
|
||||
| 'missing_visual_style'
|
||||
| 'missing_reference'
|
||||
| 'missing_identity'
|
||||
| 'missing_identity_anchor'
|
||||
| 'identity_unlocked'
|
||||
| 'missing_prompt'
|
||||
| 'missing_keyframe'
|
||||
|
||||
@@ -37,12 +40,17 @@ export interface PromptReadiness {
|
||||
|
||||
/** 镜头首帧就绪检查的逐镜结果。 */
|
||||
export interface KeyframeReadinessItem extends PromptReadinessItem {
|
||||
episodeNo: number
|
||||
beatNo: number
|
||||
primaryKeyframeId?: string | null
|
||||
}
|
||||
|
||||
/** 镜头首帧项目级就绪汇总。 */
|
||||
export interface KeyframeReadiness extends PromptReadiness {
|
||||
missingVisualStyle: number
|
||||
missingIdentity: number
|
||||
missingIdentityAnchor: number
|
||||
identityUnlocked: number
|
||||
items: KeyframeReadinessItem[]
|
||||
}
|
||||
|
||||
@@ -111,7 +119,8 @@ export interface KeyframeReference {
|
||||
subjectId: string
|
||||
subjectRef: string
|
||||
module: string
|
||||
subjectFormId: string
|
||||
role: 'identity-anchor' | 'form-primary'
|
||||
subjectFormId?: string
|
||||
imageId: string
|
||||
imageUrl: string
|
||||
providerImageUrl?: string
|
||||
@@ -129,9 +138,16 @@ export interface KeyframeSpec {
|
||||
}
|
||||
|
||||
/** 视频生成规格中的主体参考图。 */
|
||||
export interface VideoReference extends KeyframeReference {
|
||||
export interface VideoReference {
|
||||
subjectId: string
|
||||
subjectRef: string
|
||||
subjectName: string
|
||||
module: string
|
||||
subjectFormId: string
|
||||
subjectFormName: string
|
||||
imageId: string
|
||||
imageUrl: string
|
||||
providerImageUrl?: string
|
||||
}
|
||||
|
||||
/** 后端实际提交 Seedance 前编译的完整视频生成规格。 */
|
||||
|
||||
Reference in New Issue
Block a user