Gaurav vashistha commited on
Commit
72ade06
·
1 Parent(s): fef4fb4

feat(frontend): implemented history gallery, session persistence, and playback controls

Browse files
Files changed (1) hide show
  1. stitch_continuity_dashboard/code.html +348 -402
stitch_continuity_dashboard/code.html CHANGED
@@ -1,424 +1,370 @@
1
  <!DOCTYPE html>
2
  <html class="dark" lang="en">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
 
4
- <head>
5
- <meta charset="UTF-8">
6
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
- <title>Continuity</title>
8
- <script src="https://cdn.tailwindcss.com"></script>
9
- <link
10
- href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;700&family=Space+Grotesk:wght@300;400;500;600;700&display=swap"
11
- rel="stylesheet">
12
- <link
13
- href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200"
14
- rel="stylesheet" />
15
- <script>
16
- tailwind.config = {
17
- darkMode: 'class',
18
- theme: {
19
- extend: {
20
- colors: {
21
- primary: '#6b0bc9', // Deep Purple
22
- secondary: '#10b981', // Emerald
23
- background: '#0d0612', // Very Dark Purple/Black
24
- surface: '#1c1326', // Dark Purple Surface
25
- 'surface-dark': '#130a1a',
26
- 'border-dark': 'rgba(255,255,255,0.05)',
27
- accent: '#d946ef', // Fuchsia
28
- },
29
- fontFamily: {
30
- sans: ['Outfit', 'sans-serif'],
31
- display: ['Space Grotesk', 'sans-serif'],
32
- },
33
- boxShadow: {
34
- 'neon': '0 0 20px rgba(107, 11, 201, 0.3)',
35
- 'glow': '0 0 15px rgba(217, 70, 239, 0.4)',
36
- }
37
- }
38
- }
39
- }
40
- </script>
41
- <style>
42
- body {
43
- background-color: #0d0612;
44
- background-image: radial-gradient(circle at 15% 50%, rgba(107, 11, 201, 0.15), transparent 25%),
45
- radial-gradient(circle at 85% 30%, rgba(16, 185, 129, 0.1), transparent 25%);
46
- color: #e2e8f0;
47
- overflow-x: hidden;
48
- }
49
-
50
- ::-webkit-scrollbar {
51
- width: 8px;
52
- }
53
-
54
- ::-webkit-scrollbar-track {
55
- background: #191022;
56
- }
57
-
58
- ::-webkit-scrollbar-thumb {
59
- background: #4d3168;
60
- border-radius: 4px;
61
- }
62
-
63
- ::-webkit-scrollbar-thumb:hover {
64
- background: #7f0df2;
65
- }
66
-
67
- /* Floating Panel Glass Effect */
68
- .glass-panel {
69
- background: rgba(25, 16, 34, 0.85);
70
- backdrop-filter: blur(16px);
71
- -webkit-backdrop-filter: blur(16px);
72
- border: 1px solid rgba(255, 255, 255, 0.08);
73
- }
74
-
75
- /* Strict Clipping Fix */
76
- .force-clip {
77
- mask-image: radial-gradient(white, black);
78
- -webkit-mask-image: -webkit-radial-gradient(white, black);
79
- border-radius: inherit;
80
- }
81
- </style>
82
- </head>
83
-
84
- <body class="h-screen flex flex-col relative">
85
-
86
- <!-- Header -->
87
- <div class="h-20 flex items-center justify-between px-8 z-20 shrink-0">
88
- <div class="flex items-center gap-3">
89
- <div
90
- class="size-8 flex items-center justify-center bg-primary/20 rounded-lg border border-primary/30 text-primary">
91
- <span class="material-symbols-outlined">movie_filter</span>
92
- </div>
93
- <h1 class="text-xl font-display font-bold tracking-tight">Continuity <span
94
- class="opacity-50 font-normal text-sm ml-2">v2.2</span></h1>
95
  </div>
96
- <div class="flex items-center gap-2">
97
- <div class="flex items-center gap-2 px-3 py-1 bg-green-500/10 border border-green-500/20 rounded-full">
98
- <div class="size-2 bg-green-500 rounded-full animate-pulse"></div>
99
- <span class="text-xs font-bold text-green-400 tracking-wide uppercase">System Online</span>
 
100
  </div>
 
 
 
 
101
  </div>
102
  </div>
 
 
 
 
 
103
 
104
- <!-- Main Workspace (Center Aligned) -->
105
- <main class="flex-1 flex flex-col items-center justify-center p-6 pb-80">
106
- <div class="w-full max-w-6xl flex items-center justify-center gap-4 md:gap-8 lg:gap-12 mt-20">
107
-
108
- <!-- Scene A -->
109
- <div class="flex flex-col gap-3 flex-1 max-w-[320px] group">
110
- <div class="flex justify-between px-1">
111
- <span class="text-[10px] font-bold tracking-widest text-gray-500 uppercase">Scene A (Start)</span>
112
- </div>
113
- <div class="relative aspect-[9/16] md:aspect-[3/4] bg-surface-dark border-2 border-dashed border-border-dark rounded-2xl flex flex-col items-center justify-center gap-4 hover:border-primary/50 hover:bg-surface-dark/80 transition-all cursor-pointer shadow-lg overflow-hidden"
114
- onclick="document.getElementById('video-upload-a').click()">
115
  <div
