mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-01 07:58:23 +00:00
for loop removed and using video batch in endpoint
This commit is contained in:
1 parent
f7c9e4ecc7
commit
8dcebec25c
6 files changed
+72
-18
No files matched your search
+1
@@ -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);
|
||||
|
||||
Reference in new issue
Block a user