everydaycats commited on
Commit
7e606a0
Β·
verified Β·
1 Parent(s): b07b173

Update apps/trend_cat.js

Browse files
Files changed (1) hide show
  1. apps/trend_cat.js +175 -187
apps/trend_cat.js CHANGED
@@ -1,258 +1,247 @@
1
  // apps/viral_cat.js
2
  import express from 'express';
3
  import { generateCompletion } from '../ai_engine.js';
4
- // import { createClient } from '@supabase/supabase-js';
 
5
 
6
  const router = express.Router();
7
- // const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_SERVICE_ROLE_KEY);
8
 
9
- // ── ADMIN AUTH ──
10
  const adminAuth = (req, res, next) => {
11
  const b64auth = (req.headers.authorization || '').split(' ')[1] || '';
12
- const[login, password] = Buffer.from(b64auth, 'base64').toString().split(':');
13
  if (login === 'admin' && password === process.env.VIRAL_CAT_ADMIN_PASS) return next();
14
  res.set('WWW-Authenticate', 'Basic realm="Viral Cat Admin"');
15
  res.status(401).send('Auth required.');
16
  };
17
 
18
- // ── 1. GET TRENDING FEED ──
19
  router.get('/trending', async (req, res) => {
20
  try {
21
- // const { data } = await supabase.from('viral_cat_trending').select('*').order('created_at', { ascending: false });
22
-
23
- // Mock data to build the UI today
24
- const mockData =[
25
- {
26
- id: '1',
27
- platform: 'tiktok',
28
- video_url: 'https://tiktok.com/@user/video/123',
29
- thumbnail_url: 'https://images.unsplash.com/photo-1611162617474-5b21e879e113?q=80&w=1000&auto=format&fit=crop',
30
- views: 12500000,
31
- comments: 45200,
32
- shares: 120000,
33
- transcript: "Here are 3 secret websites that feel illegal to know...",
34
- ai_environment_data: "Dark room, neon LED backlights, creator sitting close to camera.",
35
- ai_scene_changes: "Fast jump cuts every 2 seconds. Screen recordings overlayed.",
36
- }
37
- ];
38
- res.json({ success: true, data: mockData });
39
  } catch (err) {
40
  res.status(500).json({ success: false, error: err.message });
41
  }
42
  });
43
 
44
- // ── 2. THE CRAWLER & METADATA EXTRACTOR ──
45
- router.post('/crawl', adminAuth, async (req, res) => {
46
- const { video_url, frames_per_second = 2 } = req.body;
47
 
48
  try {
49
- console.log(`[CRAWLER] Initiating download for ${video_url}`);
50
-
51
- // TODO: Integrate yt-dlp to download video, Whisper for transcript.
52
- // DYNAMIC FRAME MATH LOGIC:
53
- // If a video is 10s long, and frames_per_second is 3 (start, middle, end)
54
- // We run FFmpeg to extract at timestamps: 0.0, 0.5, 1.0, 1.0, 1.5, 2.0...
 
 
55
 
56
- /*
57
- const duration = 10;
58
- const timestamps =[];
59
- for(let i=0; i<duration; i++) {
60
- if (frames_per_second === 2) timestamps.push(i + 0.1, i + 0.9);
61
- if (frames_per_second === 3) timestamps.push(i + 0.1, i + 0.5, i + 0.9);
62
- }
63
- // feed timestamps to FFmpeg
64
- */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
 
66
- res.json({ success: true, message: "Video crawled, parsed, and added to DB." });
67
  } catch (err) {
 
68
  res.status(500).json({ success: false, error: err.message });
69
  }
70
  });
71
 