116
- class="size-12 rounded-full bg-white/5 flex items-center justify-center group-hover:scale-110 transition-transform relative z-10">
117
- <span
118
- class="material-symbols-outlined text-2xl text-gray-400 group-hover:text-white">upload</span>
119
  </div>
120
- <p id="label-a" class="text-xs font-medium text-gray-400 text-center px-4 relative z-10">Upload
121
- Start Clip</p>
122
- <input type="file" id="video-upload-a" accept="video/*" class="hidden"
123
- onchange="handleFileSelect(this, 'label-a')">
124
- </div>
125
- </div>
126
-
127
- <!-- Bridge (Center) -->
128
- <div class="flex flex-col gap-3 flex-[1.5] max-w-[500px] relative z-20">
129
- <div class="flex justify-center px-1">
130
- <span class="text-[10px] font-bold tracking-[0.2em] text-primary uppercase animate-pulse">Generated
131
- Bridge</span>
132
- </div>
133
-
134
- <div id="bridge-card-outer"
135
- class="relative aspect-video rounded-2xl shadow-neon transition-all duration-500 border border-primary/20">
136
-
137
- <div id="bridge-card-inner" class="force-clip w-full h-full bg-black relative">
138
-
139
- <div id="bridge-content" class="w-full h-full">
140
- <div
141
- class="absolute inset-0 bg-[url('https://images.unsplash.com/photo-1614850523060-8da1d56ae167?q=80&w=1000&auto=format&fit=crop')] bg-cover bg-center opacity-20 mix-blend-overlay">
142
- </div>
143
- <div class="absolute inset-0 flex flex-col items-center justify-center text-center p-6">
144
- <div
145
- class="size-16 rounded-full bg-primary/10 border border-primary/20 flex items-center justify-center mb-3">
146
- <span class="material-symbols-outlined text-3xl text-primary">auto_awesome</span>
147
- </div>
148
- <p class="text-sm text-gray-300">Ready to bridge the gap</p>
149
- </div>
150
  </div>
151
-
152
- <div id="bridge-border"
153
- class="absolute inset-0 rounded-2xl border-2 border-transparent pointer-events-none z-30">
154
- </div>
155
- </div>
156
- </div>
157
- </div>
158
-
159
- <!-- Scene C -->
160
- <div class="flex flex-col gap-3 flex-1 max-w-[320px] group">
161
- <div class="flex justify-end px-1">
162
- <span class="text-[10px] font-bold tracking-widest text-gray-500 uppercase">Scene C (End)</span>
163
- </div>
164
- <div class="relative aspect-[9/16] md:aspect-[3/4] bg-surface-dark border-2 border-dashed border-border-dark rounded-2xl flex flex-col items-center justify-center gap-4 hover:border-primary/50 hover:bg-surface-dark/80 transition-all cursor-pointer shadow-lg overflow-hidden"
165
- onclick="document.getElementById('video-upload-c').click()">
166
- <div
167
- class="size-12 rounded-full bg-white/5 flex items-center justify-center group-hover:scale-110 transition-transform relative z-10">
168
- <span
169
- class="material-symbols-outlined text-2xl text-gray-400 group-hover:text-white">upload</span>
170
  </div>
171
- <p id="label-c" class="text-xs font-medium text-gray-400 text-center px-4 relative z-10">Upload End
172
- Clip</p>
173
- <input type="file" id="video-upload-c" accept="video/*" class="hidden"
174
- onchange="handleFileSelect(this, 'label-c')">
175
  </div>
 
 
176
  </div>
177
  </div>
178
- </main>
179
-
180
- <!-- Floating Director's Suite (Fixed Bottom Center) -->
181
- <div class="fixed bottom-10 left-1/2 transform -translate-x-1/2 z-50 w-full max-w-md px-4">
182
-
183
- <!-- Analysis Panel -->
184
- <div id="analysis-panel"
185
- class="glass-panel p-2 rounded-full shadow-neon flex items-center justify-between pl-6 pr-2">
186
- <div class="flex flex-col">
187
- <span class="text-sm font-bold text-white">Continuity Engine</span>
188
- <span class="text-[10px] text-gray-400 uppercase tracking-wide">Ready for analysis</span>
189
- </div>
190
- <button id="analyze-btn"
191
- class="bg-primary hover:bg-[#6b0bc9] text-white px-6 py-3 rounded-full font-bold text-sm transition-all flex items-center gap-2 shadow-lg">
192
- <span class="material-symbols-outlined text-lg">analytics</span>
193
- Analyze Scenes
194
- </button>
195
  </div>
