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 >({}) /** 正式 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 }