feat(reels): add real-time generation progress tracking on dashboard (desktop & mobile)

This commit is contained in:
Michael committed 2026-02-12 10:33:00 +01:00
1 parent 11b5ef0ad2
commit e1c6b51e0a
7 files changed
+209 -6

No files matched your search

+18
View File
@@ -95,6 +95,24 @@ export async function migrate() {
);
`);
// posts.generation_status (Reel progress tracking)
await client.query(`
ALTER TABLE "posts"
ADD COLUMN IF NOT EXISTS "generation_status" text;
`);
// posts.generation_progress
await client.query(`
ALTER TABLE "posts"
ADD COLUMN IF NOT EXISTS "generation_progress" integer DEFAULT 0;
`);
// posts.generation_error
await client.query(`
ALTER TABLE "posts"
ADD COLUMN IF NOT EXISTS "generation_error" text;
`);
console.log("[Migration] Safe migration completed.");
} catch (error) {
console.error("[Migration] Error during migration:", error);
+44 -1
View File
@@ -417,7 +417,17 @@ async function processReelBackground(
console.log(`🔄 [Background] Starting processing for Post ${postId}`);
// Helper to update generation progress
const updateProgress = async (progress: number, status: string = 'processing') => {
try {
await storage.updatePostGenerationStatus(postId, status, progress);
} catch (e) {
console.error(`⚠️ [Background] Failed to update progress for ${postId}:`, e);
}
};
try {
await updateProgress(5);
// 1. Récupérer le média vidéo source
const media = await storage.getMediaById(videoMediaId);
if (!media || media.type !== 'video') {
@@ -441,6 +451,7 @@ async function processReelBackground(
});
// 3. Traiter la vidéo via FFmpeg
await updateProgress(15);
const startTime = Date.now();
const ffmpegResult = await ffmpegService.processReelFromUrl(media.originalUrl, {
text: overlayText,
@@ -461,6 +472,7 @@ async function processReelBackground(
}
// 4. Upload sur Cloudinary
await updateProgress(65);
console.log('☁️ [Background] Uploading to Cloudinary...');
const videoBuffer = Buffer.from(ffmpegResult.videoBase64, 'base64');
const cloudinaryResult = await cloudinaryService.uploadMedia(
@@ -486,8 +498,10 @@ async function processReelBackground(
// 6. Lier le média traité au Post existant
await storage.updatePostMedia(postId, [processedMedia.id]);
await updateProgress(85);
// 7. Publier sur les pages
await updateProgress(90);
const results: { pageId: string; success: boolean; reelId?: string; error?: string }[] = [];
for (const pageId of pageIds) {
@@ -562,6 +576,7 @@ async function processReelBackground(
await storage.updatePost(postId, {
status: finalStatus,
});
await updateProgress(100, 'completed');
console.log(`✅ [Background] Processing complete for Post ${postId}. Status: ${finalStatus}`);
@@ -570,6 +585,14 @@ async function processReelBackground(
await storage.updatePost(postId, {
status: 'failed',
});
try {
await storage.updatePostGenerationStatus(
postId, 'failed', 0,
error?.message || 'Erreur inconnue lors du traitement'
);
} catch (e) {
console.error(`⚠️ [Background] Failed to update error status for ${postId}:`, e);
}
}
}
@@ -612,8 +635,10 @@ reelsRouter.post('/reels', async (req: Request, res: Response) => {
userId: user.id,
content: description || overlayText || '',
aiGenerated: 'false',
status: scheduledFor ? 'scheduled' : 'draft', // 'draft' servira de 'processing' temporaire
status: scheduledFor ? 'scheduled' : 'draft',
scheduledFor: scheduledFor ? new Date(scheduledFor) : undefined,
generationStatus: 'processing',
generationProgress: 0,
});
console.log(`✨ Reel Request accepted. Post ID: ${post.id}. Starting background processing.`);
@@ -661,3 +686,21 @@ reelsRouter.get('/reels/config', async (req: Request, res: Response) => {
res.status(500).json({ error: 'Erreur lors de la récupération de la configuration' });
}
});
/**
* Reels en cours de génération
* GET /api/reels/ongoing
*/
reelsRouter.get('/reels/ongoing', async (req: Request, res: Response) => {
try {
const user = req.user as User;
const allPosts = await storage.getPosts(user.id);
const ongoing = allPosts.filter(
(p) => p.generationStatus === 'processing' || p.generationStatus === 'pending'
);
res.json(ongoing);
} catch (error) {
console.error('❌ Error fetching ongoing reels:', error);
res.status(500).json({ error: 'Erreur lors de la récupération des reels en cours' });
}
});
+23
View File
@@ -68,6 +68,7 @@ export interface IStorage {
getPostWithMedia(id: string): Promise<{ post: Post; media: Media[] } | undefined>;
createPost(post: InsertPost): Promise<Post>;
updatePost(id: string, post: Partial<InsertPost>): Promise<Post>;
updatePostGenerationStatus(id: string, status: string, progress: number, error?: string): Promise<Post>;
deletePost(id: string): Promise<void>;
updatePostMedia(postId: string, mediaIds: string[]): Promise<void>;
@@ -257,6 +258,28 @@ export class DatabaseStorage implements IStorage {
await db.delete(posts).where(eq(posts.id, id));
}
async updatePostGenerationStatus(
id: string,
status: string,
progress: number,
error?: string
): Promise<Post> {
const updateData: Record<string, unknown> = {
generationStatus: status,
generationProgress: progress,
updatedAt: new Date(),
};
if (error !== undefined) {
updateData.generationError = error;
}
const [updated] = await db
.update(posts)
.set(updateData)
.where(eq(posts.id, id))
.returning();
return updated;
}
async updatePostMedia(postId: string, mediaIds: string[]): Promise<void> {
await db.delete(postMedia).where(eq(postMedia.postId, postId));