196
-
197
- <!-- Review Panel -->
198
- <div id="review-panel"
199
- class="hidden glass-panel rounded-2xl p-5 shadow-2xl flex flex-col gap-4 animate-in slide-in-from-bottom-4 duration-300">
200
- <div class="flex items-center justify-between border-b border-white/10 pb-3">
201
- <h3 class="text-sm font-bold text-white flex items-center gap-2">
202
- <span class="material-symbols-outlined text-primary">movie_edit</span>
203
- Director's Configuration
204
- </h3>
205
- <button onclick="resetUI()"
206
- class="text-xs text-gray-500 hover:text-white uppercase tracking-wider">Reset</button>
207
- </div>
208
-
209
- <div>
210
- <label class="text-[10px] font-bold text-gray-500 uppercase tracking-widest mb-1 block">Visual
211
- Direction</label>
212
- <textarea id="prompt-box" rows="2"
213
- class="w-full bg-black/20 border border-white/10 rounded-lg p-3 text-sm text-white focus:border-primary focus:ring-1 focus:ring-primary outline-none resize-none"></textarea>
214
- </div>
215
-
216
- <div class="grid grid-cols-2 gap-4">
217
- <div>
218
- <label class="text-[10px] font-bold text-gray-500 uppercase tracking-widest mb-1 block">Visual
219
- Style</label>
220
- <select id="style-select"
221
- class="w-full bg-black/20 border border-white/10 rounded-lg p-2.5 text-sm text-white focus:border-primary outline-none">
222
- <option value="Cinematic">Cinematic</option>
223
- <option value="Anime">Anime</option>
224
- <option value="Cyberpunk">Cyberpunk</option>
225
- <option value="VHS">VHS Glitch</option>
226
- <option value="Noir">Noir</option>
227
- </select>
228
- </div>
229
- <div>
230
- <label class="text-[10px] font-bold text-gray-500 uppercase tracking-widest mb-1 block">Audio
231
- Mood</label>
232
- <select id="audio-input"
233
- class="w-full bg-black/20 border border-white/10 rounded-lg p-2.5 text-sm text-white focus:border-primary outline-none cursor-pointer">
234
- <option value="Cinematic orchestral score, epic, dramatic, high fidelity">Cinematic (Default)
235
- </option>
236
- <option value="Industrial synthwave, futuristic, neon hum, electronic">Cyberpunk / Sci-Fi
237
- </option>
238
- <option value="Nature sounds, wind, birds, flowing water, organic">Nature / Organic</option>
239
- <option value="Tense atmosphere, suspenseful drone, horror, scary">Horror / Suspense</option>
240
- <option value="Lo-fi hip hop, chill, relaxing, soft beats">Lo-fi / Chill</option>
241
- <option value="High energy, rock, intense, fast paced">Action / Intense</option>
242
- </select>
243
- </div>
244
  </div>
245
-
246
- <button id="generate-btn"
247
- class="w-full bg-gradient-to-r from-primary to-[#9d4edd] hover:brightness-110 text-white py-3.5 rounded-xl font-bold text-sm tracking-wide shadow-lg flex items-center justify-center gap-2 mt-2">
248
- <span class="material-symbols-outlined">auto_fix_high</span>
249
- Generate Video
250
- </button>
251
  </div>
252
  </div>
