auto-save 2026-05-27 14:47 (~2)
This commit is contained in:
@@ -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
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user