feat: 同步精简接口并统一表单校验

This commit is contained in:
GJ
2026-09-10 22:34:47 +08:00
parent 82d7bde631
commit 2eea0ea72e
66 changed files with 1611 additions and 1100 deletions
@@ -27,7 +27,7 @@ import ActionMenu from '../../../components/ui/ActionMenu.vue'
import { AppDialog } from '../../../components/ui'
import ModelCapabilities from './ModelCapabilities.vue'
import type { QualityTarget } from '../quality.types'
import { savedVideoValidation, videoQualityInfo, videoRepairInfo } from '../quality'
import { sessionVideoValidation } from '../quality'
/** 单镜头生产面板只负责正式资产,不修改上游导演设计与即时状态。 */
const props = defineProps<{
@@ -64,10 +64,6 @@ const assetBlocked = computed(() => props.disabled || operation.value.pending ||
const keyframeOpen = ref(false)
const qualityOpen = ref(false)
const qualityTarget = ref<QualityTarget | null>(null)
const qualityRawJson = computed(() => {
const target = qualityTarget.value
return target?.kind === 'video' ? (videos.value.find(item => item.id === target.assetId)?.rawJson ?? null) : null
})
const specsOpen = ref(false)
/** 从实际候选打开质量面板,保留单镜头主操作,不默认展开全部校验参数。 */
@@ -132,7 +128,7 @@ async function generatePrompt() {
const overwrite = props.promptReadiness?.status === 'skipped'
await runOperation(props.projectId, `生成镜头 ${props.shot.shotNo} 视频提示词`, async () => {
const result = await storyboardApi.generatePrompt(shotId, overwrite)
if (!result || result.id !== shotId || !result.videoPrompt) throw new Error('接口未返回有效视频提示词。')
if (!result || result.shotId !== shotId || !result.videoPrompt) throw new Error('接口未返回有效视频提示词。')
})
await refreshAll()
}
@@ -151,7 +147,12 @@ async function generateKeyframe(input: GenerateKeyframeInput) {
/** 切换主首帧不调用模型,但会改变后续视频生成输入。 */
async function setPrimaryKeyframe() {
const keyframeId = confirmingKeyframe.value
if (assetBlocked.value || !keyframeId) return
if (
assetBlocked.value ||
!keyframeId ||
keyframes.value.find(item => item.id === keyframeId)?.source === 'provider_variant'
)
return
const shotId = props.shot.shotId
confirmingKeyframe.value = ''
await runOperation(props.projectId, `设置镜头 ${props.shot.shotNo} 主首帧`, async () => {
@@ -195,8 +196,9 @@ async function setPrimaryVideo() {
const video = videos.value.find(item => item.id === videoId)
if (
!video ||
((videoRepairInfo(video.rawJson) || videoQualityInfo(video.rawJson)) &&
savedVideoValidation(video.rawJson)?.passed !== true)
video.status !== 'completed' ||
!video.videoUrl ||
sessionVideoValidation(props.projectId, video.shotId, video.id)?.passed !== true
)
return
const shotId = props.shot.shotId
@@ -322,6 +324,9 @@ async function inspectSpecs() {
<div class="flex flex-wrap items-center gap-2">
<StatusBadge :status="item.status" :label="productionStatusLabel(item.status)" />
<NTag v-if="item.isPrimary" size="small" :bordered="false">主首帧</NTag>
<NTag v-if="item.source === 'provider_variant'" size="small" :bordered="false"
>模型输入变体</NTag
>
<NTag v-if="item.isPrimary && keyframeStale" size="small" :bordered="false">已过期</NTag>
<span class="text-[10px] text-muted"
>{{ item.width || '—' }} × {{ item.height || '—' }}</span
@@ -338,7 +343,12 @@ async function inspectSpecs() {
>质量检查</NButton
>
<NButton
v-if="item.status === 'completed' && item.imageUrl && !item.isPrimary"
v-if="
item.status === 'completed' &&
item.imageUrl &&
item.source !== 'provider_variant' &&
!item.isPrimary
"
:disabled="assetBlocked"
@click="confirmingKeyframe = item.id"
text
@@ -401,22 +411,7 @@ async function inspectSpecs() {
<div class="flex flex-wrap items-center gap-2">
<StatusBadge :status="item.status" :label="productionStatusLabel(item.status)" />
<NTag v-if="item.isPrimary" size="small" :bordered="false">主视频</NTag>
<NTag v-if="videoRepairInfo(item.rawJson) && !item.isPrimary" size="small" :bordered="false"
>修复候选 · 待复检</NTag
>
<NTag
v-else-if="videoQualityInfo(item.rawJson) && !item.isPrimary"
size="small"
:bordered="false"
>质量候选 ·
{{
savedVideoValidation(item.rawJson)
? savedVideoValidation(item.rawJson)?.passed
? '已通过'
: '未通过'
: '自动校验中'
}}</NTag
>
<NTag v-if="!item.isPrimary" size="small" :bordered="false">候选视频</NTag>
<NTag v-if="item.isPrimary && videoStale" size="small" :bordered="false">已过期</NTag>
<span class="text-[10px] text-muted"
>{{ item.durationSeconds || shot.durationSeconds || '—' }}s</span
@@ -434,8 +429,13 @@ async function inspectSpecs() {
text
size="small"
@click="openQuality('video', item.id)"
>质量检查<template v-if="savedVideoValidation(item.rawJson)">
· {{ savedVideoValidation(item.rawJson)?.passed ? '曾通过' : '未通过' }}</template
>质量检查<template v-if="sessionVideoValidation(projectId, item.shotId, item.id)">
·
{{
sessionVideoValidation(projectId, item.shotId, item.id)?.passed
? '曾通过'
: '未通过'
}}</template
></NButton
>
<a
@@ -451,8 +451,7 @@ async function inspectSpecs() {
item.status === 'completed' &&
item.videoUrl &&
!item.isPrimary &&
(!(videoRepairInfo(item.rawJson) || videoQualityInfo(item.rawJson)) ||
savedVideoValidation(item.rawJson)?.passed === true)
sessionVideoValidation(projectId, item.shotId, item.id)?.passed === true
"
:disabled="assetBlocked"
@click="confirmingVideo = item.id"
@@ -473,6 +472,9 @@ async function inspectSpecs() {
</article>
</div>
<p v-else class="mt-4 text-xs text-muted">尚无视频任务记录。</p>
<p v-if="videos.some(item => !item.isPrimary)" class="mt-3 text-xs text-muted">
候选视频的历史质量结果未公开;手动设为主视频前需在本会话完成视觉校验。自动质量任务的晋升结果以刷新后的主视频标记为准。
</p>
<div class="mt-4 flex flex-wrap gap-3">
<ConfirmAction
:label="
@@ -525,7 +527,15 @@ async function inspectSpecs() {
Negative prompt: {{ videoSpec.negativePrompt }}
</p>
<p class="mt-3 break-all font-mono text-[10px] text-muted">
Keyframe ID · {{ videoSpec.keyframe.id }}
业务主首帧 ID · {{ videoSpec.keyframe.id }}
</p>
<p v-if="videoSpec.providerInputKeyframe" class="mt-2 break-all text-[10px] text-muted">
模型输入首帧 ID · {{ videoSpec.providerInputKeyframe.id }}
{{
videoSpec.providerInputKeyframe.source === 'provider_variant'
? '(专用变体)'
: '(业务主首帧)'
}}
</p>
</div>
</div></NCollapseItem
@@ -537,7 +547,6 @@ async function inspectSpecs() {
:project-id="projectId"
:target="qualityTarget"
:disabled="assetBlocked"
:saved-raw-json="qualityRawJson"
@changed="refreshAll"
/>
<KeyframeDialog