253
-
254
- <!-- Logic -->
255
- <script>
256
- let currentVideoAPath = "";
257
- let currentVideoCPath = "";
258
-
259
- function handleFileSelect(input, labelId) {
260
- if (input.files && input.files[0]) {
261
- const label = document.getElementById(labelId);
262
- label.innerText = input.files[0].name;
263
- label.classList.add("text-primary", "font-bold");
264
- }
265
- }
266
-
267
- function resetUI() {
268
- document.getElementById("analysis-panel").classList.remove("hidden");
269
- document.getElementById("review-panel").classList.add("hidden");
270
- document.getElementById("prompt-box").value = "";
271
-
272
- // Reset Bridge
273
- document.getElementById("bridge-content").innerHTML = `
274
- <div class="absolute inset-0 bg-[url('https://images.unsplash.com/photo-1614850523060-8da1d56ae167?q=80&w=1000&auto=format&fit=crop')] bg-cover bg-center opacity-20 mix-blend-overlay"></div>
275
- <div class="absolute inset-0 flex flex-col items-center justify-center text-center p-6">
276
- <div class="size-16 rounded-full bg-primary/10 border border-primary/20 flex items-center justify-center mb-3">
277
- <span class="material-symbols-outlined text-3xl text-primary">auto_awesome</span>
278
- </div>
279
- <p class="text-sm text-gray-300">Ready to bridge the gap</p>
280
- </div>
281
- `;
282
- // Reset Outer Glow
283
- const outer = document.getElementById("bridge-card-outer");
284
- outer.classList.remove("border-primary", "shadow-[0_0_30px_rgba(127,13,242,0.6)]");
285
- outer.classList.add("border-primary/20");
286
-
287
- // Reset Inner Border
288
- const border = document.getElementById("bridge-border");
289
- border.classList.remove("border-primary/50");
290
- border.classList.add("border-transparent");
291
- currentVideoAPath = "";
292
- currentVideoCPath = "";
293
- }
294
-
295
- // --- ANALYZE LOGIC ---
296
- document.getElementById("analyze-btn").addEventListener("click", async () => {
297
- const fileA = document.getElementById("video-upload-a").files[0];
298
- const fileC = document.getElementById("video-upload-c").files[0];
299
- const btn = document.getElementById("analyze-btn");
300
-
301
- if (!fileA || !fileC) {
302
- alert("⚠️ Please upload both Scene A and Scene C first.");
303
- return;
304
- }
305
-
306
- const originalText = btn.innerHTML;
307
- btn.innerHTML = `<span class="material-symbols-outlined animate-spin text-lg">progress_activity</span> Analyzing...`;
308
- btn.disabled = true;
309
-
310
- const formData = new FormData();
311
- formData.append("video_a", fileA);
312
- formData.append("video_c", fileC);
313
-
314
- try {
315
- const res = await fetch("/analyze", { method: "POST", body: formData });
316
- if (!res.ok) throw new Error(await res.text());
317
-
318
- const data = await res.json();
319
-
320
- // Switch UI
321
- document.getElementById("prompt-box").value = data.prompt;
322
- currentVideoAPath = data.video_a_path;
323
- currentVideoCPath = data.video_c_path;
324
-
325
- document.getElementById("analysis-panel").classList.add("hidden");
326
- document.getElementById("review-panel").classList.remove("hidden");
327
- } catch (err) {
328
- alert("Analysis Error: " + err.message);
329
- } finally {
330
- btn.innerHTML = originalText;
331
- btn.disabled = false;
332
- }
333
- });
334
-
335
- // --- GENERATE LOGIC ---
336
- document.getElementById("generate-btn").addEventListener("click", async () => {
337
- const btn = document.getElementById("generate-btn");
338
- const prompt = document.getElementById("prompt-box").value;
339
- const style = document.getElementById("style-select").value;
340
- const audio = document.getElementById("audio-input").value;
341
-
342
- const originalText = btn.innerHTML;
343
- btn.innerHTML = `<span class="material-symbols-outlined animate-spin text-lg">progress_activity</span> Generating (this takes ~60s)...`;
344
- btn.disabled = true;
345
- btn.classList.add("opacity-50");
346
-
347
- try {
348
- const res = await fetch("/generate", {
349
- method: "POST",
350
- headers: { "Content-Type": "application/json" },
351
- body: JSON.stringify({
352
- prompt: prompt,
353
- style: style,
354
- audio_prompt: audio,
355
- video_a_path: currentVideoAPath,
356
- video_c_path: currentVideoCPath
357
- })
358
- });
359
-
360
- if (!res.ok) throw new Error(await res.text());
361
-
362
- const data = await res.json();
363
- const jobId = data.job_id;
364
-
365
- // Poll Status
366
- const poll = setInterval(async () => {
367
- try {
368
- const statusRes = await fetch(`/status/${jobId}?t=${Date.now()}`);
369
- if (statusRes.ok) {
370
- const status = await statusRes.json();
371
-
372
- if (status.status === "completed") {
373
- clearInterval(poll);
374
-
375
- // Render Video (Object-Contain prevents zoom overlap)
376
- const bridgeContent = document.getElementById("bridge-content");
377
- bridgeContent.innerHTML = `
378
- <video controls autoplay loop class="w-full h-full object-contain bg-black">
379
- <source src="${status.video_url}" type="video/mp4">
380
- </video>
381
- `;
382
-
383
- // Activate Outer Glow
384
- const outer = document.getElementById("bridge-card-outer");
385
- outer.classList.remove("border-primary/20");
386
- outer.classList.add("border-primary", "shadow-[0_0_30px_rgba(127,13,242,0.6)]");
387
-
388
- // Activate Inner Overlay Border
389
- const border = document.getElementById("bridge-border");
390
- border.classList.remove("border-transparent");
391
- border.classList.add("border-primary/50");
392
-
393
- btn.innerHTML = `<span class="material-symbols-outlined">check_circle</span> Done!`;
394
- setTimeout(() => {
395
- btn.innerHTML = originalText;
396
- btn.disabled = false;
397
- btn.classList.remove("opacity-50");
398
- }, 3000);
399
- } else if (status.status === "error") {
400
- clearInterval(poll);
401
- alert("Generation Error: " + status.log);
402
- btn.innerHTML = originalText;
403
- btn.disabled = false;
404
- btn.classList.remove("opacity-50");
405
- } else {
406
- btn.innerHTML = `<span class="material-symbols-outlined animate-spin text-lg">progress_activity</span> ${status.log} (${status.progress}%)`;
407
- }
408
- }
409
- } catch (e) {
410
- console.error(e);
411
- }
412
- }, 1500);
413
-
414
- } catch (err) {
415
- alert("Request Error: " + err.message);
416
- btn.innerHTML = originalText;
417
- btn.disabled = false;
418
- btn.classList.remove("opacity-50");
419
- }
420
- });
421
- </script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
422
  </body>
423
 
424
  </html>
 
1
  <!DOCTYPE html>
2
  <html class="dark" lang="en">