72
- // ── 3. REMIX SCRIPT GENERATOR ──
73
- router.post('/remix', async (req, res) => {
74
- const { user_input, transcript, ai_environment_data, ai_scene_changes, model } = req.body;
75
-
76
- const systemPrompt = `You are Viral Cat πŸ“ˆπŸˆ, an elite social media strategist.
77
- The user wants to recreate a viral video for their own niche.
78
-
79
- ORIGINAL VIRAL VIDEO DATA:
80
- Transcript: ${transcript}
81
- Environment: ${ai_environment_data}
82
- Pacing/Cuts: ${ai_scene_changes}
83
-
84
- USER'S NICHE/IDEA: "${user_input}"
85
-
86
- Task: Write a shot-by-shot script that maps the pacing, hooks, and visual structure of the original viral video onto the user's new niche.
87
- Format in clean Markdown:
88
- ## 🎬 The Hook (0-3s)
89
- ## πŸ“– The Body
90
- ## πŸ’₯ The Call to Action`;
91
-
92
  try {
93
- const result = await generateCompletion({
94
- model: model || "maverick",
95
- prompt: "Generate the customized remix script.",
96
- system_prompt: systemPrompt,
97
- images:[]
98
- });
99
- res.json(result);
100
  } catch (err) {
101
  res.status(500).json({ success: false, error: err.message });
102
  }
103
  });
104
 
105
- // ── 4. RATING ENDPOINT (Returns data for Local Storage) ──
106
- router.post('/rate', async (req, res) => {
107
- const { media_data, model } = req.body;
108
- // media_data is an array of base64 frames
109
-
110
- const systemPrompt = `You are Viral Cat. Analyze these frames.
111
- Format STRICTLY:
112
- [SCORE: 0-100]
113
- ## Vibe Check
114
- [Roast/Feedback here]
115
- ## Fixes
116
- [3 tips]`;
117
 
118
  try {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
119
  const result = await generateCompletion({
120
  model: model || "maverick",
121
- prompt: "Rate my content.",
122
  system_prompt: systemPrompt,
123
- images: media_data
124
  });
125
- // Note: We DO NOT save to Supabase here. It goes back to the app to be saved locally.
126
  res.json(result);
127
  } catch (err) {
128
  res.status(500).json({ success: false, error: err.message });
129
  }
130
  });
131
 
