Skip to content

Commit 1ed1261

Browse files
authored
Merge pull request #1809 from gitroomhq/fix/video-generation-hang
Fix AI video generation hanging forever when the provider reports failure
2 parents e287a14 + 8800814 commit 1ed1261

3 files changed

Lines changed: 63 additions & 24 deletions

File tree

apps/frontend/src/components/launches/ai.video.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,15 @@ export const Modal: FC<{
6060

6161
if (image.status == 200 || image.status == 201) {
6262
onChange(await image.json());
63+
} else {
64+
toaster.show('Video generation failed', 'warning');
6365
}
64-
} catch (e) {}
66+
} catch (e) {
67+
toaster.show(
68+
'Video generation failed or timed out — if it completes, it will appear in your media library',
69+
'warning'
70+
);
71+
}
6572

6673
setLocked(false);
6774
setLoading(false);

libraries/nestjs-libraries/src/chat/tools/generate.video.tool.ts

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -55,26 +55,35 @@ export class GenerateVideoTool implements AgentToolInterface {
5555
),
5656
}),
5757
outputSchema: z.object({
58-
url: z.string(),
58+
url: z.string().optional(),
59+
error: z.string().optional(),
5960
}),
6061
execute: async (inputData, context) => {
6162
checkAuth(inputData, context);
6263
const org = JSON.parse((context?.requestContext as any)?.get('organization') as string);
63-
const value = await this._mediaService.generateVideo(org, {
64-
type: inputData.identifier,
65-
output: inputData.output,
66-
customParams: inputData.customParams.reduce(
67-
(all: Record<string, any>, current: { key: string; value: any }) => ({
68-
...all,
69-
[current.key]: current.value,
70-
}),
71-
{} as Record<string, any>
72-
),
73-
});
64+
try {
65+
const value = await this._mediaService.generateVideo(org, {
66+
type: inputData.identifier,
67+
output: inputData.output,
68+
customParams: inputData.customParams.reduce(
69+
(all: Record<string, any>, current: { key: string; value: any }) => ({
70+
...all,
71+
[current.key]: current.value,
72+
}),
73+
{} as Record<string, any>
74+
),
75+
});
7476

75-
return {
76-
url: value.path,
77-
};
77+
return {
78+
url: value.path,
79+
};
80+
} catch (err) {
81+
return {
82+
error: `Video generation failed: ${
83+
err instanceof Error ? err.message : String(err)
84+
}. The user's video credit was not used.`,
85+
};
86+
}
7887
},
7988
});
8089
}

libraries/nestjs-libraries/src/videos/veo3/veo3.ts

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ export class Veo3 extends VideoAbstract<Veo3Params> {
4848
Authorization: `Bearer ${process.env.KIEAI_API_KEY}`,
4949
},
5050
method: 'POST',
51+
signal: AbortSignal.timeout(30000),
5152
body: JSON.stringify({
5253
prompt: customParams.prompt,
5354
imageUrls: customParams?.images?.map((p) => p.path) || [],
@@ -58,12 +59,18 @@ export class Veo3 extends VideoAbstract<Veo3Params> {
5859
).json();
5960

6061
if (value.code !== 200 && value.code !== 201) {
61-
throw new Error(`Failed to generate video`);
62+
throw new Error(value?.msg || `Failed to generate video`);
6263
}
6364

6465
const taskId = value.data.taskId;
65-
let videoUrl = [];
66-
while (videoUrl.length === 0) {
66+
console.log('veo3 taskId', taskId);
67+
let attempts = 0;
68+
const maxAttempts = 180; // ~30 minutes at 10s interval
69+
while (true) {
70+
if (attempts++ >= maxAttempts) {
71+
throw new Error('Video generation timed out');
72+
}
73+
6774
console.log('waiting for video to be ready');
6875
const data = await (
6976
await fetch(
@@ -73,18 +80,34 @@ export class Veo3 extends VideoAbstract<Veo3Params> {
7380
'Content-Type': 'application/json',
7481
Authorization: `Bearer ${process.env.KIEAI_API_KEY}`,
7582
},
83+
signal: AbortSignal.timeout(30000),
7684
}
7785
)
7886
).json();
7987

80-
if (data.code !== 200 && data.code !== 400) {
81-
throw new Error(`Failed to get video info`);
88+
if (data.code !== 200) {
89+
throw new Error(data?.msg || `Failed to get video info`);
90+
}
91+
92+
// successFlag: 0 = generating, 1 = success, anything else = failed
93+
const successFlag = data?.data?.successFlag;
94+
if (successFlag !== 0 && successFlag !== 1) {
95+
throw new Error(
96+
data?.data?.errorMessage ||
97+
`Video generation failed (status ${successFlag})`
98+
);
99+
}
100+
101+
const videoUrl = data?.data?.response?.resultUrls || [];
102+
if (videoUrl.length > 0) {
103+
return videoUrl[0];
104+
}
105+
106+
if (successFlag === 1) {
107+
throw new Error('Video generation succeeded but no video URL returned');
82108
}
83109

84-
videoUrl = data?.data?.response?.resultUrls || [];
85110
await timer(10000);
86111
}
87-
88-
return videoUrl[0];
89112
}
90113
}

0 commit comments

Comments
 (0)