auto-save 2026-05-27 14:47 (~2)

This commit is contained in:
2026-05-27 14:47:45 +08:00
parent a699899323
commit 22398c1483
2 changed files with 69 additions and 27 deletions

View File

@@ -1,12 +1,5 @@
{ {
"entries": [ "entries": [
{
"files_changed": 3,
"hash": "1d0a77b",
"message": "fix: prefer width-first workbench scaling",
"ts": "2026-05-20T18:58:31+08:00",
"type": "commit"
},
{ {
"files_changed": 1, "files_changed": 1,
"hash": "4a22ca0", "hash": "4a22ca0",
@@ -3192,6 +3185,13 @@
"message": "auto-save 2026-05-27 14:36 (~3)", "message": "auto-save 2026-05-27 14:36 (~3)",
"hash": "5046e23", "hash": "5046e23",
"files_changed": 3 "files_changed": 3
},
{
"ts": "2026-05-27T14:42:16+08:00",
"type": "commit",
"message": "auto-save 2026-05-27 14:42 (~2)",
"hash": "a699899",
"files_changed": 2
} }
] ]
} }

View File

@@ -79,6 +79,36 @@ const newestGeneratedVideo = (job) => (
[...(job.generated_videos || [])].sort((a, b) => (b.created_at || 0) - (a.created_at || 0))[0] [...(job.generated_videos || [])].sort((a, b) => (b.created_at || 0) - (a.created_at || 0))[0]
) )
const parseVideoTaskId = (pollTaskId) => {
const match = /^skg:([^:]+):([^:]+)$/.exec(String(pollTaskId || ''))
if (!match) {
const err = new Error('未知视频任务类型')
err.terminal = true
throw err
}
return { jobId: match[1], videoId: match[2] }
}
export const readVideoTask = async (pollTaskId) => {
const { jobId, videoId } = parseVideoTaskId(pollTaskId)
const job = await requestJson(`/jobs/${jobId}`, { method: 'GET' })
const item = (job.generated_videos || []).find(v => v.id === videoId)
if (!item) {
const err = new Error('视频任务不存在')
err.terminal = true
throw err
}
return {
jobId,
videoId,
job,
video: item,
status: item.status,
progress: item.progress || 0,
url: item.status === 'completed' ? toAssetUrl(item.url || `/jobs/${jobId}/storyboard-videos/${videoId}.mp4`) : ''
}
}
const normalizeVideoSize = (value) => { const normalizeVideoSize = (value) => {
const raw = String(value || '').trim().toLowerCase() const raw = String(value || '').trim().toLowerCase()
const map = { const map = {
@@ -256,32 +286,44 @@ export const useVideoGeneration = () => {
} }
const pollVideoTask = async (pollTaskId, onProgress = () => {}) => { const pollVideoTask = async (pollTaskId, onProgress = () => {}) => {
const match = /^skg:([^:]+):([^:]+)$/.exec(String(pollTaskId || '')) const maxAttempts = 720
if (!match) throw new Error('未知视频任务类型')
const [, jobId, videoId] = match
const maxAttempts = 180
const interval = 5000 const interval = 5000
let transientFailures = 0
for (let i = 0; i < maxAttempts; i += 1) { for (let i = 0; i < maxAttempts; i += 1) {
const job = await requestJson(`/jobs/${jobId}`, { method: 'GET' }) try {
const item = (job.generated_videos || []).find(v => v.id === videoId) const snapshot = await readVideoTask(pollTaskId)
if (!item) throw new Error('视频任务不存在') const item = snapshot.video
const percentage = item.progress || Math.min(Math.round((i / maxAttempts) * 100), 98) transientFailures = 0
onProgress(i + 1, percentage) const percentage = item.progress || Math.min(Math.round((i / maxAttempts) * 100), 98)
progress.attempt = i + 1 onProgress(i + 1, percentage)
progress.percentage = percentage progress.attempt = i + 1
if (item.status === 'completed') { progress.percentage = percentage
const result = { ...item, url: toAssetUrl(item.url || `/jobs/${jobId}/storyboard-videos/${videoId}.mp4`) } if (item.status === 'completed') {
video.value = result const result = { ...item, url: snapshot.url }
setSuccess() video.value = result
return result setSuccess()
} return result
if (item.status === 'failed') { }
throw new Error(item.error || '视频生成失败') if (item.status === 'failed') {
const err = new Error(item.error || '视频生成失败')
err.terminal = true
throw err
}
} catch (err) {
if (err.terminal) throw err
transientFailures += 1
if (transientFailures >= 24) {
const retryable = new Error(err.message || '视频状态同步暂时中断')
retryable.retryable = true
throw retryable
}
} }
await new Promise(resolve => setTimeout(resolve, interval)) await new Promise(resolve => setTimeout(resolve, interval))
} }
throw new Error('视频生成超时') const retryable = new Error('视频仍在生成,已交给后台同步')
retryable.retryable = true
throw retryable
} }
const generate = async (params) => { const generate = async (params) => {