Wckd314 commited on
Commit
a893a6b
Β·
verified Β·
1 Parent(s): 58bd9f7

Upload 2 files

Browse files
Files changed (2) hide show
  1. static/index.html +143 -0
  2. static/script.js +383 -0
static/index.html ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Pundit Feynman β€” Research Paper to Code</title>
8
+ <link rel="stylesheet" href="/style.css">
9
+ <link
10
+ href="https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;600;700&family=JetBrains+Mono:wght@400;500&display=swap"
11
+ rel="stylesheet">
12
+ </head>
13
+
14
+ <body>
15
+ <!-- Left Panel: Upload & Status -->
16
+ <aside class="left-panel" id="left-panel">
17
+ <div class="panel-inner">
18
+ <header>
19
+ <h1>Pundit Feynman</h1>
20
+ <p class="tagline">Upload a research paper.<br>Learn it the Feynman way.</p>
21
+ <button id="visualize-btn" class="header-visualize hidden" style="display: none !important;">🎨
22
+ Visualize Concept</button>
23
+ </header>
24
+
25
+ <!-- Upload State -->
26
+ <div id="upload-section">
27
+ <div id="drop-zone" class="drop-zone">
28
+ <svg class="upload-icon" width="32" height="32" viewBox="0 0 24 24" fill="none"
29
+ stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
30
+ <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
31
+ <polyline points="17 8 12 3 7 8"></polyline>
32
+ <line x1="12" y1="3" x2="12" y2="15"></line>
33
+ </svg>
34
+ <p class="drop-text">Drop your PDF here</p>
35
+ <span class="drop-hint">or click to browse</span>
36
+ <input type="file" id="file-input" accept="application/pdf" hidden>
37
+ </div>
38
+
39
+ <!-- Divider -->
40
+ <div class="divider">
41
+ <span>or paste arXiv link</span>
42
+ </div>
43
+
44
+ <!-- arXiv URL Input -->
45
+ <div class="arxiv-input-row">
46
+ <input type="text" id="arxiv-input" class="arxiv-input"
47
+ placeholder="https://arxiv.org/abs/2401.12345">
48
+ <button id="arxiv-btn" class="btn btn-primary arxiv-btn">Go β†’</button>
49
+ </div>
50
+ </div>
51
+
52
+ <!-- Extraction Progress -->
53
+ <div id="extract-status" class="status-box hidden">
54
+ <div class="spinner"></div>
55
+ <p class="status-label" id="extract-label">Analyzing paper…</p>
56
+ <p class="status-sub">This may take a few minutes for long papers.</p>
57
+ </div>
58
+
59
+ <!-- Stream Active Indicator -->
60
+ <div id="stream-status" class="status-box hidden">
61
+ <div class="pulse-dot"></div>
62
+ <p class="status-label">Generating code live…</p>
63
+ <p class="status-sub">Watch the output in the code viewer β†’</p>
64
+ </div>
65
+
66
+ <!-- Done -->
67
+ <div id="done-section" class="status-box hidden">
68
+ <p class="done-check">βœ“</p>
69
+ <p class="status-label">Generation complete</p>
70
+ <div class="btn-row">
71
+ <a id="download-btn" class="btn btn-primary" download="pundit_feynman_notebook.ipynb">⬇ Download
72
+ .ipynb</a>
73
+ <button id="reset-btn" class="btn btn-secondary">↻ New Paper</button>
74
+ </div>
75
+ </div>
76
+
77
+ <!-- Error -->
78
+ <div id="error-section" class="status-box hidden">
79
+ <p class="error-x">βœ•</p>
80
+ <p class="status-label">Something went wrong</p>
81
+ <p class="status-sub" id="error-text"></p>
82
+ <button id="error-reset-btn" class="btn btn-secondary">↻ Try Again</button>
83
+ </div>
84
+
85
+ <footer>
86
+ <p>Powered by <strong>NVIDIA NIM</strong></p>
87
+ <div class="feedback-footer">
88
+ <p>please give feedback, so that i can make it better</p>
89
+ <a href="https://mail.google.com/mail/?view=cm&to=Avijitshil52460@gmail.com&su=Pundit%20Feynman%20Feedback"
90
+ target="_blank" class="feedback-link">Avijitshil52460@gmail.com</a>
91
+ </div>
92
+ </footer>
93
+ </div>
94
+ </aside>
95
+
96
+ <!-- Right Panel: Live Code Viewer -->
97
+ <main class="right-panel" id="right-panel">
98
+ <div class="code-header">
99
+ <span class="code-title">Code Output</span>
100
+ <span class="code-badge" id="code-badge">waiting</span>
101
+ </div>
102
+ <pre class="code-viewer"
103
+ id="code-viewer"><code id="code-output">// Upload a paper to see the generated code here…</code></pre>
104
+ </main>
105
+
106
+ <script src="/script.js?v=4"></script>
107
+
108
+ <!-- Floating Image Window (Hidden) -->
109
+ <div id="image-float" class="float-window hidden" style="display: none !important;">
110
+ <div class="float-header" id="float-header">
111
+ <span class="float-title">🎨 Concept Illustration</span>
112
+ <div class="float-actions">
113
+ <button id="float-download" class="float-btn" title="Download PNG">
114
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
115
+ stroke-linecap="round" stroke-linejoin="round">
116
+ <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
117
+ <polyline points="7 10 12 15 17 10" />
118
+ <line x1="12" y1="15" x2="12" y2="3" />
119
+ </svg>
120
+ </button>
121
+ <button id="float-minimize" class="float-btn" title="Minimize">─</button>
122
+ <button id="float-close" class="float-btn" title="Close">βœ•</button>
123
+ </div>
124
+ </div>
125
+ <div class="float-body" id="float-body">
126
+ <div class="float-spinner" id="float-spinner">
127
+ <div class="paint-brush-container">
128
+ <div class="brush">πŸ–ŒοΈ</div>
129
+ <div class="shimmer-line"></div>
130
+ </div>
131
+ <p id="visualize-status">FLUX is painting your concept…</p>
132
+ </div>
133
+ <img id="float-image" class="float-image hidden" alt="Concept Illustration" />
134
+ </div>
135
+ </div>
136
+
137
+ <!-- Minimized Pill (Hidden) -->
138
+ <div id="image-pill" class="float-pill hidden" style="display: none !important;">
139
+ <span>🎨 Illustration</span>
140
+ </div>
141
+ </body>
142
+
143
+ </html>
static/script.js ADDED
@@ -0,0 +1,383 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ console.log('πŸš€ Pundit Feynman Script Loading... [v3.2]');
2
+
3
+ window.onerror = function (msg, url, lineNo, columnNo, error) {
4
+ alert(`JS Error: ${msg}\nLine: ${lineNo}\nCheck browser console!`);
5
+ return false;
6
+ };
7
+
8
+ // ── DOM Refs ──
9
+ const getEl = (id) => document.getElementById(id);
10
+ const dropZone = getEl('drop-zone');
11
+ const fileInput = getEl('file-input');
12
+ const uploadSection = getEl('upload-section');
13
+ const extractStatus = getEl('extract-status');
14
+ const extractLabel = getEl('extract-label');
15
+ const streamStatus = getEl('stream-status');
16
+ const doneSection = getEl('done-section');
17
+ const errorSection = getEl('error-section');
18
+ const errorText = getEl('error-text');
19
+ const downloadBtn = getEl('download-btn');
20
+ const resetBtn = getEl('reset-btn');
21
+ const errorResetBtn = getEl('error-reset-btn');
22
+ const codeOutput = getEl('code-output');
23
+ const codeViewer = getEl('code-viewer');
24
+ const codeBadge = getEl('code-badge');
25
+ const arxivInput = getEl('arxiv-input');
26
+ const arxivBtn = getEl('arxiv-btn');
27
+ const visualizeBtn = getEl('visualize-btn');
28
+ const imageFloat = getEl('image-float');
29
+ const imagePill = getEl('image-pill');
30
+ const floatHeader = getEl('float-header');
31
+ const floatImage = getEl('float-image');
32
+ const floatSpinner = getEl('float-spinner');
33
+ const floatDownload = getEl('float-download');
34
+ const floatMinimize = getEl('float-minimize');
35
+ const floatClose = getEl('float-close');
36
+
37
+ console.log('🎨 UI elements mapped. Visualize button:', !!visualizeBtn);
38
+
39
+ // Test backend connectivity
40
+ fetch('/api/ping').then(r => r.json()).then(d => console.log('πŸ“ Backend connectivity:', d.status)).catch(e => console.error('❌ Backend UNREACHABLE:', e));
41
+
42
+ // ── Visual Illustration State ──
43
+ let currentJobId = null;
44
+ window._debugJobId = () => currentJobId; // Access via console: window._debugJobId()
45
+
46
+ // ── State Manager ──
47
+ function showSection(section) {
48
+ [uploadSection, extractStatus, streamStatus, doneSection, errorSection]
49
+ .forEach(el => el.classList.add('hidden'));
50
+ if (section) section.classList.remove('hidden');
51
+ }
52
+
53
+ // ── Drag & Drop ──
54
+ if (dropZone) {
55
+ dropZone.addEventListener('click', () => {
56
+ if (fileInput) fileInput.click();
57
+ });
58
+
59
+ dropZone.addEventListener('dragover', (e) => {
60
+ e.preventDefault();
61
+ dropZone.classList.add('drag-over');
62
+ });
63
+
64
+ dropZone.addEventListener('dragleave', () => dropZone.classList.remove('drag-over'));
65
+
66
+ dropZone.addEventListener('drop', (e) => {
67
+ e.preventDefault();
68
+ dropZone.classList.remove('drag-over');
69
+ if (e.dataTransfer.files.length > 0) handleUpload(e.dataTransfer.files[0]);
70
+ });
71
+ }
72
+
73
+ if (fileInput) {
74
+ fileInput.addEventListener('change', (e) => {
75
+ if (e.target.files.length > 0) handleUpload(e.target.files[0]);
76
+ });
77
+ }
78
+
79
+ // ── arXiv URL Handler ──
80
+ if (arxivBtn) {
81
+ arxivBtn.addEventListener('click', () => handleArxiv());
82
+ }
83
+ if (arxivInput) {
84
+ arxivInput.addEventListener('keydown', (e) => {
85
+ if (e.key === 'Enter') handleArxiv();
86
+ });
87
+ }
88
+
89
+ async function handleArxiv() {
90
+ const url = arxivInput.value.trim();
91
+ if (!url) return;
92
+ if (!url.includes('arxiv.org')) {
93
+ alert('Please enter a valid arXiv URL (e.g. https://arxiv.org/abs/2401.12345)');
94
+ return;
95
+ }
96
+
97
+ showSection(extractStatus);
98
+ extractLabel.textContent = 'Downloading & analyzing arXiv paper…';
99
+ codeOutput.textContent = '// Downloading PDF from arXiv…';
100
+ codeBadge.textContent = 'extracting';
101
+ codeBadge.className = 'code-badge';
102
+
103
+ try {
104
+ const res = await fetch('/api/extract-arxiv', {
105
+ method: 'POST',
106
+ headers: { 'Content-Type': 'application/json' },
107
+ body: JSON.stringify({ url })
108
+ });
109
+
110
+ if (!res.ok) {
111
+ const err = await res.json().catch(() => ({ detail: 'arXiv extraction failed' }));
112
+ throw new Error(err.detail || 'arXiv extraction failed');
113
+ }
114
+
115
+ const data = await res.json();
116
+ console.log('arXiv extraction complete:', data);
117
+ startStream(data.job_id);
118
+
119
+ } catch (err) {
120
+ showError(err.message);
121
+ }
122
+ }
123
+
124
+ // ── Upload & Extract (Step 1) ──
125
+ async function handleUpload(file) {
126
+ if (!file.name.toLowerCase().endsWith('.pdf')) {
127
+ alert('Please upload a PDF file.');
128
+ return;
129
+ }
130
+
131
+ // Show extraction spinner
132
+ showSection(extractStatus);
133
+ extractLabel.textContent = 'Uploading & analyzing paper…';
134
+ codeOutput.textContent = '// Waiting for paper analysis to complete…';
135
+ codeBadge.textContent = 'extracting';
136
+ codeBadge.className = 'code-badge';
137
+
138
+ const formData = new FormData();
139
+ formData.append('file', file);
140
+
141
+ try {
142
+ const res = await fetch('/api/extract', {
143
+ method: 'POST',
144
+ body: formData
145
+ });
146
+
147
+ if (!res.ok) {
148
+ const err = await res.json().catch(() => ({ detail: 'Extraction failed' }));
149
+ throw new Error(err.detail || 'Extraction failed');
150
+ }
151
+
152
+ const data = await res.json();
153
+ console.log('Extraction complete:', data);
154
+
155
+ // Hide visualize button from previous run if any
156
+ visualizeBtn.classList.add('hidden');
157
+
158
+ // Start streaming (Step 2)
159
+ startStream(data.job_id);
160
+
161
+ } catch (err) {
162
+ showError(err.message);
163
+ }
164
+ }
165
+
166
+ // ── Live Streaming (Step 2) ──
167
+ function startStream(jobId) {
168
+ currentJobId = jobId; // Store immediately
169
+ showSection(streamStatus);
170
+ codeOutput.textContent = '';
171
+ codeBadge.textContent = 'streaming';
172
+ codeBadge.className = 'code-badge streaming';
173
+
174
+ const source = new EventSource(`/api/generate_stream/${jobId}`);
175
+ let hasError = false;
176
+
177
+ source.onmessage = (event) => {
178
+ try {
179
+ const payload = JSON.parse(event.data);
180
+
181
+ if (payload.done) {
182
+ source.close();
183
+ if (payload.success) {
184
+ onStreamComplete(jobId);
185
+ } else {
186
+ // Pipeline finished but failed β€” show error state
187
+ showError('Pipeline failed to generate notebook. Check the code output panel for details.');
188
+ codeBadge.textContent = 'failed';
189
+ codeBadge.className = 'code-badge';
190
+ }
191
+ return;
192
+ }
193
+
194
+ if (payload.analysis_done) {
195
+ // Show visualize button early!
196
+ if (visualizeBtn) visualizeBtn.classList.remove('hidden');
197
+ return;
198
+ }
199
+
200
+ if (payload.text) {
201
+ // Check if it's an error message
202
+ if (payload.text.includes('❌')) {
203
+ hasError = true;
204
+ }
205
+ codeOutput.textContent += payload.text;
206
+ // Auto-scroll to bottom
207
+ codeViewer.scrollTop = codeViewer.scrollHeight;
208
+ }
209
+ } catch (e) {
210
+ console.error('Parse error:', e);
211
+ }
212
+ };
213
+
214
+ source.onerror = (err) => {
215
+ console.error('SSE error:', err);
216
+ source.close();
217
+ showError('Stream connection lost. Please try again.');
218
+ };
219
+ }
220
+
221
+ function onStreamComplete(jobId) {
222
+ showSection(doneSection);
223
+ if (downloadBtn) {
224
+ downloadBtn.href = `/api/download/${jobId}`;
225
+ downloadBtn.download = "pundit_feynman_notebook.ipynb";
226
+ }
227
+ currentJobId = jobId; // Store for visualization
228
+ if (codeBadge) {
229
+ codeBadge.textContent = 'complete';
230
+ codeBadge.className = 'code-badge done';
231
+ }
232
+ }
233
+
234
+ // ── Visual Illustration Logic ──
235
+
236
+ if (visualizeBtn) {
237
+ visualizeBtn.addEventListener('click', async (e) => {
238
+ console.log('πŸ–±οΈ Visualize button CLICKED. Event object:', e);
239
+
240
+ if (!currentJobId) {
241
+ console.error('❌ Cannot visualize: currentJobId is null');
242
+ alert('Software Error: Job ID not captured yet. Please wait for analysis or refresh.');
243
+ return;
244
+ }
245
+
246
+ console.log('🎨 Requesting visualization for Job:', currentJobId);
247
+
248
+ // Disable button to prevent double-clicks
249
+ visualizeBtn.disabled = true;
250
+ const originalText = visualizeBtn.textContent;
251
+ visualizeBtn.textContent = '🎨 Painting...';
252
+
253
+ // Show float UI
254
+ if (imageFloat) imageFloat.classList.remove('hidden');
255
+ if (imagePill) imagePill.classList.add('hidden');
256
+ if (floatImage) floatImage.classList.add('hidden');
257
+ if (floatSpinner) floatSpinner.classList.remove('hidden');
258
+
259
+ try {
260
+ const url = `/api/visualize/${currentJobId}`;
261
+ console.log('🌐 Fetching:', url);
262
+
263
+ const res = await fetch(url, { method: 'POST' });
264
+ console.log('πŸ“₯ Response status:', res.status);
265
+
266
+ if (!res.ok) {
267
+ const errDetail = await res.json().catch(() => ({ detail: 'Network error' }));
268
+ throw new Error(errDetail.detail || `Server error ${res.status}`);
269
+ }
270
+
271
+ const data = await res.json();
272
+ console.log('πŸ–ΌοΈ Image received! Length:', data.image.length);
273
+
274
+ if (floatImage) {
275
+ floatImage.src = data.image;
276
+ floatImage.classList.remove('hidden');
277
+ }
278
+ if (floatSpinner) floatSpinner.classList.add('hidden');
279
+ } catch (err) {
280
+ console.error('❌ Visualization flow error:', err);
281
+ alert(`Painting failed: ${err.message}`);
282
+ if (imageFloat) imageFloat.classList.add('hidden');
283
+ } finally {
284
+ visualizeBtn.disabled = false;
285
+ visualizeBtn.textContent = originalText;
286
+ console.log('🏁 Visualize flow completed.');
287
+ }
288
+ });
289
+ }
290
+
291
+ // Drag Logic
292
+ let isDragging = false;
293
+ let startX, startY, initialX, initialY;
294
+
295
+ if (floatHeader && imageFloat) {
296
+ floatHeader.addEventListener('mousedown', (e) => {
297
+ isDragging = true;
298
+ startX = e.clientX;
299
+ startY = e.clientY;
300
+ initialX = imageFloat.offsetLeft;
301
+ initialY = imageFloat.offsetTop;
302
+ imageFloat.style.transition = 'none';
303
+ });
304
+ }
305
+
306
+ document.addEventListener('mousemove', (e) => {
307
+ if (!isDragging || !imageFloat) return;
308
+ const dx = e.clientX - startX;
309
+ const dy = e.clientY - startY;
310
+ imageFloat.style.left = (initialX + dx) + 'px';
311
+ imageFloat.style.top = (initialY + dy) + 'px';
312
+ imageFloat.style.bottom = 'auto'; // Remove fixed positioning
313
+ imageFloat.style.right = 'auto';
314
+ });
315
+
316
+ document.addEventListener('mouseup', () => {
317
+ isDragging = false;
318
+ if (imageFloat) imageFloat.style.transition = '';
319
+ });
320
+
321
+ // Minimize/Close/Download
322
+ if (floatMinimize && imageFloat && imagePill) {
323
+ floatMinimize.addEventListener('click', () => {
324
+ imageFloat.classList.add('hidden');
325
+ imagePill.classList.remove('hidden');
326
+ });
327
+ }
328
+
329
+ if (imagePill && imageFloat) {
330
+ imagePill.addEventListener('click', () => {
331
+ imageFloat.classList.remove('hidden');
332
+ imagePill.classList.add('hidden');
333
+ });
334
+ }
335
+
336
+ if (floatClose && imageFloat && imagePill) {
337
+ floatClose.addEventListener('click', () => {
338
+ imageFloat.classList.add('hidden');
339
+ imagePill.classList.add('hidden');
340
+ });
341
+ }
342
+
343
+ if (floatDownload && floatImage) {
344
+ floatDownload.addEventListener('click', () => {
345
+ if (!floatImage.src) return;
346
+ const link = document.createElement('a');
347
+ link.href = floatImage.src;
348
+ link.download = `pundit_feynman_illustration_${currentJobId}.png`;
349
+ link.click();
350
+ });
351
+ }
352
+
353
+ // ── Error & Reset ──
354
+ function showError(msg) {
355
+ showSection(errorSection);
356
+ if (errorText) errorText.textContent = msg;
357
+ if (codeBadge) {
358
+ codeBadge.textContent = 'error';
359
+ codeBadge.className = 'code-badge';
360
+ }
361
+ // Cleanup float on error β€” with null checks!
362
+ if (imageFloat) imageFloat.classList.add('hidden');
363
+ if (imagePill) imagePill.classList.add('hidden');
364
+ }
365
+
366
+ function resetUI() {
367
+ showSection(uploadSection);
368
+ if (fileInput) fileInput.value = '';
369
+ if (arxivInput) arxivInput.value = '';
370
+ if (codeOutput) codeOutput.textContent = '// Upload a paper to see the generated code here…';
371
+ if (codeBadge) {
372
+ codeBadge.textContent = 'waiting';
373
+ codeBadge.className = 'code-badge';
374
+ }
375
+ currentJobId = null;
376
+ if (visualizeBtn) visualizeBtn.classList.add('hidden');
377
+ // Cleanup float on reset
378
+ if (imageFloat) imageFloat.classList.add('hidden');
379
+ if (imagePill) imagePill.classList.add('hidden');
380
+ }
381
+
382
+ if (resetBtn) resetBtn.addEventListener('click', resetUI);
383
+ if (errorResetBtn) errorResetBtn.addEventListener('click', resetUI);