feat: 同步质量校验修复并精简工作台操作

This commit is contained in:
GouJ
2026-09-03 19:38:57 +08:00
parent 771b66cafc
commit 13bb6dc83b
47 changed files with 2244 additions and 260 deletions
+106
View File
@@ -0,0 +1,106 @@
import { reactive } from 'vue'
import { validOptionalSize } from './model'
import type { KeyframeReadiness } from './types'
import type { QualityInput, QualityReceipt, QualityTarget, VisualValidation } from './quality.types'
/** 未返回的操作不显示上次成功结论;网络失败不会自动重发。 */
const sessions = reactive<Record<string, { receipt: QualityReceipt | null; error: string }>>({})
/** 正式 ID 与目标类型共同隔离会话结果,批量回执按剧集隔离。 */
export function qualityKey(projectId: string, target: QualityTarget | null) {
return JSON.stringify([
projectId,
target?.kind,
target && ('assetId' in target ? [target.shotId, target.assetId] : target.episodeNo)
])
}
/** 同一目标关闭后重新打开仍能查看本会话的回执。 */
export function qualitySession(key: string) {
return (sessions[key] ??= { receipt: null, error: '' })
}
/** 一行一个允许文字,仅清理空白与重复,不自行添加授权内容。 */
export function allowedTextLines(value: string) {
return [
...new Set(
value
.split('\n')
.map(item => item.trim())
.filter(Boolean)
)
]
}
/** 付费前独立校验整数、尺寸与剧集范围,0 次修复代表只验不修。 */
export function validQualityInput(input: QualityInput) {
return (
[input.concurrency, input.limit, input.episodeNo].every(value => Number.isSafeInteger(value) && value > 0) &&
Number.isSafeInteger(input.maxRepairAttempts) &&
input.maxRepairAttempts >= 0 &&
validOptionalSize(input.width ?? '', input.height ?? '')
)
}
/** 与后端保持同一优先级:全项目有过期项时先筛过期,再按当前集筛选。 */
export function qualityTargets(readiness: KeyframeReadiness, input: QualityInput) {
return readiness.items
.filter(
item =>
item.status === 'ready' &&
(input.force ||
(readiness.stalePrimaryKeyframe > 0
? item.primaryKeyframeStale === true
: !item.primaryKeyframeId)) &&
item.episodeNo === input.episodeNo
)
.slice(0, input.limit)
}
/** 最小响应校验,避免畸形响应或仅 HTTP 200 被显示成视觉通过。 */
export function isVisualValidation(value: unknown): value is VisualValidation {
if (!value || typeof value !== 'object') return false
const item = value as VisualValidation
return (
typeof item.passed === 'boolean' &&
typeof item.summary === 'string' &&
typeof item.subjectCountConsistent === 'boolean' &&
Array.isArray(item.subjects) &&
item.subjects.every(
subject =>
typeof subject.subjectRef === 'string' &&
typeof subject.identityConsistent === 'boolean' &&
typeof subject.formConsistent === 'boolean' &&
Array.isArray(subject.issues) &&
subject.issues.every(issue => typeof issue === 'string')
) &&
Array.isArray(item.issues) &&
item.issues.every(
issue => typeof issue.description === 'string' && ['warning', 'error'].includes(issue.severity)
) &&
typeof item.unauthorizedText?.detected === 'boolean' &&
Array.isArray(item.unauthorizedText.texts)
)
}
/** 读取视频已保存的抽样校验,不为查看历史再次调用视觉模型。 */
export function savedVideoValidation(rawJson?: string | null): VisualValidation | null {
try {
const value: unknown = JSON.parse(rawJson || '{}').videoValidation
return isVisualValidation(value) ? value : null
} catch {
return null
}
}
/** 识别后端修复候选标记,避免未复检候选通过普通主视频按钮绕过校验。 */
export function videoRepairInfo(rawJson?: string | null): { attempt: number } | null {
try {
const repair: unknown = JSON.parse(rawJson || '{}').repair
if (!repair || typeof repair !== 'object' || Array.isArray(repair)) return null
const attempt = (repair as { attempt?: unknown }).attempt
return { attempt: typeof attempt === 'number' && Number.isSafeInteger(attempt) && attempt >= 0 ? attempt : 0 }
} catch {
return null
}
}