feat(drawnix): 添加页面刷新后恢复视频任务轮询的功能
添加对页面刷新中断的视频生成任务的恢复机制。当用户刷新页面时,系统会通过 requestIdleCallback 或 setTimeout 自动恢复所有处于 PROCESSING 状态且拥有 remoteId 的视频任务,继续轮询其生成状态直至完成或失败,避免用户手动重新触发任务。
This commit is contained in:
1 parent
d6f37f0bb0
commit
d434c1655d
2 files changed
+94
No files matched your search
@@ -326,6 +326,21 @@ export const Drawnix: React.FC<DrawnixProps> = ({
|
||||
}
|
||||
}, [board]);
|
||||
|
||||
// Initialize fallback media executor to resume pending tasks
|
||||
useEffect(() => {
|
||||
const resumeTasks = () => {
|
||||
import('./services/media-executor/fallback-executor').then(({ fallbackMediaExecutor }) => {
|
||||
fallbackMediaExecutor.resumePendingTasks();
|
||||
});
|
||||
};
|
||||
|
||||
if ('requestIdleCallback' in window) {
|
||||
(window as Window).requestIdleCallback(resumeTasks, { timeout: 5000 });
|
||||
} else {
|
||||
setTimeout(resumeTasks, 1000);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// 监听 API 认证错误事件,自动打开设置对话框
|
||||
useEffect(() => {
|
||||
const handleApiAuthError = (event: Event) => {
|
||||
|
||||
@@ -15,7 +15,9 @@ import type {
|
||||
GeminiConfig,
|
||||
VideoAPIConfig,
|
||||
} from './types';
|
||||
import { Task, TaskStatus } from '../../types/task.types';
|
||||
import { taskStorageWriter } from './task-storage-writer';
|
||||
import { taskStorageReader } from '../task-storage-reader';
|
||||
import { geminiSettings } from '../../utils/settings-manager';
|
||||
import {
|
||||
startLLMApiLog,
|
||||
@@ -433,6 +435,9 @@ export class FallbackMediaExecutor implements IMediaExecutor {
|
||||
httpStatus: 200,
|
||||
});
|
||||
|
||||
// 保存 remoteId,用于页面刷新后恢复轮询
|
||||
await taskStorageWriter.updateRemoteId(taskId, videoId);
|
||||
|
||||
options?.onProgress?.({ progress: 10, phase: 'polling' });
|
||||
|
||||
// 轮询等待视频完成
|
||||
@@ -651,6 +656,80 @@ export class FallbackMediaExecutor implements IMediaExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 恢复未完成的任务(例如页面刷新导致中断的任务)
|
||||
* 仅恢复有 remoteId 且状态为 processing 的任务
|
||||
*/
|
||||
async resumePendingTasks(): Promise<void> {
|
||||
try {
|
||||
const pendingTasks = await taskStorageReader.getAllTasks({ status: TaskStatus.PROCESSING });
|
||||
|
||||
// 筛选出有 remoteId 的视频任务
|
||||
const videoTasks = pendingTasks.filter(t =>
|
||||
t.type === 'video' && t.remoteId && t.status === TaskStatus.PROCESSING
|
||||
);
|
||||
|
||||
if (videoTasks.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`[FallbackMediaExecutor] Resuming ${videoTasks.length} pending video tasks...`);
|
||||
|
||||
// 并行恢复
|
||||
await Promise.all(videoTasks.map(task => this.resumeVideoTask(task)));
|
||||
} catch (error) {
|
||||
console.error('[FallbackMediaExecutor] Failed to resume pending tasks:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 恢复单个视频任务的轮询
|
||||
*/
|
||||
private async resumeVideoTask(task: Task): Promise<void> {
|
||||
const config = this.getConfig();
|
||||
const videoId = task.remoteId!;
|
||||
|
||||
try {
|
||||
console.log(`[FallbackMediaExecutor] Resuming video task: ${task.id} (remoteId: ${videoId})`);
|
||||
|
||||
// 重新开始轮询
|
||||
const result = await pollVideoStatus(
|
||||
videoId,
|
||||
config.videoConfig,
|
||||
(progress) => {
|
||||
// 这里的 progress 是 0-1
|
||||
// 视频生成中,polling 阶段通常对应 10%-90%
|
||||
const mappedProgress = 10 + progress * 80;
|
||||
// taskStorageWriter.updateStatus 会写入 storage,触发 observer
|
||||
taskStorageWriter.updateStatus(task.id, TaskStatus.PROCESSING).catch(() => {});
|
||||
taskStorageWriter.updateProgress(task.id, mappedProgress).catch(() => {});
|
||||
}
|
||||
);
|
||||
|
||||
// 缓存远程 URL
|
||||
const cachedVidUrl = await cacheRemoteUrl(result.url, task.id, 'video', 'mp4');
|
||||
|
||||
const duration = task.params.duration as string | undefined;
|
||||
|
||||
// 完成任务
|
||||
await taskStorageWriter.completeTask(task.id, {
|
||||
url: cachedVidUrl,
|
||||
format: 'mp4',
|
||||
size: 0,
|
||||
duration: duration ? parseInt(duration, 10) : undefined,
|
||||
});
|
||||
|
||||
console.log(`[FallbackMediaExecutor] Resumed video task completed: ${task.id}`);
|
||||
} catch (error: any) {
|
||||
console.error(`[FallbackMediaExecutor] Failed to resume task ${task.id}:`, error);
|
||||
|
||||
await taskStorageWriter.failTask(task.id, {
|
||||
code: error.code || 'RESUME_FAILED',
|
||||
message: error.message || 'Failed to resume task'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 规范化 baseUrl,移除尾部 / 或 /v1,便于拼接 /v1/videos
|
||||
*/
|
||||
|
||||
Reference in new issue
Block a user