/** * live progress 合并(Task 144)。 * * history 条目缺失 file 进度时保留实时缓存(live)进度;live 有值且有效时 * 以 live 为准;history 已可下载(fileReady/downloadUrl 就绪)时不覆盖—— * 结果文件既成,实时进度无意义。纯函数无副作用,入参不修改。 */ export const FILE_PROGRESS_KEYS = [ 'fileProgressPercent', 'fileProgressCurrent', 'fileProgressTotal', 'fileProgressMessage', 'fileReady', 'fileStatus', ] as const export interface DownloadableItem { resultId?: number | null fileReady?: boolean | null fileStatus?: string | null downloadUrl?: string | null } /** * 可下载判定(Task 145):有结果记录,且满足其一—— * fileReady=true、downloadUrl 非空、fileStatus 为终态(SUCCESS 可下载结果文件, * FAILED 可下载错误文件或用于展示错误)。 */ export function canDownloadItem(item: DownloadableItem | null | undefined): boolean { if (!item) return false const ready = item.fileReady || Boolean(item.downloadUrl) const terminalStatus = item.fileStatus === 'SUCCESS' || item.fileStatus === 'FAILED' return Boolean(item.resultId && (ready || terminalStatus)) } /** 有效进度值:仅 fileProgressPercent 需要严格 >0,其余字段非空即可(0 是合法计数)。 */ export function hasMeaningfulProgress(key: string, value: unknown): boolean { if (value === undefined || value === null) return false if (key === 'fileProgressPercent' && typeof value === 'number') { return value > 0 } return true } export function mergeHistoryItemPreservingLiveProgress>( history: T, live: T | undefined, ): T { if (!live) return history if (canDownloadItem(history as unknown as DownloadableItem)) return history const result: Record = { ...history } for (const key of FILE_PROGRESS_KEYS) { const value = (live as Record)[key] if (hasMeaningfulProgress(key, value)) { result[key] = value } } return result as T }