// apps/viralcat.js import express from 'express'; import multer from 'multer'; import { generateCompletion } from '../ai_engine.js'; import { supabase } from '../config/supabaseClient.js'; import fetch from 'node-fetch'; const router = express.Router(); const FASTAPI_URL = process.env.FASTAPI_SERVER_URL || 'http://localhost:8000'; const upload = multer({ storage: multer.memoryStorage() }); const adminAuth = (req, res, next) => { const b64auth = (req.headers.authorization || '').split(' ')[1] || ''; const[login, password] = Buffer.from(b64auth, 'base64').toString().split(':'); if (login === 'admin' && password === process.env.VIRAL_CAT_ADMIN_PASS) return next(); res.set('WWW-Authenticate', 'Basic realm="Viral Cat Admin"'); res.status(401).send('Auth required.'); }; const getBase64Payload = (req) => { if (req.file) return req.file.buffer.toString('base64'); if (req.body.video_base64) return req.body.video_base64; return null; }; const handleMediaProcessing = async (req, num_frames = 14, get_transcript = true) => { const video_base64 = getBase64Payload(req); if (!video_base64) throw new Error("No video data found in request"); const mediaRes = await fetch(`${FASTAPI_URL}/process-video`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ video_base64, num_frames, get_transcript }) }); const mediaData = await mediaRes.json(); if (!mediaData.success) throw new Error(mediaData.error); return mediaData; }; // ── PARALLEL MAP-REDUCE WITH OPENROUTER ── const performDeepAnalysis = async (frames, transcript, targetNiche = null) => { console.log(`[DEEP ANALYZE] Starting Parallel Map-Reduce on ${frames.length} frames...`); // 1. Cap at 14 frames max const cappedFrames = frames.slice(0, 14); const chunks =[]; // 2. Chunk into batches of 7 (OpenRouter's max attachment limit) for (let i = 0; i < cappedFrames.length; i += 7) { chunks.push(cappedFrames.slice(i, i + 7)); } // 3. Process all chunks AT THE SAME TIME (No more sleep delays!) console.log(`-> Firing ${chunks.length} parallel worker instances to OpenRouter...`); const scenePromises = chunks.map((chunk, index) => { const prompt = `Analyze this ${chunk.length}-image sequence. Return ONLY a JSON object with a single key "description" containing a brief summary of the action, camera movement, lighting, and subjects.`; return generateCompletion({ model: "qwen", // Qwen is cheapest and fastest for this vision task prompt, system_prompt: "You are a precise video frame analyzer. Output strictly JSON.", images: chunk }); }); // Wait for all workers to finish simultaneously const sceneResults = await Promise.all(scenePromises); const sceneDescriptions = sceneResults.map((res, i) => { try { const parsed = JSON.parse(res.data); return `[Scene Segment ${i + 1}]:\n${parsed.description}`; } catch (e) { return `[Scene Segment ${i + 1}]:\n${res.data}`; // Fallback if JSON parse fails } }); const combinedVisualData = sceneDescriptions.join("\n\n"); console.log(`[DEEP ANALYZE] Map phase complete. Synthesizing output...`); if (targetNiche) { // 🚨 REDUCE PHASE: Custom Remix const finalPrompt = ` TASK: Rewrite an existing viral video script for a NEW NICHE. [ORIGINAL VISUAL TIMELINE] ${combinedVisualData}[ORIGINAL AUDIO TRANSCRIPT] ${transcript} [NEW TARGET NICHE] User's Niche: "${targetNiche}" INSTRUCTIONS: 1. DECONSTRUCT THE AURA: Analyze exactly what made the original video go viral. 2. Map those EXACT psychological triggers onto the user's new niche. 3. Return ONLY a JSON object with a single key "script_markdown" containing the fully formatted markdown script. Markdown format inside the JSON string should be: ## 🧠 The Viral Aura [Explanation] ## 🎬 The Hook (0-3s) **Visual:** [What to show] **Audio:** [What to say] ## 📖 The Body ...etc`; const result = await generateCompletion({ model: "maverick", prompt: finalPrompt, system_prompt: "You are Viral Cat 📈🐈. Output strictly JSON." }); return { success: true, data: JSON.parse(result.data).script_markdown }; } else { // 🚨 REDUCE PHASE: Database Template Extraction const finalPrompt = `[VISUAL TIMELINE]\n${combinedVisualData}\n[TRANSCRIPT]\n${transcript}\n Analyze the data and return ONLY a JSON object with these exact keys: { "transcript": "Cleaned dialogue", "environment": "Setting/lighting description", "pacing": "Edit style and camera movement", "viral_factors": "Analyze the psychology, specific word choices, hooks, and timing that made this viral" }`; return await generateCompletion({ model: "maverick", prompt: finalPrompt, system_prompt: "Elite metadata extractor. Output strictly JSON." }); } }; router.get('/trending', async (req, res) => { try { const page = parseInt(req.query.page) || 1; const limit = parseInt(req.query.limit) || 10; const search = req.query.search || ''; const from = (page - 1) * limit; const to = from + limit - 1; let query = supabase.from('viral_cat_trending').select('*', { count: 'exact' }); if (search) { query = query.or(`title.ilike.%${search}%,platform.ilike.%${search}%`); } const { data, count, error } = await query.order('created_at', { ascending: false }).range(from, to); if (error) throw error; const hasMore = (from + data.length) < count; res.json({ success: true, data, hasMore, total: count }); } catch (err) { res.status(500).json({ success: false, error: err.message }); } }); router.post('/admin/template', upload.single('video_file'), async (req, res) => { try { const { title, video_url, platform, use_deep_analysis } = req.body; const isDeep = (use_deep_analysis === 'true' || use_deep_analysis === true); // Max 14 frames for deep, 7 for fast (fits in 1 OpenRouter request) const framesToExtract = isDeep ? 14 : 7; const mediaData = await handleMediaProcessing(req, framesToExtract, true); let aiResult; if (isDeep) { aiResult = await performDeepAnalysis(mediaData.frames, mediaData.transcript); } else { const fastFrames = mediaData.frames.slice(0, 7); const aiPrompt = `Analyze transcript and frames.\nTRANSCRIPT:\n${mediaData.transcript}\n Return ONLY a JSON object with these exact keys: { "transcript": "Cleaned dialogue", "environment": "Setting/lighting description", "pacing": "Edit style and camera movement", "viral_factors": "Identify psychological hooks and word choices" }`; aiResult = await generateCompletion({ model: "maverick", prompt: aiPrompt, images: fastFrames, system_prompt: "Fast metadata extractor. Output strictly JSON." }); } // 🚨 CLEAN JSON PARSING (No more Regex!) const parsedData = JSON.parse(aiResult.data); const { error } = await supabase.from('viral_cat_trending').insert([{ platform, video_url, title, thumbnail_url: `data:image/jpeg;base64,${mediaData.thumbnail}`, transcript: parsedData.transcript || mediaData.transcript, ai_environment_data: parsedData.environment || "N/A", ai_scene_changes: parsedData.pacing || "N/A", ai_viral_factors: parsedData.viral_factors || "N/A" }]); if (error) throw error; res.json({ success: true }); } catch (err) { console.error("Admin Error:", err.message); res.status(500).json({ success: false, error: err.message }); } }); router.post('/remix', async (req, res) => { try { const { user_input, transcript, ai_environment_data, ai_scene_changes, ai_viral_factors } = req.body; const aiPrompt = `TASK: Rewrite script for a NEW NICHE. [ORIGINAL DATA] Pacing: ${ai_scene_changes} Environment: ${ai_environment_data} Viral Psychology (The Aura): ${ai_viral_factors} Transcript: ${transcript} [NEW NICHE] "${user_input}" INSTRUCTIONS: Map the original viral psychology and pacing onto the user's new niche. Return ONLY a JSON object with a single key "script_markdown" containing the fully formatted markdown script.`; const result = await generateCompletion({ model: "maverick", prompt: aiPrompt, system_prompt: "You are Viral Cat 📈🐈. Output strictly JSON." }); // Extract the markdown string from the JSON response const finalMarkdown = JSON.parse(result.data).script_markdown; res.json({ success: true, data: finalMarkdown }); } catch (err) { res.status(500).json({ success: false, error: err.message }); } }); router.post('/custom_remix', upload.single('video_file'), async (req, res) => { try { const { user_niche } = req.body; const mediaData = await handleMediaProcessing(req, 14, true); const result = await performDeepAnalysis(mediaData.frames, mediaData.transcript, user_niche); res.json({ success: true, data: result.data, thumbnail: mediaData.thumbnail }); } catch (err) { console.error("Custom Remix Error:", err.message); res.status(500).json({ success: false, error: err.message }); } }); router.delete('/admin/template/:id', async (req, res) => { await supabase.from('viral_cat_trending').delete().eq('id', req.params.id); res.json({ success: true }); }); // Admin Dashboard HTML (Unchanged) router.get('/admin', (req, res) => { res.send(`