132
-
133
-
134
- // ── 4. ADMIN DASHBOARD (EMBEDDED HTML) ──
135
  router.get('/admin', adminAuth, (req, res) => {
136
- // A sleek dashboard to upload new viral templates to the DB and view app usage
137
  const html = `
138
  <!DOCTYPE html>
139
  <html lang="en">
140
  <head>
141
- <meta charset="UTF-8">
142
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
143
  <title>Viral Cat | Creator Admin</title>
144
  <script src="https://cdn.tailwindcss.com"></script>
145
  <style>
146
  body { background-color: #090A0F; color: #FFFFFF; font-family: 'Inter', sans-serif; }
147
  .neon-text { color: #12D8C3; text-shadow: 0 0 10px rgba(18, 216, 195, 0.4); }
148
- .glass-card { background: #16181F; border: 1px solid #2D3748; border-radius: 20px; box-shadow: 0 10px 30px rgba(0,0,0,0.5); }
149
- .btn-cyan { background-color: #12D8C3; color: #000; font-weight: bold; border-radius: 10px; padding: 10px 20px; transition: 0.2s; }
150
- .btn-cyan:hover { background-color: #0EBAA8; }
151
- input, select { background: #090A0F; border: 1px solid #2D3748; color: white; padding: 10px; border-radius: 10px; width: 100%; margin-top: 5px; }
152
  </style>
153
  </head>
154
- <body class="p-8">
155
- <div class="max-w-6xl mx-auto">
156
-
157
- <!-- Header -->
158
- <div class="flex items-center justify-between mb-10">
159
- <div class="flex items-center gap-4">
160
- <div class="w-12 h-12 rounded-full flex items-center justify-center bg-[#12D8C3] bg-opacity-20">
161
- <span class="text-2xl">πŸ“ˆ</span>
162
- </div>
163
- <div>
164
- <h1 class="text-3xl font-bold">Viral Cat <span class="neon-text">Creator Admin</span></h1>
165
- <p class="text-gray-400 text-sm">Manage Templates & View Stats</p>
166
- </div>
167
- </div>
 
 
 
 
 
 
 
 
 
168
  </div>
169
 
170
- <div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
171
- <!-- Stats Cards -->
172
- <div class="glass-card p-6">
173
- <h3 class="text-gray-400 font-semibold mb-2">Total AI Ratings</h3>
174
- <p class="text-4xl font-bold neon-text">1,204</p>
175
- </div>
176
- <div class="glass-card p-6">
177
- <h3 class="text-gray-400 font-semibold mb-2">Videos Remixed</h3>
178
- <p class="text-4xl font-bold neon-text">843</p>
179
- </div>
180
- <div class="glass-card p-6">
181
- <h3 class="text-gray-400 font-semibold mb-2">Active Templates</h3>
182
- <p class="text-4xl font-bold neon-text">42</p>
183
- </div>
184
- </div>
185
-
186
- <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
187
- <!-- Add New Viral Template Form -->
188
- <div class="glass-card p-8">
189
- <h2 class="text-2xl font-bold mb-6 border-b border-gray-700 pb-4">Add Viral Template</h2>
190
- <form id="templateForm" onsubmit="addTemplate(event)">
191
- <div class="mb-4">
192
- <label class="text-sm text-gray-400">Video Title / Trend Name</label>
193
- <input type="text" id="title" placeholder="e.g., Pedro Pedro Raccoon" required />
194
- </div>
195
- <div class="mb-4">
196
- <label class="text-sm text-gray-400">Video URL (S3, Mux, Cloudinary)</label>
197
- <input type="url" id="video_url" placeholder="https://..." required />
198
- </div>
199
- <div class="grid grid-cols-2 gap-4 mb-4">
200
- <div>
201
- <label class="text-sm text-gray-400">Niche</label>
202
- <select id="niche">
203
- <option value="meme">Meme / Comedy</option>
204
- <option value="dance">Dance</option>
205
- <option value="tech">Tech / Education</option>
206
- <option value="lifestyle">Lifestyle / Vlog</option>
207
- </select>
208
- </div>
209
- <div>
210
- <label class="text-sm text-gray-400">Virality Score (Base)</label>
211
- <input type="number" id="score" value="95" min="1" max="100" />
212
- </div>
213
- </div>
214
- <button type="submit" class="btn-cyan w-full mt-4">Publish Template to Database</button>
215
- </form>
216
- </div>
217
-
218
- <!-- Recent Ratings Feed -->
219
- <div class="glass-card p-8">
220
- <h2 class="text-2xl font-bold mb-6 border-b border-gray-700 pb-4">Recent AI Ratings</h2>
221
- <div class="space-y-4 overflow-y-auto h-80">
222
- <!-- Mocked Feed -->
223
- <div class="p-4 bg-gray-800 bg-opacity-50 rounded-xl border border-gray-700">
224
- <div class="flex justify-between items-center mb-2">
225
- <span class="text-xs bg-[#12D8C3] text-black px-2 py-1 rounded font-bold">Video Rated</span>
226
- <span class="text-gray-400 text-xs">2 mins ago</span>
227
- </div>
228
- <p class="text-sm">Score: <span class="neon-text font-bold">88/100</span></p>
229
- <p class="text-xs text-gray-400 mt-1 line-clamp-2">"The hook is incredibly strong, but the lighting drops in frame 3..."</p>
230
- </div>
231
- <div class="p-4 bg-gray-800 bg-opacity-50 rounded-xl border border-gray-700">
232
- <div class="flex justify-between items-center mb-2">
233
- <span class="text-xs bg-blue-500 text-white px-2 py-1 rounded font-bold">Remix Created</span>
234
- <span class="text-gray-400 text-xs">15 mins ago</span>
235
- </div>
236
- <p class="text-sm">Style: <span class="font-bold">Green Screen</span></p>
237
- <p class="text-xs text-gray-400 mt-1">Template: Pedro Pedro Raccoon</p>
238
- </div>
239
- </div>
240
  </div>
241
  </div>
242
  </div>
243
 
244
  <script>
245
- function addTemplate(e) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
246
  e.preventDefault();
247
- const btn = e.target.querySelector('button');
248
- btn.innerText = "Adding...";
249
- // TODO: Make POST request to an /api/admin/template endpoint to save to Supabase
250
- setTimeout(() => {
251
- alert("Template added to database!");
252
- btn.innerText = "Publish Template to Database";
 
 
 
 
 
 
 
 
 
 
 
 
253
  e.target.reset();
254
- }, 1000);
 
 
 
 
 
 
 
 
 
 
 
255
  }
 
 
256
  </script>
257
  </body>
258
  </html>
@@ -260,5 +249,4 @@ router.get('/admin', adminAuth, (req, res) => {
260
  res.send(html);
261
  });
262
 
263
-
264
  export default router;
 
1
  // apps/viral_cat.js
2
  import express from 'express';
3
  import { generateCompletion } from '../ai_engine.js';
4
+ import { supabase } from '../config/supabaseClient.js';
5
+ import fetch from 'node-fetch'; // Ensure you have node-fetch or use native fetch if Node 18+
6
 
7
  const router = express.Router();
8
+ const FASTAPI_URL = process.env.FASTAPI_SERVER_URL || 'http://localhost:8000';
9
 
10
+ // ── ADMIN AUTH MIDDLEWARE ──
11
  const adminAuth = (req, res, next) => {
12
  const b64auth = (req.headers.authorization || '').split(' ')[1] || '';
13
+ const [login, password] = Buffer.from(b64auth, 'base64').toString().split(':');
14
  if (login === 'admin' && password === process.env.VIRAL_CAT_ADMIN_PASS) return next();
15
  res.set('WWW-Authenticate', 'Basic realm="Viral Cat Admin"');
16
  res.status(401).send('Auth required.');
17
  };
18
 
19
+ // ── 1. GET TRENDING FEED (Public for App) ──
20
  router.get('/trending', async (req, res) => {
21
  try {
22
+ const { data, error } = await supabase
23
+ .from('viral_cat_trending')
24
+ .select('*')
25
+ .order('created_at', { ascending: false });
26
+
27
+ if (error) throw error;
28
+ res.json({ success: true, data });
 
 
 
 
 
 
 
 
 
 
 
29
  } catch (err) {
30
  res.status(500).json({ success: false, error: err.message });
31
  }
32
  });
33
 
34
+ // ── 2. ADMIN: ADD TEMPLATE (Manual or Crawler) ──
35
+ router.post('/admin/template', adminAuth, async (req, res) => {
36
+ const { title, video_url, platform, niche, score } = req.body;
37
 
38
  try {
39
+ // Step A: Send to FastAPI to download, extract frames, and transcribe
40
+ console.log(`[CRAWLER] Sending ${video_url} to FastAPI Worker...`);
41
+ const mediaRes = await fetch(`${FASTAPI_URL}/process-video`, {
42
+ method: 'POST',
43
+ headers: { 'Content-Type': 'application/json' },
44
+ body: JSON.stringify({ url: video_url, platform })
45
+ });
46
+ const mediaData = await mediaRes.json();
47
 
48
+ // Expected from FastAPI: { frames: ["base64..."], transcript: "...", thumbnail_url: "..." }
49
+ const { frames, transcript, thumbnail_url } = mediaData;
50
+
51
+ // Step B: Send frames to Llama 4 to analyze pacing and environment
52
+ const aiPrompt = `Analyze these frames from a viral video. Provide exactly two paragraphs:
53
+ Environment:[Describe lighting, setting, subjects]
54
+ Pacing: [Describe cuts, camera movement, visual hooks]`;
55
+
56
+ const aiResult = await generateCompletion({
57
+ model: "maverick",
58
+ prompt: aiPrompt,
59
+ system_prompt: "You are an elite video metadata extractor.",
60
+ images: frames
61
+ });
62
+
63
+ // Step C: Save everything to Supabase
64
+ const { data, error } = await supabase.from('viral_cat_trending').insert([{
65
+ platform,
66
+ video_url,
67
+ thumbnail_url: thumbnail_url || "https://placehold.co/400x600/png",
68
+ views: Math.floor(Math.random() * 5000000) + 100000, // Mock stats for now
69
+ comments: Math.floor(Math.random() * 10000),
70
+ shares: Math.floor(Math.random() * 50000),
71
+ transcript: transcript || "No transcript available.",
72
+ ai_environment_data: aiResult.data,
73
+ ai_scene_changes: "Dynamic framing extracted by AI",
74
+ }]).select();
75
+
76
+ if (error) throw error;
77
+ res.json({ success: true, data });
78
 
 
79
  } catch (err) {
80
+ console.error(err);
81
  res.status(500).json({ success: false, error: err.message });
82
  }
83
  });
84
 
85
+ // ── 3. ADMIN: DELETE TEMPLATE ──
86
+ router.delete('/admin/template/:id', adminAuth, async (req, res) => {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
  try {
88
+ const { error } = await supabase.from('viral_cat_trending').delete().eq('id', req.params.id);
89
+ if (error) throw error;
90
+ res.json({ success: true, message: "Deleted successfully" });
 
 
 
 
91
  } catch (err) {
92
  res.status(500).json({ success: false, error: err.message });
93
  }
94
  });
95
 
96
+ // ── 4. CUSTOM REMIX (User inputs ANY link -> FastAPI -> Llama 4) ──
97
+ router.post('/custom_remix', async (req, res) => {
98
+ const { video_url, platform, user_niche, model } = req.body;
 
 
 
 
 
 
 
 
 
99
 
100
  try {
101
+ console.log(`[CUSTOM REMIX] Crawling user requested URL: ${video_url}`);
102
+
103
+ // 1. Call FastAPI Media Server
104
+ const mediaRes = await fetch(`${FASTAPI_URL}/process-video`, {
105
+ method: 'POST',
106
+ headers: { 'Content-Type': 'application/json' },
107
+ body: JSON.stringify({ url: video_url, platform })
108
+ });
109
+ const { frames, transcript } = await mediaRes.json();
110
+
111
+ // 2. Feed to Llama 4 Maverick to write the script
112
+ const systemPrompt = `You are Viral Cat πŸ“ˆπŸˆ. The user wants to remix an external viral video.
113
+ ORIGINAL TRANSCRIPT: ${transcript}
114
+ USER'S NEW NICHE: "${user_niche}"
115
+
116
+ Look at the attached frames to understand the visual pacing.
117
+ Write a highly viral, customized shooting script mapped to the user's niche.
118
+ Format in Markdown:
119
+ ## 🎬 The Hook (0-3s)
120
+ ## πŸ“– The Body
121
+ ## πŸ’₯ The Call to Action`;
122
+
123
  const result = await generateCompletion({
124
  model: model || "maverick",
125
+ prompt: "Write the custom remix script.",
126
  system_prompt: systemPrompt,
127
+ images: frames // Pass the dynamic frames straight to the vision model!
128
  });
129
+
130
  res.json(result);
131
  } catch (err) {
132
  res.status(500).json({ success: false, error: err.message });
133
  }
134
  });