3
+ <div class="flex items-center gap-3">
4
+ <div class="size-8 flex items-center justify-center bg-primary/20 rounded-lg border border-primary/30 text-primary">
5
+ <span class="material-symbols-outlined">movie_filter</span>
6
+ </div>
7
+ <h1 class="text-xl font-display font-bold tracking-tight">Continuity <span
8
+ class="opacity-50 font-normal text-sm ml-2">v2.3</span></h1>
9
+ </div>
10
+ <div class="flex items-center gap-3">
11
+ <button id="history-btn"
12
+ class="flex items-center gap-2 px-4 py-2 bg-surface-dark border border-white/10 rounded-lg hover:bg-white/5 transition-colors text-xs font-bold uppercase tracking-wider">
13
+ <span class="material-symbols-outlined text-lg">history</span>
14
+ Gallery
15
+ </button>
16
+ <div class="flex items-center gap-2 px-3 py-1 bg-green-500/10 border border-green-500/20 rounded-full">
17
+ <div class="size-2 bg-green-500 rounded-full animate-pulse"></div>
18
+ <span class="text-xs font-bold text-green-400 tracking-wide uppercase">System Online</span>
19
+ </div>
20
+ </div>
21
+ <div class="w-full max-w-6xl flex items-center justify-center gap-4 md:gap-8 lg:gap-12 mt-20">
22
 
23
+ <div class="flex flex-col gap-3 flex-1 max-w-[320px] group">
24
+ <div class="flex justify-between px-1">
25
+ <span class="text-[10px] font-bold tracking-widest text-gray-500 uppercase">Scene A (Start)</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  </div>
27
+ <div class="relative aspect-[9/16] md:aspect-[3/4] bg-surface-dark border-2 border-dashed border-border-dark rounded-2xl flex flex-col items-center justify-center gap-4 hover:border-primary/50 hover:bg-surface-dark/80 transition-all cursor-pointer shadow-lg overflow-hidden"
28
+ onclick="document.getElementById('video-upload-a').click()">
29
+ <div
30
+ class="size-12 rounded-full bg-white/5 flex items-center justify-center group-hover:scale-110 transition-transform relative z-10">
31
+ <span class="material-symbols-outlined text-2xl text-gray-400 group-hover:text-white">upload</span>
32
  </div>
33
+ <p id="label-a" class="text-xs font-medium text-gray-400 text-center px-4 relative z-10">Upload Start Clip
34
+ </p>
35
+ <input type="file" id="video-upload-a" accept="video/*" class="hidden"
36
+ onchange="handleFileSelect(this, 'label-a')">
37
  </div>
38
  </div>
39
+ <div class="flex flex-col gap-3 flex-[1.5] max-w-[500px] relative z-20">
40
+ <div class="flex justify-center px-1">
41
+ <span class="text-[10px] font-bold tracking-[0.2em] text-primary uppercase animate-pulse">Generated
42
+ Bridge</span>
43
+ </div>
44
 
45
+ <div id="bridge-card-outer"
46
+ class="relative aspect-video rounded-2xl shadow-neon transition-all duration-500 border border-primary/20">
47
+ <div id="bridge-card-inner" class="force-clip w-full h-full bg-black relative">
48
+ <div id="bridge-content" class="w-full h-full">
 
 
 
 
 
 
 
49
  <div
50
+ class="absolute inset-0 bg-[url('https://images.unsplash.com/photo-1614850523060-8da1d56ae167?q=80&w=1000&auto=format&fit=crop')] bg-cover bg-center opacity-20 mix-blend-overlay">
 
 
51
  </div>
52
+ <div class="absolute inset-0 flex flex-col items-center justify-center text-center p-6">
53
+ <div
54
+ class="size-16 rounded-full bg-primary/10 border border-primary/20 flex items-center justify-center mb-3">
55
+ <span class="material-symbols-outlined text-3xl text-primary">auto_awesome</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  </div>
57
+ <p class="text-sm text-gray-300">Ready to bridge the gap</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
  </div>
 
 
 
 
59
  </div>
60
+ <div id="bridge-border"
61
+ class="absolute inset-0 rounded-2xl border-2 border-transparent pointer-events-none z-30"></div>
62
  </div>
63
  </div>
64
+ </div>
65
+ <div class="flex flex-col gap-3 flex-1 max-w-[320px] group">
66
+ <div class="flex justify-end px-1">
67
+ <span class="text-[10px] font-bold tracking-widest text-gray-500 uppercase">Scene C (End)</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
68
  </div>
69
+ <div class="relative aspect-[9/16] md:aspect-[3/4] bg-surface-dark border-2 border-dashed border-border-dark rounded-2xl flex flex-col items-center justify-center gap-4 hover:border-primary/50 hover:bg-surface-dark/80 transition-all cursor-pointer shadow-lg overflow-hidden"
70
+ onclick="document.getElementById('video-upload-c').click()">
71
+ <div
72
+ class="size-12 rounded-full bg-white/5 flex items-center justify-center group-hover:scale-110 transition-transform relative z-10">
73
+ <span class="material-symbols-outlined text-2xl text-gray-400 group-hover:text-white">upload</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  </div>
75
+ <p id="label-c" class="text-xs font-medium text-gray-400 text-center px-4 relative z-10">Upload End Clip</p>
76
+ <input type="file" id="video-upload-c" accept="video/*" class="hidden"
77
+ onchange="handleFileSelect(this, 'label-c')">
 
 
 
