feat(agent): add task status and client-scoped operations

This commit is contained in:
yu
2026-07-17 18:30:10 +08:00
parent bdca6b0a5c
commit 062e4569aa
13 changed files with 407 additions and 39 deletions
+29 -11
View File
@@ -97,7 +97,9 @@ export default function VideoPage() {
const [autoRunToken, setAutoRunToken] = useState(0);
const videoCommand = useWorkbenchAgentStore((state) => state.videoCommand);
const clearVideoCommand = useWorkbenchAgentStore((state) => state.clearVideoCommand);
const updateAgentTask = useWorkbenchAgentStore((state) => state.updateTask);
const processedCommandRef = useRef(0);
const agentTaskIdRef = useRef<string | undefined>(undefined);
const model = effectiveConfig.videoModel || effectiveConfig.model;
const canGenerate = Boolean(prompt.trim());
@@ -170,10 +172,16 @@ export default function VideoPage() {
}
};
const generate = async () => {
const agentTaskId = agentTaskIdRef.current;
agentTaskIdRef.current = undefined;
const snapshot = buildRequestSnapshot();
if (!snapshot) return;
if (!snapshot) {
if (agentTaskId) updateAgentTask(agentTaskId, { status: "failed", error: "视频生成参数无效" });
return;
}
setElapsedMs(0);
setRunning(true);
if (agentTaskId) updateAgentTask(agentTaskId, { status: "running", error: undefined });
setPreviewLog(null);
setResults([{ id: nanoid(), status: "pending" }]);
const batchStartedAt = performance.now();
@@ -181,11 +189,12 @@ export default function VideoPage() {
try {
const task = await createVideoGenerationTask(snapshot.config, snapshot.text, snapshot.references, snapshot.videoReferences, snapshot.audioReferences);
const log = buildLog({ prompt: snapshot.text, model, config: snapshot.config, references: snapshot.references, videoReferences: snapshot.videoReferences, audioReferences: snapshot.audioReferences, durationMs: 0, status: "生成中", task });
await saveLog(log);
void pollGenerationLog(log, snapshot.config);
await saveLog(log, false);
void pollGenerationLog(log, snapshot.config, agentTaskId);
} catch (error) {
const errorMessage = error instanceof Error ? error.message : "生成失败";
setResults([{ id: nanoid(), status: "failed", error: errorMessage }]);
if (agentTaskId) updateAgentTask(agentTaskId, { status: "failed", successCount: 0, failCount: 1, error: errorMessage });
await saveLog(buildLog({ prompt: snapshot.text, model, config: snapshot.config, references: snapshot.references, videoReferences: snapshot.videoReferences, audioReferences: snapshot.audioReferences, durationMs: performance.now() - batchStartedAt, status: "失败", error: errorMessage }));
message.error(errorMessage);
setRunning(false);
@@ -198,8 +207,15 @@ export default function VideoPage() {
processedCommandRef.current = videoCommand.nonce;
clearVideoCommand();
if (typeof videoCommand.prompt === "string") setPrompt(videoCommand.prompt);
if (videoCommand.run && !running) setAutoRunToken((value) => value + 1);
}, [videoCommand, clearVideoCommand, running]);
if (videoCommand.run && running) {
if (videoCommand.taskId) updateAgentTask(videoCommand.taskId, { status: "failed", error: "视频工作台已有任务正在运行" });
return;
}
if (videoCommand.run) {
agentTaskIdRef.current = videoCommand.taskId;
setAutoRunToken((value) => value + 1);
}
}, [videoCommand, clearVideoCommand, running, updateAgentTask]);
useEffect(() => {
if (!autoRunToken) return;
@@ -276,7 +292,7 @@ export default function VideoPage() {
.filter((log) => selectedLogIds.includes(log.id))
.map((log) => log.video?.storageKey)
.filter((key): key is string => Boolean(key));
void Promise.all([deleteStoredMedia(mediaKeys), ...selectedLogIds.map((id) => logStore.removeItem(id))]).then(refreshLogs);
void Promise.all([deleteStoredMedia(mediaKeys), ...selectedLogIds.map((id) => logStore.removeItem(id))]).then(() => refreshLogs());
if (previewLog && selectedLogIds.includes(previewLog.id)) {
setPreviewLog(null);
setResults([]);
@@ -285,15 +301,15 @@ export default function VideoPage() {
setDeleteConfirmOpen(false);
};
const saveLog = async (log: GenerationLog) => {
const saveLog = async (log: GenerationLog, resumePending = true) => {
await logStore.setItem(log.id, serializeLog(log));
await refreshLogs();
await refreshLogs(resumePending);
};
const refreshLogs = async () => {
const refreshLogs = async (resumePending = true) => {
const nextLogs = await readStoredLogs();
setLogs(nextLogs);
resumePendingLogs(nextLogs);
if (resumePending) resumePendingLogs(nextLogs);
return nextLogs;
};
@@ -303,7 +319,7 @@ export default function VideoPage() {
}
};
const pollGenerationLog = async (log: GenerationLog, configOverride?: AiConfig) => {
const pollGenerationLog = async (log: GenerationLog, configOverride?: AiConfig, agentTaskId?: string) => {
if (!log.task || activeLogIdsRef.current.has(log.id)) return;
activeLogIdsRef.current.add(log.id);
setRunning(true);
@@ -326,6 +342,7 @@ export default function VideoPage() {
mimeType: stored.mimeType,
};
setResults([{ id: nextVideo.id, status: "success", video: nextVideo }]);
if (agentTaskId) updateAgentTask(agentTaskId, { status: "succeeded", successCount: 1, failCount: 0, error: undefined });
await saveLog({ ...log, status: "成功", durationMs: nextVideo.durationMs, video: nextVideo, error: undefined });
message.success("视频已生成");
return;
@@ -337,6 +354,7 @@ export default function VideoPage() {
} catch (error) {
const errorMessage = error instanceof Error ? error.message : "生成失败";
setResults([{ id: log.id, status: "failed", error: errorMessage }]);
if (agentTaskId) updateAgentTask(agentTaskId, { status: "failed", successCount: 0, failCount: 1, error: errorMessage });
await saveLog({ ...log, status: "失败", durationMs: Date.now() - log.createdAt, error: errorMessage });
message.error(errorMessage);
} finally {