135
 
136
+ // ── 5. ADMIN DASHBOARD (Fully Functional) ──
 
 
137
  router.get('/admin', adminAuth, (req, res) => {
 
138
  const html = `
139
  <!DOCTYPE html>
140
  <html lang="en">
141
  <head>
142
+ <meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
 
143
  <title>Viral Cat | Creator Admin</title>
144
  <script src="https://cdn.tailwindcss.com"></script>
145
  <style>
146
  body { background-color: #090A0F; color: #FFFFFF; font-family: 'Inter', sans-serif; }
147
  .neon-text { color: #12D8C3; text-shadow: 0 0 10px rgba(18, 216, 195, 0.4); }
148
+ .glass-card { background: #16181F; border: 1px solid #2D3748; border-radius: 20px; padding: 24px; }
149
+ .btn-cyan { background-color: #12D8C3; color: #000; font-weight: bold; border-radius: 10px; padding: 10px; cursor: pointer; }
150
+ input, select { background: #090A0F; border: 1px solid #2D3748; color: white; padding: 10px; border-radius: 10px; width: 100%; margin-bottom: 15px; }
 
151
  </style>
152
  </head>
153
+ <body class="p-8 max-w-6xl mx-auto">
154
+ <h1 class="text-3xl font-bold mb-8">Viral Cat <span class="neon-text">Admin</span></h1>
155
+
156
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-8">
157
+ <!-- UPLOAD FORM -->
158
+ <div class="glass-card">
159
+ <h2 class="text-2xl font-bold mb-4 border-b border-gray-700 pb-2">Add Viral Template</h2>
160
+ <form id="templateForm" onsubmit="addTemplate(event)">
161
+ <label>Video Title</label>
162
+ <input type="text" id="title" required />
163
+
164
+ <label>Video URL (TikTok/IG/YT)</label>
165
+ <input type="url" id="video_url" required />
166
+
167
+ <label>Platform</label>
168
+ <select id="platform">
169
+ <option value="tiktok">TikTok</option>
170
+ <option value="instagram">Instagram Reels</option>
171
+ <option value="youtube">YouTube Shorts</option>
172
+ </select>
173
+
174
+ <button type="submit" id="submitBtn" class="btn-cyan w-full mt-4">Crawl & Add to Database</button>
175
+ </form>
176
  </div>
177
 
178
+ <!-- TEMPLATE LIST -->
179
+ <div class="glass-card">
180
+ <h2 class="text-2xl font-bold mb-4 border-b border-gray-700 pb-2">Active Templates</h2>
181
+ <div id="templateList" class="space-y-4 overflow-y-auto h-96">
182
+ <p class="text-gray-400">Loading...</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
183
  </div>
184
  </div>
185
  </div>
186
 
187
  <script>
188
+ // Automatically fetch active templates on load
189
+ async function loadTemplates() {
190
+ const res = await fetch('/viral_cat/trending');
191
+ const json = await res.json();
192
+ const list = document.getElementById('templateList');
193
+ list.innerHTML = '';
194
+
195
+ if(json.data.length === 0) list.innerHTML = '<p>No templates found.</p>';
196
+
197
+ json.data.forEach(t => {
198
+ list.innerHTML += \`
199
+ <div class="bg-gray-800 p-4 rounded-xl flex justify-between items-center border border-gray-700">
200
+ <div>
201
+ <span class="text-xs bg-[#12D8C3] text-black px-2 py-1 rounded font-bold">\${t.platform.toUpperCase()}</span>
202
+ <p class="text-sm mt-2 font-bold truncate w-48">\${t.transcript.substring(0, 30)}...</p>
203
+ </div>
204
+ <button onclick="deleteTemplate('\${t.id}')" class="bg-red-500 hover:bg-red-600 text-white text-xs px-3 py-2 rounded">Delete</button>
205
+ </div>\`;
206
+ });
207
+ }
208
+
209
+ async function addTemplate(e) {
210
  e.preventDefault();
211
+ const btn = document.getElementById('submitBtn');
212
+ btn.innerText = "Crawling & Processing (Takes 10-20s)...";
213
+ btn.disabled = true;
214
+
215
+ const payload = {
216
+ title: document.getElementById('title').value,
217
+ video_url: document.getElementById('video_url').value,
218
+ platform: document.getElementById('platform').value,
219
+ };
220
+
221
+ const res = await fetch('/viral_cat/admin/template', {
222
+ method: 'POST',
223
+ headers: { 'Content-Type': 'application/json' },
224
+ body: JSON.stringify(payload)
225
+ });
226
+
227
+ if(res.ok) {
228
+ alert("Success! Video processed by FastAPI, analyzed by Llama, and saved to DB.");
229
  e.target.reset();
230
+ loadTemplates();
231
+ } else {
232
+ alert("Error processing video.");
233
+ }
234
+ btn.innerText = "Crawl & Add to Database";
235
+ btn.disabled = false;
236
+ }
237
+
238
+ async function deleteTemplate(id) {
239
+ if(!confirm("Are you sure?")) return;
240
+ await fetch(\`/viral_cat/admin/template/\${id}\`, { method: 'DELETE' });
241
+ loadTemplates();
242
  }
243
+
244
+ loadTemplates();
245
  </script>
246
  </body>
247
  </html>
 
249
  res.send(html);
250
  });
251
 
 
252
  export default router;