for loop removed and using video batch in endpoint

This commit is contained in:
bflatastic committed 2026-04-30 08:09:22 +07:00
1 parent f7c9e4ecc7
commit 8dcebec25c
6 files changed
+72 -18

No files matched your search

@@ -144,6 +144,7 @@ export function GenerationGalleryGrid({
modelLabel={entry.job.modelLabel}
progress={entry.job.progress}
estimatedTimeLeftMs={entry.job.estimatedTimeLeftMs}
batchCount={entry.job.batchCount}
/>
)}
{entry.kind === "failed" && (
@@ -10,6 +10,7 @@ export interface PendingCardProps {
prompt: string;
progress?: number;
estimatedTimeLeftMs?: number;
batchCount?: number;
}
const formatTimeLeft = (ms: number): string => {
@@ -29,6 +30,7 @@ export const PendingCard = memo(function PendingCard({
prompt,
progress,
estimatedTimeLeftMs,
batchCount,
}: PendingCardProps) {
const progressPercent =
progress != null ? Math.max(0, Math.min(100, Math.round(progress))) : null;
@@ -44,6 +46,11 @@ export const PendingCard = memo(function PendingCard({
return (
<div className="relative aspect-square w-full overflow-hidden rounded-lg bg-white/[0.03]">
<div className="animate-shimmer h-full w-full" />
{batchCount != null && batchCount > 1 && (
<div className="absolute left-2 right-2 top-2 z-10 rounded-md bg-black/60 px-2.5 py-1.5 text-center text-[10px] leading-snug text-white/70 backdrop-blur-sm">
Generating {batchCount} videos · Results may appear one at a time
</div>
)}
<div className="absolute inset-0 flex flex-col items-center justify-center gap-2">
<FontAwesomeIcon
icon={faSpinnerThird}
@@ -19,6 +19,7 @@ export interface InProgressJob {
progress: number;
estimatedTimeLeftMs?: number;
createdAt: string;
batchCount?: number;
}
export interface FailedJob {
@@ -21,6 +21,7 @@ export type VideoBatch = {
modelLabel: string;
jobToken?: string;
failureReason?: string;
batchCount?: number;
};
export type VideoInputMode = "keyframe" | "reference";
@@ -52,7 +53,7 @@ type CreateVideoState = {
setRefs: (patch: Partial<VideoRefsState>) => void;
setPendingRecreate: (payload: RecreatePayload | null) => void;
consumePendingRecreate: () => RecreatePayload | null;
startBatch: (prompt: string, modelLabel: string) => string;
startBatch: (prompt: string, modelLabel: string, batchCount?: number) => string;
setBatchJobToken: (batchId: string, jobToken: string) => void;
completeBatch: (batchId: string, video: GeneratedVideo) => void;
failBatch: (batchId: string, reason?: string) => void;
@@ -99,7 +100,7 @@ export const useCreateVideoStore = create<CreateVideoState>((set, get) => ({
return payload;
},
startBatch: (prompt, modelLabel) => {
startBatch: (prompt, modelLabel, batchCount) => {
const id = crypto.randomUUID();
const batch: VideoBatch = {
id,
@@ -107,6 +108,7 @@ export const useCreateVideoStore = create<CreateVideoState>((set, get) => ({
status: "pending",
createdAt: Date.now(),
modelLabel,
batchCount,
};
set((s) => ({ batches: [...s.batches, batch] }));
return id;
@@ -275,6 +275,7 @@ export default function CreateVideo() {
}, [charactersLoaded, storeSetCharacters, storeSetLoaded]);
// Batch store (enqueue flow only)
const batches = useCreateVideoStore((s) => s.batches);
const startBatch = useCreateVideoStore((s) => s.startBatch);
const setBatchJobToken = useCreateVideoStore((s) => s.setBatchJobToken);
const completeBatch = useCreateVideoStore((s) => s.completeBatch);
@@ -310,6 +311,26 @@ export default function CreateVideo() {
excludeUploads: true,
});
// Map job token → batch count so PendingCard can show "N videos generating"
const jobTokenToBatchCount = useMemo(() => {
const map = new Map<string, number>();
for (const batch of batches) {
if (batch.jobToken && batch.batchCount && batch.batchCount > 1) {
map.set(batch.jobToken, batch.batchCount);
}
}
return map;
}, [batches]);
const enrichedInProgress = useMemo(
() =>
jobs.inProgress.map((job) => {
const batchCount = jobTokenToBatchCount.get(job.id);
return batchCount ? { ...job, batchCount } : job;
}),
[jobs.inProgress, jobTokenToBatchCount],
);
const newlyCompletedTokens = useMemo(
() => new Set(jobs.newlyCompleted.map((i) => i.id)),
[jobs.newlyCompleted],
@@ -690,8 +711,20 @@ export default function CreateVideo() {
needsImage ||
!selectedModel
) {
console.log("[generate-video] blocked", {
hasPrompt: !!prompt.trim(),
isGenerating: isGeneratingRef.current,
needsImage,
hasModel: !!selectedModel,
});
return;
}
console.log("[generate-video] starting", {
model: selectedModel.model,
numVideos,
inputMode,
isReferenceMode,
});
isGeneratingRef.current = true;
setIsGenerating(true);
@@ -743,7 +776,7 @@ export default function CreateVideo() {
const baseParams = {
prompt: prompt.trim(),
model: selectedModel.model,
numVideos: 1,
numVideos,
aspectRatio: selectedSize,
duration: duration ?? selectedModel.duration_seconds_default ?? undefined,
resolution: hasResolutionOptions
@@ -767,45 +800,53 @@ export default function CreateVideo() {
: undefined,
referenceCharacterTokens,
};
console.log("[generate-video] params", baseParams);
// Enqueue each video as a separate job so they complete independently
const modelLabel = selectedModel.full_name ?? selectedModel.model;
const count = Math.max(1, numVideos);
const batchId = startBatch(prompt, modelLabel, numVideos > 1 ? numVideos : undefined);
for (let i = 0; i < count; i++) {
const batchId = startBatch(prompt, modelLabel);
try {
const result = await enqueueVideoGeneration(baseParams);
if (!result.success || !result.jobToken) {
failBatch(batchId, result.error ?? "Failed to start generation");
continue;
}
try {
console.log("[generate-video] enqueueing job...");
const result = await enqueueVideoGeneration(baseParams);
console.log("[generate-video] enqueue result", result);
if (!result.success || !result.jobToken) {
console.warn("[generate-video] enqueue failed", result.error);
failBatch(batchId, result.error ?? "Failed to start generation");
} else {
setBatchJobToken(batchId, result.jobToken);
console.log("[generate-video] polling started", {
jobToken: result.jobToken,
});
const stopPolling = startVideoPolling(
result.jobToken,
(video) => {
console.log("[generate-video] complete", {
batchId,
media_token: video.media_token,
});
completeBatch(batchId, video);
pollingCleanupsRef.current.delete(batchId);
window.dispatchEvent(new Event("task-queue-update"));
},
(reason) => {
console.warn("[generate-video] poll failed", { batchId, reason });
failBatch(batchId, reason);
pollingCleanupsRef.current.delete(batchId);
window.dispatchEvent(new Event("task-queue-update"));
},
);
pollingCleanupsRef.current.set(batchId, stopPolling);
} catch {
failBatch(batchId, "Network error - please try again");
}
} catch (err) {
console.error("[generate-video] unexpected error", err);
failBatch(batchId, "Network error - please try again");
}
window.dispatchEvent(new Event("credits-change"));
window.dispatchEvent(new Event("task-queue-update"));
console.log("[generate-video] done enqueuing");
setIsGenerating(false);
isGeneratingRef.current = false;
}, [
@@ -861,7 +902,7 @@ export default function CreateVideo() {
glowOrbs={videoGlowOrbs}
gridContent={
<GenerationGalleryGrid
inProgressJobs={jobs.inProgress}
inProgressJobs={enrichedInProgress}
failedJobs={jobs.failed}
onDismissFailed={jobs.dismissFailed}
newlyCompletedItems={jobs.newlyCompleted}
@@ -50,6 +50,8 @@ export async function enqueueVideoGeneration(
: null,
};
console.log("[generate-video] raw request body", body);
try {
const api = new OmniGenApi();
const response = await api.generateVideo(body);