Files
short-drama-agent-front/src/features/production/quality.ts
T

92 lines
3.8 KiB
TypeScript

import { reactive } from 'vue'
import { validOptionalSize } from './model'
import type { KeyframeReadiness } from './types'
import type { QualityInput, QualityReceipt, QualityTarget, VisualValidation, VideoValidation } from './quality.types'
/** 未返回的操作不显示上次成功结论;网络失败不会自动重发。 */
const sessions = reactive<
Record<string, { receipt: QualityReceipt | null; error: string; videoValidation?: VideoValidation }>
>({})
/** 正式 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)
)
}
/** 公开视频 DTO 不含质量历史,只使用本会话真实收到的校验回执。 */
export function sessionVideoValidation(projectId: string, shotId: string, assetId: string): VideoValidation | null {
return qualitySession(qualityKey(projectId, { kind: 'video', shotId, assetId, title: '' })).videoValidation ?? null
}