78
  </div>
79
  </div>
80
+ </div>
81
+ <div class="p-6 border-b border-white/5 flex items-center justify-between">
82
+ <h2 class="text-lg font-bold text-white flex items-center gap-2">
83
+ <span class="material-symbols-outlined text-primary">history</span>
84
+ Creation History
85
+ </h2>
86
+ <button id="close-drawer" class="text-gray-400 hover:text-white transition-colors">
87
+ <span class="material-symbols-outlined">close</span>
88
+ </button>
89
+ </div>
90
+ <div id="gallery-content" class="flex-1 overflow-y-auto p-4 space-y-4">
91
+ <div class="text-center text-gray-500 mt-10">Loading history...</div>
92
+ </div>
93
+ <div id="analysis-panel" class="glass-panel p-2 rounded-full shadow-neon flex items-center justify-between pl-6 pr-2">
94
+ <div class="flex flex-col">
95
+ <span class="text-sm font-bold text-white">Continuity Engine</span>
96
+ <span class="text-[10px] text-gray-400 uppercase tracking-wide">Ready for analysis</span>
97
+ </div>
98
+ <button id="analyze-btn"
99
+ class="bg-primary hover:bg-[#6b0bc9] text-white px-6 py-3 rounded-full font-bold text-sm transition-all flex items-center gap-2 shadow-lg">
100
+ <span class="material-symbols-outlined text-lg">analytics</span>
101
+ Analyze Scenes
102
+ </button>
103
+ </div>
104
+ <div id="review-panel"
105
+ class="hidden glass-panel rounded-2xl p-5 shadow-2xl flex flex-col gap-4 animate-in slide-in-from-bottom-4 duration-300">
106
+ <div class="flex items-center justify-between border-b border-white/10 pb-3">
107
+ <h3 class="text-sm font-bold text-white flex items-center gap-2">
108
+ <span class="material-symbols-outlined text-primary">movie_edit</span>
109
+ Director's Configuration
110
+ </h3>
111
+ <button onclick="resetUI()"
112
+ class="text-xs text-gray-500 hover:text-white uppercase tracking-wider">Reset</button>
113
+ </div>
114
+ <div>
115
+ <label class="text-[10px] font-bold text-gray-500 uppercase tracking-widest mb-1 block">Visual Direction</label>
116
+ <textarea id="prompt-box" rows="2"
117
+ class="w-full bg-black/20 border border-white/10 rounded-lg p-3 text-sm text-white focus:border-primary focus:ring-1 focus:ring-primary outline-none resize-none"></textarea>
118
+ </div>
119
+ <div class="grid grid-cols-2 gap-4">
120
+ <div>
121
+ <label class="text-[10px] font-bold text-gray-500 uppercase tracking-widest mb-1 block">Visual Style</label>
122
+ <select id="style-select" onchange="savePreference('style', this.value)"
123
+ class="w-full bg-black/20 border border-white/10 rounded-lg p-2.5 text-sm text-white focus:border-primary outline-none">
124
+ <option value="Cinematic">Cinematic</option>
125
+ <option value="Anime">Anime</option>
126
+ <option value="Cyberpunk">Cyberpunk</option>
127
+ <option value="VHS">VHS Glitch</option>
128
+ <option value="Noir">Noir</option>
129
+ </select>
130
+ </div>
131
+ <div>
132
+ <label class="text-[10px] font-bold text-gray-500 uppercase tracking-widest mb-1 block">Audio Mood</label>
133
+ <select id="audio-input" onchange="savePreference('audio', this.value)"
134
+ class="w-full bg-black/20 border border-white/10 rounded-lg p-2.5 text-sm text-white focus:border-primary outline-none cursor-pointer">
135
+ <option value="Cinematic orchestral score, epic, dramatic, high fidelity">Cinematic (Default)</option>
136
+ <option value="Industrial synthwave, futuristic, neon hum, electronic">Cyberpunk / Sci-Fi</option>
137
+ <option value="Nature sounds, wind, birds, flowing water, organic">Nature / Organic</option>
138
+ <option value="Tense atmosphere, suspenseful drone, horror, scary">Horror / Suspense</option>
139
+ <option value="Lo-fi hip hop, chill, relaxing, soft beats">Lo-fi / Chill</option>
140
+ <option value="High energy, rock, intense, fast paced">Action / Intense</option>
141
+ </select>
142
+ </div>
143
+ </div>
144
+ <button id="generate-btn"
145
+ class="w-full bg-gradient-to-r from-primary to-[#9d4edd] hover:brightness-110 text-white py-3.5 rounded-xl font-bold text-sm tracking-wide shadow-lg flex items-center justify-center gap-2 mt-2">
146
+ <span class="material-symbols-outlined">auto_fix_high</span>
147
+ Generate Video
148
+ </button>
149
+ </div>
150
+ // --- SESSION PERSISTENCE ---
151
+ function savePreference(key, value) {
152
+ localStorage.setItem('continuity_' + key, value);
153
+ }
154
+ function loadPreferences() {
155
+ const savedStyle = localStorage.getItem('continuity_style');
156
+ const savedAudio = localStorage.getItem('continuity_audio');
157
+
158
+ if (savedStyle) document.getElementById('style-select').value = savedStyle;
159
+ if (savedAudio) document.getElementById('audio-input').value = savedAudio;
160
+ }
161
+ // Load prefs on init
162
+ loadPreferences();
163
+ // --- HISTORY GALLERY LOGIC ---
164
+ const drawer = document.getElementById('gallery-drawer');
165
+ const overlay = document.getElementById('drawer-overlay');
166
+ const historyBtn = document.getElementById('history-btn');
167
+ const closeBtn = document.getElementById('close-drawer');
168
+ function toggleDrawer(show) {
169
+ if (show) {
170
+ drawer.classList.remove('drawer-closed');
171
+ drawer.classList.add('drawer-open');
172
+ overlay.classList.remove('hidden');
173
+ fetchHistory();
174
+ } else {
175
+ drawer.classList.remove('drawer-open');
176
+ drawer.classList.add('drawer-closed');
177
+ overlay.classList.add('hidden');
178
+ }
179
+ }
180
+ historyBtn.addEventListener('click', () => toggleDrawer(true));
181
+ closeBtn.addEventListener('click', () => toggleDrawer(false));
182
+ overlay.addEventListener('click', () => toggleDrawer(false));
183
+ async function fetchHistory() {
184
+ const container = document.getElementById('gallery-content');
185
+ container.innerHTML = '<div class="text-center text-gray-500 mt-10"><span
186
+ class="material-symbols-outlined animate-spin text-2xl">progress_activity</span></div>';
187
+
188
+ try {
189
+ const res = await fetch('/history');
190
+ const data = await res.json();
191
+
192
+ if (!data || data.length === 0) {
193
+ container.innerHTML = '<div class="text-center text-gray-500 mt-10 text-sm">No history found. Start creating!</div>';
194
+ return;
195
+ }
196
+ container.innerHTML = data.map(item => `
197
+ <div
198
+ class="bg-black/40 rounded-xl overflow-hidden border border-white/5 hover:border-primary/50 transition-colors group">
199
+ <div class="aspect-video relative bg-black">
200
+ <video src="${item.url}" class="w-full h-full object-cover" controls preload="metadata"></video>
201
+ </div>
202
+ <div class="p-3 flex items-center justify-between">
203
+ <div>
204
+ <p class="text-xs text-gray-400 font-mono truncate w-40">${item.name}</p>
205
+ <p class="text-[10px] text-gray-600">${new Date(item.created).toLocaleDateString()}</p>
206
+ </div>
207
+ <a href="${item.url}" download target="_blank"
208
+ class="p-2 bg-white/5 hover:bg-primary hover:text-white rounded-lg transition-colors" title="Download">
209
+ <span class="material-symbols-outlined text-sm">download</span>
210
+ </a>
211
+ </div>
212
+ </div>
213
+ `).join('');
214
+
215
+ } catch (e) {
216
+ container.innerHTML = '<div class="text-center text-red-400 mt-10 text-sm">Failed to load history.</div>';
217
+ console.error(e);
218
+ }
219
+ }
220
+ // --- CORE LOGIC ---
221
+ function handleFileSelect(input, labelId) {
222
+ if (input.files && input.files[0]) {
223
+ const label = document.getElementById(labelId);
224
+ label.innerText = input.files[0].name;
225
+ label.classList.add("text-primary", "font-bold");
226
+ }
227
+ }
228
+ function resetUI() {
229
+ document.getElementById("analysis-panel").classList.remove("hidden");
230
+ document.getElementById("review-panel").classList.add("hidden");
231
+ document.getElementById("prompt-box").value = "";
232
+
233
+ // Reset Bridge
234
+ document.getElementById("bridge-content").innerHTML = `
235
+ <div
236
+ class="absolute inset-0 bg-[url('https://images.unsplash.com/photo-1614850523060-8da1d56ae167?q=80&w=1000&auto=format&fit=crop')] bg-cover bg-center opacity-20 mix-blend-overlay">
237
+ </div>
238
+ <div class="absolute inset-0 flex flex-col items-center justify-center text-center p-6">
239
+ <div class="size-16 rounded-full bg-primary/10 border border-primary/20 flex items-center justify-center mb-3">
240
+ <span class="material-symbols-outlined text-3xl text-primary">auto_awesome</span>
241
+ </div>
242
+ <p class="text-sm text-gray-300">Ready to bridge the gap</p>
243
+ </div>
244
+ `;
245
+ // Reset Outer Glow
246
+ const outer = document.getElementById("bridge-card-outer");
247
+ outer.classList.remove("border-primary", "shadow-[0_0_30px_rgba(127,13,242,0.6)]");
248
+ outer.classList.add("border-primary/20");
249
+
250
+ // Reset Inner Border
251
+ const border = document.getElementById("bridge-border");
252
+ border.classList.remove("border-primary/50");
253
+ border.classList.add("border-transparent");
254
+ currentVideoAPath = "";
255
+ currentVideoCPath = "";
256
+ }
257
+ // --- ANALYZE LOGIC ---
258
+ document.getElementById("analyze-btn").addEventListener("click", async () => {
259
+ const fileA = document.getElementById("video-upload-a").files[0];
260
+ const fileC = document.getElementById("video-upload-c").files[0];
261
+ const btn = document.getElementById("analyze-btn");
262
+ if (!fileA || !fileC) {
263
+ alert("⚠️ Please upload both Scene A and Scene C first.");
264
+ return;
265
+ }
266
+ const originalText = btn.innerHTML;
267
+ btn.innerHTML = `<span class="material-symbols-outlined animate-spin text-lg">progress_activity</span> Analyzing...`;
268
+ btn.disabled = true;
269
+ const formData = new FormData();
270
+ formData.append("video_a", fileA);
271
+ formData.append("video_c", fileC);
272
+ try {
273
+ const res = await fetch("/analyze", { method: "POST", body: formData });
274
+ if (!res.ok) throw new Error(await res.text());
275
+
276
+ const data = await res.json();
277
+
278
+ document.getElementById("prompt-box").value = data.prompt;
279
+ currentVideoAPath = data.video_a_path;
280
+ currentVideoCPath = data.video_c_path;
281
+
282
+ document.getElementById("analysis-panel").classList.add("hidden");
283
+ document.getElementById("review-panel").classList.remove("hidden");
284
+ } catch (err) {
285
+ alert("Analysis Error: " + err.message);
286
+ } finally {
287
+ btn.innerHTML = originalText;
288
+ btn.disabled = false;
289
+ }
290
+ });
291
+ // --- GENERATE LOGIC ---
292
+ document.getElementById("generate-btn").addEventListener("click", async () => {
293
+ const btn = document.getElementById("generate-btn");
294
+ const prompt = document.getElementById("prompt-box").value;
295
+ const style = document.getElementById("style-select").value;
296
+ const audio = document.getElementById("audio-input").value;
297
+ const originalText = btn.innerHTML;
298
+ btn.innerHTML = `<span class="material-symbols-outlined animate-spin text-lg">progress_activity</span> Generating (this
299
+ takes ~60s)...`;
300
+ btn.disabled = true;
301
+ btn.classList.add("opacity-50");
302
+ try {
303
+ const res = await fetch("/generate", {
304
+ method: "POST",
305
+ headers: { "Content-Type": "application/json" },
306
+ body: JSON.stringify({
307
+ prompt: prompt,
308
+ style: style,
309
+ audio_prompt: audio,
310
+ video_a_path: currentVideoAPath,
311
+ video_c_path: currentVideoCPath
312
+ })
313
+ });
314
+ if (!res.ok) throw new Error(await res.text());
315
+ const data = await res.json();
316
+ const jobId = data.job_id;
317
+ // Poll Status
318
+ const poll = setInterval(async () => {
319
+ const statusRes = await fetch(`/status/${jobId}?t=${Date.now()}`);
320
+ if (statusRes.ok) {
321
+ const status = await statusRes.json();
322
+
323
+ if (status.status === "completed") {
324
+ clearInterval(poll);
325
+
326
+ // Render Video (Object-Contain prevents zoom overlap)
327
+ const bridgeContent = document.getElementById("bridge-content");
328
+ bridgeContent.innerHTML = `
329
+ <video controls autoplay loop class="w-full h-full object-contain bg-black">
330
+ <source src="${status.video_url}" type="video/mp4">
331
+ </video>
332
+ `;
333
+
334
+ // Activate Outer Glow
335
+ const outer = document.getElementById("bridge-card-outer");
336
+ outer.classList.remove("border-primary/20");
337
+ outer.classList.add("border-primary", "shadow-[0_0_30px_rgba(127,13,242,0.6)]");
338
+ // Activate Inner Overlay Border
339
+ const border = document.getElementById("bridge-border");
340
+ border.classList.remove("border-transparent");
341
+ border.classList.add("border-primary/50");
342
+ btn.innerHTML = `<span class="material-symbols-outlined">check_circle</span> Done!`;
343
+ setTimeout(() => {
344
+ btn.innerHTML = originalText;
345
+ btn.disabled = false;
346
+ btn.classList.remove("opacity-50");
347
+ }, 3000);
348
+ } else if (status.status === "error") {
349
+ clearInterval(poll);
350
+ alert("Generation Error: " + status.log);
351
+ btn.innerHTML = originalText;
352
+ btn.disabled = false;
353
+ btn.classList.remove("opacity-50");
354
+ } else {
355
+ btn.innerHTML = `<span class="material-symbols-outlined animate-spin text-lg">progress_activity</span> ${status.log}
356
+ (${status.progress}%)`;
357
+ }
358
+ }
359
+ }, 1500);
360
+ } catch (err) {
361
+ alert("Request Error: " + err.message);
362
+ btn.innerHTML = originalText;
363
+ btn.disabled = false;
364
+ btn.classList.remove("opacity-50");
365
+ }
366
+ });
367
+ </script>
368
  </body>
369
 
370
  </html>