aradhyapavan commited on
Commit
6c54eb3
·
verified ·
1 Parent(s): f41975f

Upload 12 files

Browse files
Dockerfile CHANGED
@@ -2,7 +2,12 @@ FROM python:3.10-slim
2
 
3
  # Prevents Python from writing .pyc files and ensures logs are shown immediately
4
  ENV PYTHONDONTWRITEBYTECODE=1 \
5
- PYTHONUNBUFFERED=1
 
 
 
 
 
6
 
7
  # System deps
8
  RUN apt-get update && apt-get install -y --no-install-recommends \
@@ -24,7 +29,11 @@ RUN pip install --no-cache-dir -r /app/requirements.txt
24
  COPY . /app
25
 
26
  # Create necessary directories at build/run time
27
- RUN mkdir -p /app/uploads /app/faiss_indices
 
 
 
 
28
 
29
  # Expose the port used by Hugging Face Spaces
30
  EXPOSE 7860
 
2
 
3
  # Prevents Python from writing .pyc files and ensures logs are shown immediately
4
  ENV PYTHONDONTWRITEBYTECODE=1 \
5
+ PYTHONUNBUFFERED=1 \
6
+ HOME=/app \
7
+ XDG_CACHE_HOME=/app/.cache \
8
+ HF_HOME=/app/.cache/huggingface \
9
+ HUGGINGFACE_HUB_CACHE=/app/.cache/huggingface/hub \
10
+ TRANSFORMERS_CACHE=/app/.cache/huggingface/transformers
11
 
12
  # System deps
13
  RUN apt-get update && apt-get install -y --no-install-recommends \
 
29
  COPY . /app
30
 
31
  # Create necessary directories at build/run time
32
+ RUN mkdir -p /app/uploads /app/faiss_indices \
33
+ /app/.cache \
34
+ /app/.cache/huggingface \
35
+ /app/.cache/huggingface/hub \
36
+ /app/.cache/huggingface/transformers
37
 
38
  # Expose the port used by Hugging Face Spaces
39
  EXPOSE 7860
templates/base.html ADDED
@@ -0,0 +1,461 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>AI Question Generator</title>
7
+
8
+ <!-- Bootstrap CSS -->
9
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
10
+ <!-- Bootstrap Icons -->
11
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css" rel="stylesheet">
12
+ <!-- Google Fonts -->
13
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
14
+
15
+ <style>
16
+ :root {
17
+ --primary-color: #6366f1;
18
+ --primary-dark: #4f46e5;
19
+ --secondary-color: #64748b;
20
+ --success-color: #10b981;
21
+ --warning-color: #f59e0b;
22
+ --danger-color: #ef4444;
23
+ --light-bg: #f8fafc;
24
+ --card-bg: #ffffff;
25
+ --text-primary: #1e293b;
26
+ --text-secondary: #64748b;
27
+ --border-color: #e2e8f0;
28
+ --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
29
+ --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
30
+ --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
31
+ --radius-sm: 0.375rem;
32
+ --radius-md: 0.5rem;
33
+ --radius-lg: 0.75rem;
34
+ }
35
+
36
+ * {
37
+ margin: 0;
38
+ padding: 0;
39
+ box-sizing: border-box;
40
+ }
41
+
42
+ body {
43
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
44
+ background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
45
+ min-height: 100vh;
46
+ color: var(--text-primary);
47
+ line-height: 1.6;
48
+ }
49
+
50
+ .main-container {
51
+ min-height: 100vh;
52
+ display: flex;
53
+ flex-direction: column;
54
+ }
55
+
56
+ /* Header */
57
+ .header {
58
+ background: rgba(255, 255, 255, 0.9);
59
+ backdrop-filter: blur(10px);
60
+ border-bottom: 1px solid var(--border-color);
61
+ padding: 1rem 0;
62
+ position: sticky;
63
+ top: 0;
64
+ z-index: 100;
65
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
66
+ }
67
+
68
+ .header-content {
69
+ max-width: 1200px;
70
+ margin: 0 auto;
71
+ padding: 0 1rem;
72
+ display: flex;
73
+ align-items: center;
74
+ justify-content: space-between;
75
+ }
76
+
77
+ .logo {
78
+ display: flex;
79
+ align-items: center;
80
+ gap: 0.75rem;
81
+ text-decoration: none;
82
+ color: var(--text-primary);
83
+ }
84
+
85
+ .logo-icon {
86
+ width: 40px;
87
+ height: 40px;
88
+ background: linear-gradient(135deg, var(--primary-color), var(--primary-dark));
89
+ border-radius: var(--radius-md);
90
+ display: flex;
91
+ align-items: center;
92
+ justify-content: center;
93
+ color: white;
94
+ font-size: 1.25rem;
95
+ }
96
+
97
+ .logo-text {
98
+ font-weight: 600;
99
+ font-size: 1.25rem;
100
+ }
101
+
102
+ .nav-links {
103
+ display: flex;
104
+ gap: 2rem;
105
+ list-style: none;
106
+ }
107
+
108
+ .nav-link {
109
+ color: var(--text-secondary);
110
+ text-decoration: none;
111
+ font-weight: 500;
112
+ transition: color 0.2s ease;
113
+ }
114
+
115
+ .nav-link:hover {
116
+ color: var(--primary-color);
117
+ }
118
+
119
+ /* Main Content */
120
+ .main-content {
121
+ flex: 1;
122
+ padding: 2rem 0;
123
+ }
124
+
125
+ .content-wrapper {
126
+ max-width: 1200px;
127
+ margin: 0 auto;
128
+ padding: 0 1rem;
129
+ }
130
+
131
+ /* Cards */
132
+ .card {
133
+ background: var(--card-bg);
134
+ border: 1px solid var(--border-color);
135
+ border-radius: var(--radius-lg);
136
+ box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
137
+ transition: all 0.3s ease;
138
+ }
139
+
140
+ .card:hover {
141
+ box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
142
+ transform: translateY(-2px);
143
+ }
144
+
145
+ .card-header {
146
+ background: transparent;
147
+ border-bottom: 1px solid var(--border-color);
148
+ padding: 1.5rem;
149
+ }
150
+
151
+ .card-body {
152
+ padding: 1.5rem;
153
+ }
154
+
155
+ .card-title {
156
+ font-size: 1.5rem;
157
+ font-weight: 600;
158
+ color: var(--text-primary);
159
+ margin-bottom: 0.5rem;
160
+ }
161
+
162
+ .card-subtitle {
163
+ color: var(--text-secondary);
164
+ font-size: 0.875rem;
165
+ margin-bottom: 1rem;
166
+ }
167
+
168
+ /* Buttons */
169
+ .btn {
170
+ font-weight: 500;
171
+ border-radius: var(--radius-md);
172
+ padding: 0.75rem 1.5rem;
173
+ transition: all 0.2s ease;
174
+ border: none;
175
+ cursor: pointer;
176
+ display: inline-flex;
177
+ align-items: center;
178
+ gap: 0.5rem;
179
+ text-decoration: none;
180
+ }
181
+
182
+ .btn-primary {
183
+ background: linear-gradient(135deg, var(--primary-color), var(--primary-dark));
184
+ color: white;
185
+ box-shadow: var(--shadow-sm);
186
+ }
187
+
188
+ .btn-primary:hover {
189
+ transform: translateY(-1px);
190
+ box-shadow: var(--shadow-md);
191
+ color: white;
192
+ }
193
+
194
+ .btn-success {
195
+ background: linear-gradient(135deg, var(--success-color), #059669);
196
+ color: white;
197
+ }
198
+
199
+ .btn-warning {
200
+ background: linear-gradient(135deg, var(--warning-color), #d97706);
201
+ color: white;
202
+ }
203
+
204
+ .btn-danger {
205
+ background: linear-gradient(135deg, var(--danger-color), #dc2626);
206
+ color: white;
207
+ }
208
+
209
+ .btn-outline {
210
+ background: transparent;
211
+ border: 2px solid var(--border-color);
212
+ color: var(--text-secondary);
213
+ }
214
+
215
+ .btn-outline:hover {
216
+ background: var(--light-bg);
217
+ border-color: var(--primary-color);
218
+ color: var(--primary-color);
219
+ }
220
+
221
+ /* Form Elements */
222
+ .form-control {
223
+ border: 2px solid var(--border-color);
224
+ border-radius: var(--radius-md);
225
+ padding: 0.75rem 1rem;
226
+ font-size: 0.875rem;
227
+ transition: all 0.2s ease;
228
+ background: var(--card-bg);
229
+ }
230
+
231
+ .form-control:focus {
232
+ border-color: var(--primary-color);
233
+ box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
234
+ outline: none;
235
+ }
236
+
237
+ .form-label {
238
+ font-weight: 500;
239
+ color: var(--text-primary);
240
+ margin-bottom: 0.5rem;
241
+ }
242
+
243
+ /* Progress */
244
+ .progress {
245
+ height: 0.5rem;
246
+ background: var(--border-color);
247
+ border-radius: var(--radius-sm);
248
+ overflow: hidden;
249
+ }
250
+
251
+ .progress-bar {
252
+ background: linear-gradient(90deg, var(--primary-color), var(--primary-dark));
253
+ transition: width 0.3s ease;
254
+ }
255
+
256
+ /* Alerts */
257
+ .alert {
258
+ border: none;
259
+ border-radius: var(--radius-md);
260
+ padding: 1rem 1.25rem;
261
+ margin-bottom: 1rem;
262
+ }
263
+
264
+ .alert-success {
265
+ background: rgba(16, 185, 129, 0.1);
266
+ color: #065f46;
267
+ border-left: 4px solid var(--success-color);
268
+ }
269
+
270
+ .alert-danger {
271
+ background: rgba(239, 68, 68, 0.1);
272
+ color: #991b1b;
273
+ border-left: 4px solid var(--danger-color);
274
+ }
275
+
276
+ .alert-warning {
277
+ background: rgba(245, 158, 11, 0.1);
278
+ color: #92400e;
279
+ border-left: 4px solid var(--warning-color);
280
+ }
281
+
282
+ /* Utilities */
283
+ .text-gradient {
284
+ background: linear-gradient(135deg, #1e40af, #3b82f6);
285
+ -webkit-background-clip: text;
286
+ -webkit-text-fill-color: transparent;
287
+ background-clip: text;
288
+ font-weight: 600;
289
+ }
290
+
291
+ .glass-effect {
292
+ background: rgba(255, 255, 255, 0.8);
293
+ backdrop-filter: blur(10px);
294
+ border: 1px solid rgba(255, 255, 255, 0.3);
295
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
296
+ }
297
+
298
+ /* Responsive */
299
+ @media (max-width: 768px) {
300
+ .header-content {
301
+ flex-direction: column;
302
+ gap: 1rem;
303
+ }
304
+
305
+ .nav-links {
306
+ gap: 1rem;
307
+ }
308
+
309
+ .main-content {
310
+ padding: 1rem 0;
311
+ }
312
+
313
+ .card-body {
314
+ padding: 1rem;
315
+ }
316
+ }
317
+
318
+ /* Loading Animation */
319
+ .loading {
320
+ display: inline-block;
321
+ width: 20px;
322
+ height: 20px;
323
+ border: 3px solid rgba(255, 255, 255, 0.3);
324
+ border-radius: 50%;
325
+ border-top-color: white;
326
+ animation: spin 1s ease-in-out infinite;
327
+ }
328
+
329
+ @keyframes spin {
330
+ to { transform: rotate(360deg); }
331
+ }
332
+
333
+ /* Drag and Drop */
334
+ .drop-zone {
335
+ border: 2px dashed var(--border-color);
336
+ border-radius: var(--radius-lg);
337
+ padding: 3rem 2rem;
338
+ text-align: center;
339
+ transition: all 0.3s ease;
340
+ background: var(--light-bg);
341
+ cursor: pointer;
342
+ }
343
+
344
+ .drop-zone:hover,
345
+ .drop-zone.dragover {
346
+ border-color: var(--primary-color);
347
+ background: rgba(99, 102, 241, 0.05);
348
+ }
349
+
350
+ .drop-zone-icon {
351
+ font-size: 3rem;
352
+ color: var(--text-secondary);
353
+ margin-bottom: 1rem;
354
+ }
355
+
356
+ /* Topic Cards */
357
+ .topic-card {
358
+ min-height: 100px;
359
+ display: flex;
360
+ flex-direction: column;
361
+ justify-content: center;
362
+ align-items: center;
363
+ transition: all 0.3s ease;
364
+ word-wrap: break-word;
365
+ overflow-wrap: break-word;
366
+ }
367
+
368
+ .topic-card:hover {
369
+ transform: translateY(-2px);
370
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
371
+ }
372
+
373
+ .topic-text {
374
+ font-size: 0.9rem;
375
+ line-height: 1.4;
376
+ text-align: center;
377
+ word-break: break-word;
378
+ hyphens: auto;
379
+ }
380
+
381
+ /* Footer */
382
+ .footer {
383
+ background: transparent;
384
+ margin-top: auto;
385
+ }
386
+
387
+ .footer-card {
388
+ background: linear-gradient(135deg, #ffffff, #f0f4ff);
389
+ border-radius: 12px;
390
+ padding: 1rem 2rem;
391
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
392
+ display: inline-block;
393
+ max-width: 400px;
394
+ }
395
+
396
+ .footer-text {
397
+ font-family: 'Inter', sans-serif;
398
+ font-size: 0.9rem;
399
+ font-weight: 500;
400
+ color: var(--text-primary);
401
+ margin: 0;
402
+ }
403
+ </style>
404
+ </head>
405
+ <body>
406
+ <div class="main-container">
407
+ <!-- Header -->
408
+ <header class="header">
409
+ <div class="header-content">
410
+ <a href="{{ url_for('index') }}" class="logo">
411
+ <div class="logo-icon">
412
+ <i class="bi bi-robot"></i>
413
+ </div>
414
+ <div class="logo-text">AI Question Generator</div>
415
+ </a>
416
+
417
+ <nav>
418
+ <ul class="nav-links">
419
+ <li><a href="{{ url_for('index') }}" class="nav-link">Home</a></li>
420
+ <li><a href="{{ url_for('upload_pdf') }}" class="nav-link">Upload</a></li>
421
+ <li><a href="{{ url_for('configure_chunking') }}" class="nav-link">Settings</a></li>
422
+ </ul>
423
+ </nav>
424
+ </div>
425
+ </header>
426
+
427
+ <!-- Main Content -->
428
+ <main class="main-content">
429
+ <div class="content-wrapper">
430
+ {% with messages = get_flashed_messages(with_categories=true) %}
431
+ {% if messages %}
432
+ {% for category, message in messages %}
433
+ <div class="alert alert-{{ 'danger' if category == 'error' else category }}">
434
+ {{ message }}
435
+ </div>
436
+ {% endfor %}
437
+ {% endif %}
438
+ {% endwith %}
439
+
440
+ {% block content %}{% endblock %}
441
+ </div>
442
+ </main>
443
+
444
+ <!-- Footer -->
445
+ <footer class="footer mt-auto py-4">
446
+ <div class="content-wrapper">
447
+ <div class="text-center">
448
+ <div class="footer-card">
449
+ <p class="footer-text mb-0">Designed and Developed by Aradhya Pavan H S</p>
450
+ </div>
451
+ </div>
452
+ </div>
453
+ </footer>
454
+ </div>
455
+
456
+ <!-- Bootstrap JS -->
457
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
458
+
459
+ {% block scripts %}{% endblock %}
460
+ </body>
461
+ </html>
templates/configure_chunking.html ADDED
@@ -0,0 +1,172 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+
3
+ {% block content %}
4
+ <div class="row justify-content-center">
5
+ <div class="col-lg-8">
6
+ <div class="card">
7
+ <div class="card-header text-center">
8
+ <h1 class="card-title text-gradient">
9
+ <i class="bi bi-gear"></i>
10
+ Configure Processing Settings
11
+ </h1>
12
+ <p class="card-subtitle">Customize how your PDF content is processed</p>
13
+ </div>
14
+
15
+ <div class="card-body">
16
+ <form method="POST" id="configForm">
17
+ <div class="row g-4">
18
+ <!-- Chunk Size -->
19
+ <div class="col-md-6">
20
+ <label class="form-label">
21
+ <i class="bi bi-rulers"></i>
22
+ Chunk Size
23
+ </label>
24
+ <input type="number" class="form-control form-control-lg"
25
+ id="chunk_size" name="chunk_size"
26
+ value="{{ chunk_size }}" min="100" max="2000" step="50">
27
+ <div class="form-text">
28
+ Characters per chunk (100-2000)
29
+ </div>
30
+ </div>
31
+
32
+ <!-- Chunk Overlap -->
33
+ <div class="col-md-6">
34
+ <label class="form-label">
35
+ <i class="bi bi-arrow-repeat"></i>
36
+ Chunk Overlap
37
+ </label>
38
+ <input type="number" class="form-control form-control-lg"
39
+ id="chunk_overlap" name="chunk_overlap"
40
+ value="{{ chunk_overlap }}" min="0" max="500" step="10">
41
+ <div class="form-text">
42
+ Overlap between chunks (0-500)
43
+ </div>
44
+ </div>
45
+ </div>
46
+
47
+ <!-- Chunking Strategy -->
48
+ <div class="mb-4">
49
+ <label class="form-label">
50
+ <i class="bi bi-diagram-3"></i>
51
+ Processing Strategy
52
+ </label>
53
+ <select class="form-select form-select-lg" id="chunking_strategy" name="chunking_strategy">
54
+ <option value="recursive" {% if chunking_strategy == 'recursive' %}selected{% endif %}>
55
+ 🔄 Recursive Processing (Recommended)
56
+ </option>
57
+ <option value="character" {% if chunking_strategy == 'character' %}selected{% endif %}>
58
+ 📝 Character-based Processing
59
+ </option>
60
+ </select>
61
+ <div class="form-text">
62
+ <strong>Recursive:</strong> Preserves document structure<br>
63
+ <strong>Character:</strong> Simple text splitting
64
+ </div>
65
+ </div>
66
+
67
+ <!-- Live Preview -->
68
+ <div class="card bg-light mb-4">
69
+ <div class="card-body">
70
+ <h6 class="card-title">
71
+ <i class="bi bi-eye"></i>
72
+ Live Preview
73
+ </h6>
74
+ <div id="previewContent">
75
+ <p class="text-muted">Adjust settings above to see preview...</p>
76
+ </div>
77
+ </div>
78
+ </div>
79
+
80
+ <!-- Tips -->
81
+ <div class="alert alert-info">
82
+ <h6 class="alert-heading">
83
+ <i class="bi bi-lightbulb"></i>
84
+ Optimization Tips
85
+ </h6>
86
+ <ul class="mb-0">
87
+ <li><strong>Chunk Size:</strong> 1000 characters works well for most documents</li>
88
+ <li><strong>Overlap:</strong> 200 characters helps maintain context</li>
89
+ <li><strong>Strategy:</strong> Recursive processing preserves document structure</li>
90
+ </ul>
91
+ </div>
92
+
93
+ <!-- Action Buttons -->
94
+ <div class="d-flex gap-3 justify-content-center">
95
+ <a href="{{ url_for('upload_pdf') }}" class="btn btn-outline">
96
+ <i class="bi bi-arrow-left"></i>
97
+ Back to Upload
98
+ </a>
99
+ <button type="submit" class="btn btn-success btn-lg">
100
+ <i class="bi bi-check-circle"></i>
101
+ Save Settings
102
+ </button>
103
+ </div>
104
+ </form>
105
+ </div>
106
+ </div>
107
+ </div>
108
+ </div>
109
+ {% endblock %}
110
+
111
+ {% block scripts %}
112
+ <script>
113
+ document.addEventListener('DOMContentLoaded', () => {
114
+ const chunkSize = document.getElementById('chunk_size');
115
+ const chunkOverlap = document.getElementById('chunk_overlap');
116
+ const strategy = document.getElementById('chunking_strategy');
117
+ const preview = document.getElementById('previewContent');
118
+
119
+ const sampleText = "This is a sample document that demonstrates how text chunking works. The content will be split into smaller pieces based on your configuration settings. Each chunk will contain approximately the number of characters you specify, with some overlap between chunks to maintain context. This approach helps the AI better understand and process your document content for question generation.";
120
+
121
+ function updatePreview() {
122
+ const size = parseInt(chunkSize.value) || 1000;
123
+ const overlap = parseInt(chunkOverlap.value) || 200;
124
+ const selectedStrategy = strategy.value;
125
+
126
+ let chunks = [];
127
+ let start = 0;
128
+
129
+ while (start < sampleText.length) {
130
+ let end = Math.min(start + size, sampleText.length);
131
+ chunks.push(sampleText.substring(start, end));
132
+ start = end - overlap;
133
+ if (start >= sampleText.length) break;
134
+ }
135
+
136
+ const strategyText = selectedStrategy === 'recursive' ?
137
+ 'Recursive processing preserves document structure' :
138
+ 'Character-based processing for simple text splitting';
139
+
140
+ preview.innerHTML = `
141
+ <div class="mb-3">
142
+ <strong>Strategy:</strong> ${strategyText}
143
+ </div>
144
+ <div class="mb-3">
145
+ <strong>Chunk Size:</strong> ${size} characters
146
+ </div>
147
+ <div class="mb-3">
148
+ <strong>Overlap:</strong> ${overlap} characters
149
+ </div>
150
+ <div class="mb-3">
151
+ <strong>Number of Chunks:</strong> ${chunks.length}
152
+ </div>
153
+ <div class="border rounded p-3 bg-white">
154
+ <h6>Sample Chunks:</h6>
155
+ ${chunks.map((chunk, i) => `
156
+ <div class="mb-2 p-2 border-start border-primary border-3 bg-light">
157
+ <small class="text-muted">Chunk ${i + 1} (${chunk.length} chars):</small><br>
158
+ <small>${chunk.substring(0, 100)}${chunk.length > 100 ? '...' : ''}</small>
159
+ </div>
160
+ `).join('')}
161
+ </div>
162
+ `;
163
+ }
164
+
165
+ chunkSize.addEventListener('input', updatePreview);
166
+ chunkOverlap.addEventListener('input', updatePreview);
167
+ strategy.addEventListener('change', updatePreview);
168
+
169
+ updatePreview();
170
+ });
171
+ </script>
172
+ {% endblock %}
templates/configure_questions.html ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!-- templates/configure_questions.html -->
2
+ <!DOCTYPE html>
3
+ <html>
4
+ <head>
5
+ <title>Configure Questions</title>
6
+ </head>
7
+ <body>
8
+ <h1>Configure Questions</h1>
9
+ <form method="POST">
10
+ {% for topic in topics %}
11
+ <fieldset>
12
+ <legend>{{ topic }}</legend>
13
+ <h3>MCQs</h3>
14
+ <label>Easy: <input type="number" name="{{ topic }}_mcq_easy" min="0"></label>
15
+ <label>Medium: <input type="number" name="{{ topic }}_mcq_medium" min="0"></label>
16
+ <label>Hard: <input type="number" name="{{ topic }}_mcq_hard" min="0"></label>
17
+
18
+ <h3>Short Answer</h3>
19
+ <label>Easy: <input type="number" name="{{ topic }}_shortqa_easy" min="0"></label>
20
+ <label>Medium: <input type="number" name="{{ topic }}_shortqa_medium" min="0"></label>
21
+ <label>Hard: <input type="number" name="{{ topic }}_shortqa_hard" min="0"></label>
22
+ </fieldset>
23
+ {% endfor %}
24
+ <button type="submit">Generate Questions</button>
25
+ </form>
26
+ </body>
27
+ </html>
templates/index.html ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+
3
+ {% block content %}
4
+ <!-- Hero Section -->
5
+ <div class="row justify-content-center mb-5">
6
+ <div class="col-lg-10 text-center">
7
+ <div class="card glass-effect">
8
+ <div class="card-body py-5">
9
+ <h1 class="display-4 text-gradient mb-4">
10
+ <i class="bi bi-robot"></i>
11
+ AI Question Generator
12
+ </h1>
13
+ <p class="lead mb-4">
14
+ Transform your PDF documents into intelligent questions using advanced AI technology.
15
+ Perfect for educators, students, and content creators.
16
+ </p>
17
+ <div class="d-flex justify-content-center gap-3 flex-wrap">
18
+ <a href="{{ url_for('upload_pdf') }}" class="btn btn-primary btn-lg">
19
+ <i class="bi bi-upload"></i>
20
+ Start Generating Questions
21
+ </a>
22
+ <a href="{{ url_for('configure_chunking') }}" class="btn btn-outline btn-lg">
23
+ <i class="bi bi-gear"></i>
24
+ Configure Settings
25
+ </a>
26
+ </div>
27
+ </div>
28
+ </div>
29
+ </div>
30
+ </div>
31
+
32
+ <!-- Features Section -->
33
+ <div class="row g-4 mb-5">
34
+ <div class="col-md-4">
35
+ <div class="card h-100">
36
+ <div class="card-body text-center">
37
+ <div class="mb-3">
38
+ <i class="bi bi-file-earmark-pdf text-primary" style="font-size: 3rem;"></i>
39
+ </div>
40
+ <h4 class="card-title">Smart PDF Processing</h4>
41
+ <p class="text-muted">
42
+ Upload any PDF document and our AI will intelligently analyze the content to extract key topics and concepts.
43
+ </p>
44
+ </div>
45
+ </div>
46
+ </div>
47
+
48
+ <div class="col-md-4">
49
+ <div class="card h-100">
50
+ <div class="card-body text-center">
51
+ <div class="mb-3">
52
+ <i class="bi bi-question-circle text-success" style="font-size: 3rem;"></i>
53
+ </div>
54
+ <h4 class="card-title">Multiple Question Types</h4>
55
+ <p class="text-muted">
56
+ Generate multiple choice questions, short answers, and descriptive questions with customizable difficulty levels.
57
+ </p>
58
+ </div>
59
+ </div>
60
+ </div>
61
+
62
+ <div class="col-md-4">
63
+ <div class="card h-100">
64
+ <div class="card-body text-center">
65
+ <div class="mb-3">
66
+ <i class="bi bi-download text-warning" style="font-size: 3rem;"></i>
67
+ </div>
68
+ <h4 class="card-title">Export Options</h4>
69
+ <p class="text-muted">
70
+ Download your generated questions in multiple formats including PDF and CSV for easy integration.
71
+ </p>
72
+ </div>
73
+ </div>
74
+ </div>
75
+ </div>
76
+
77
+ <!-- How It Works -->
78
+ <div class="row justify-content-center">
79
+ <div class="col-lg-8">
80
+ <div class="card">
81
+ <div class="card-header text-center">
82
+ <h2 class="card-title">How It Works</h2>
83
+ <p class="card-subtitle">Simple steps to generate intelligent questions</p>
84
+ </div>
85
+ <div class="card-body">
86
+ <div class="row g-4">
87
+ <div class="col-md-4 text-center">
88
+ <div class="mb-3">
89
+ <div class="rounded-circle bg-primary text-white d-inline-flex align-items-center justify-content-center" style="width: 60px; height: 60px;">
90
+ <span class="fw-bold">1</span>
91
+ </div>
92
+ </div>
93
+ <h5>Upload PDF</h5>
94
+ <p class="text-muted">Upload your PDF document and select the pages you want to process.</p>
95
+ </div>
96
+
97
+ <div class="col-md-4 text-center">
98
+ <div class="mb-3">
99
+ <div class="rounded-circle bg-success text-white d-inline-flex align-items-center justify-content-center" style="width: 60px; height: 60px;">
100
+ <span class="fw-bold">2</span>
101
+ </div>
102
+ </div>
103
+ <h5>AI Analysis</h5>
104
+ <p class="text-muted">Our AI analyzes the content and extracts key topics and concepts.</p>
105
+ </div>
106
+
107
+ <div class="col-md-4 text-center">
108
+ <div class="mb-3">
109
+ <div class="rounded-circle bg-warning text-white d-inline-flex align-items-center justify-content-center" style="width: 60px; height: 60px;">
110
+ <span class="fw-bold">3</span>
111
+ </div>
112
+ </div>
113
+ <h5>Generate & Export</h5>
114
+ <p class="text-muted">Generate questions and download them in your preferred format.</p>
115
+ </div>
116
+ </div>
117
+ </div>
118
+ </div>
119
+ </div>
120
+ </div>
121
+ {% endblock %}
templates/manage_topics.html ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+
3
+ {% block content %}
4
+ <div class="row justify-content-center">
5
+ <div class="col-lg-8">
6
+ <div class="card">
7
+ <div class="card-header text-center">
8
+ <h1 class="card-title text-gradient">
9
+ <i class="bi bi-tags"></i>
10
+ Manage Topics
11
+ </h1>
12
+ <p class="card-subtitle">Select AI-detected topics and/or add your own</p>
13
+ </div>
14
+
15
+ <div class="card-body">
16
+ <form method="POST" id="topicsForm">
17
+ <!-- AI Detected Topics -->
18
+ <div class="mb-4">
19
+ <h5 class="mb-3">
20
+ <i class="bi bi-robot text-primary"></i>
21
+ AI-Detected Topics
22
+ </h5>
23
+ <div class="alert alert-info">
24
+ <p class="mb-2"><strong>Note:</strong> Select any AI-detected topics you want to include.</p>
25
+ <p class="mb-0">You can also add your own topics below.</p>
26
+ </div>
27
+
28
+ <div class="row g-3">
29
+ {% for topic in auto_topics %}
30
+ <div class="col-md-6">
31
+ <label class="w-100">
32
+ <div class="topic-card bg-light border rounded p-3">
33
+ <div class="form-check">
34
+ <input class="form-check-input" type="checkbox" name="selected_auto_topics" value="{{ topic }}" id="topic_{{ loop.index }}">
35
+ <label class="form-check-label ms-2" for="topic_{{ loop.index }}">
36
+ <i class="bi bi-tag text-primary me-1"></i>
37
+ <span class="topic-text fw-medium">{{ topic }}</span>
38
+ </label>
39
+ </div>
40
+ </div>
41
+ </label>
42
+ </div>
43
+ {% endfor %}
44
+ </div>
45
+ </div>
46
+
47
+ <!-- Manual Topics -->
48
+ <div class="mb-4">
49
+ <label class="form-label">
50
+ <i class="bi bi-pencil-square"></i>
51
+ Your Custom Topics
52
+ </label>
53
+ <textarea name="manual_topics" class="form-control" rows="4"
54
+ placeholder="Enter your topics separated by commas&#10;Example: Machine Learning, Data Analysis, Statistics, Python Programming"></textarea>
55
+ <div class="form-text">
56
+ <i class="bi bi-info-circle"></i>
57
+ Separate multiple topics with commas. These will be combined with selected AI topics.
58
+ </div>
59
+ </div>
60
+
61
+ <!-- Topic Preview -->
62
+ <div class="mb-4" id="topicPreview" style="display: none;">
63
+ <h6 class="mb-3">
64
+ <i class="bi bi-eye"></i>
65
+ Topic Preview
66
+ </h6>
67
+ <div class="row g-2" id="previewTopics"></div>
68
+ </div>
69
+
70
+ <!-- Submit Button -->
71
+ <div class="d-grid">
72
+ <button type="submit" class="btn btn-success btn-lg">
73
+ <i class="bi bi-save"></i>
74
+ Save Topics & Continue
75
+ </button>
76
+ </div>
77
+ </form>
78
+ </div>
79
+ </div>
80
+ </div>
81
+ </div>
82
+ {% endblock %}
83
+
84
+ {% block scripts %}
85
+ <script>
86
+ document.addEventListener('DOMContentLoaded', () => {
87
+ const textarea = document.querySelector('textarea[name="manual_topics"]');
88
+ const preview = document.getElementById('topicPreview');
89
+ const previewTopics = document.getElementById('previewTopics');
90
+
91
+ function updatePreview() {
92
+ const manual = textarea.value.trim();
93
+ const selected = Array.from(document.querySelectorAll('input[name="selected_auto_topics"]:checked')).map(cb => cb.value);
94
+
95
+ const allTopics = [];
96
+ if (manual) allTopics.push(...manual.split(',').map(t => t.trim()).filter(t => t));
97
+ allTopics.push(...selected);
98
+
99
+ if (allTopics.length > 0) {
100
+ previewTopics.innerHTML = allTopics.map(topic =>
101
+ `<div class="col-md-6 col-lg-4">
102
+ <div class="topic-card bg-primary text-white border rounded p-3 text-center">
103
+ <i class="bi bi-tag-fill mb-2 d-block" style="font-size: 1.2rem;"></i>
104
+ <div class="topic-text fw-medium">${topic}</div>
105
+ </div>
106
+ </div>`
107
+ ).join('');
108
+ preview.style.display = 'block';
109
+ } else {
110
+ preview.style.display = 'none';
111
+ }
112
+ }
113
+
114
+ textarea.addEventListener('input', updatePreview);
115
+ document.querySelectorAll('input[name="selected_auto_topics"]').forEach(cb => cb.addEventListener('change', updatePreview));
116
+
117
+ // On submit: build hidden field with selected auto topics for backend (optional convenience)
118
+ document.getElementById('topicsForm').addEventListener('submit', function(e) {
119
+ const selected = Array.from(document.querySelectorAll('input[name="selected_auto_topics"]:checked')).map(cb => cb.value);
120
+ const hidden = document.createElement('input');
121
+ hidden.type = 'hidden';
122
+ hidden.name = 'selected_auto_topics_joined';
123
+ hidden.value = selected.join(',');
124
+ this.appendChild(hidden);
125
+
126
+ if (!textarea.value.trim() && selected.length === 0) {
127
+ e.preventDefault();
128
+ alert('Please select at least one AI-detected topic or enter a custom topic.');
129
+ return false;
130
+ }
131
+ });
132
+ });
133
+ </script>
134
+ {% endblock %}
templates/results.html ADDED
@@ -0,0 +1,355 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+
3
+ {% block content %}
4
+ <div class="row justify-content-center">
5
+ <div class="col-lg-10">
6
+ <div class="d-flex justify-content-between align-items-center mb-2">
7
+ <a href="{{ url_for('select_questions') }}" class="btn btn-outline-secondary">
8
+ <i class="bi bi-arrow-left"></i> Back
9
+ </a>
10
+ </div>
11
+ <div class="card">
12
+ <div class="card-header text-center">
13
+ <h1 class="card-title text-gradient">
14
+ <i class="bi bi-file-earmark-text"></i>
15
+ Generated Questions
16
+ </h1>
17
+ <p class="card-subtitle">Your AI-generated questions are ready!</p>
18
+ </div>
19
+
20
+ <div class="card-body">
21
+ <!-- Download Actions -->
22
+ <div class="d-flex justify-content-end mb-4 flex-wrap gap-2">
23
+ <div class="btn-group">
24
+ <a href="/download/csv" class="btn btn-success">
25
+ <i class="bi bi-filetype-csv"></i>
26
+ Download CSV (Current View)
27
+ </a>
28
+ <a href="/download/pdf" class="btn btn-danger">
29
+ <i class="bi bi-filetype-pdf"></i>
30
+ Download PDF (Current View)
31
+ </a>
32
+ </div>
33
+ <div class="btn-group">
34
+ <button class="btn btn-outline-primary" onclick="exportAllQuestions(true)">
35
+ <i class="bi bi-download"></i>
36
+ Download All (Q + Answers)
37
+ </button>
38
+ <button class="btn btn-outline-secondary" onclick="exportAllQuestions(false)">
39
+ <i class="bi bi-download"></i>
40
+ Download Questions Only
41
+ </button>
42
+ </div>
43
+ <div class="btn-group">
44
+ <button class="btn btn-primary" onclick="exportSavedQuestionsPdf(true)">
45
+ <i class="bi bi-file-earmark-pdf"></i>
46
+ Saved PDF (Q + Answers)
47
+ </button>
48
+ <button class="btn btn-outline-primary" onclick="exportSavedQuestionsPdf(false)">
49
+ <i class="bi bi-file-earmark-pdf"></i>
50
+ Saved PDF (Questions Only)
51
+ </button>
52
+ </div>
53
+ </div>
54
+
55
+ <!-- Questions by Type -->
56
+ {% for q_type, topics_data in results.items() %}
57
+ <div class="mb-5">
58
+ <div class="d-flex align-items-center mb-4">
59
+ <div class="me-3">
60
+ {% if q_type == 'mcqs' %}
61
+ <i class="bi bi-list-ul text-primary" style="font-size: 2rem;"></i>
62
+ {% elif q_type == 'short_qa' %}
63
+ <i class="bi bi-chat-quote text-success" style="font-size: 2rem;"></i>
64
+ {% elif q_type == 'descriptive' %}
65
+ <i class="bi bi-file-text text-warning" style="font-size: 2rem;"></i>
66
+ {% endif %}
67
+ </div>
68
+ <div>
69
+ <h2 class="mb-1 text-capitalize">{{ q_type.replace('_', ' ') }}</h2>
70
+ <p class="text-muted mb-0">{{ topics_data|length }} topics covered</p>
71
+ </div>
72
+ </div>
73
+
74
+ <!-- Topics -->
75
+ {% for topic, difficulties_dict in topics_data.items() %}
76
+ <div class="card mb-4">
77
+ <div class="card-header">
78
+ <h4 class="mb-0">
79
+ <i class="bi bi-tag"></i>
80
+ {{ topic }}
81
+ </h4>
82
+ </div>
83
+ <div class="card-body">
84
+ <!-- One question per row -->
85
+ {% for difficulty, content in difficulties_dict.items() %}
86
+ <div class="question-row mb-4 p-3 border rounded" data-topic="{{ topic }}" data-type="{{ q_type }}" data-difficulty="{{ difficulty }}">
87
+ <div class="d-flex justify-content-between align-items-start mb-3">
88
+ <div>
89
+ <span class="badge
90
+ {% if difficulty == 'Easy' %}bg-success
91
+ {% elif difficulty == 'Medium' %}bg-warning text-dark
92
+ {% else %}bg-danger
93
+ {% endif %} fs-6 me-3">
94
+ <i class="bi bi-{% if difficulty == 'Easy' %}check-circle{% elif difficulty == 'Medium' %}exclamation-triangle{% else %}x-circle{% endif %}"></i>
95
+ {{ difficulty }}
96
+ </span>
97
+ <span class="text-muted">{{ q_type.replace('_', ' ').title() }}</span>
98
+ </div>
99
+ <div class="question-actions">
100
+ <button class="btn btn-sm btn-outline-primary" onclick="copyQuestion(this)">
101
+ <i class="bi bi-copy"></i> Copy
102
+ </button>
103
+ <button class="btn btn-sm btn-outline-success" onclick="saveQuestion(this, '{{ topic }}', '{{ difficulty }}', '{{ q_type }}')">
104
+ <i class="bi bi-bookmark-plus"></i> Save
105
+ </button>
106
+ </div>
107
+ </div>
108
+ <div class="question-content">
109
+ {{ results[q_type][topic][difficulty]
110
+ | replace('###', '')
111
+ | replace('**', '')
112
+ | replace('*', '')
113
+ | replace('---', '')
114
+ | replace('`', '')
115
+ | replace('\r\n', '\n')
116
+ | replace('\n- ', '\n• ')
117
+ | replace('- ', '• ')
118
+ | replace('\n', '<br>')
119
+ | safe }}
120
+ </div>
121
+ </div>
122
+ {% endfor %}
123
+ </div>
124
+ </div>
125
+ {% endfor %}
126
+ </div>
127
+ {% endfor %}
128
+
129
+ <!-- Action Buttons -->
130
+ <div class="text-center mt-5">
131
+ <div class="d-flex justify-content-center gap-3 flex-wrap">
132
+ <a href="{{ url_for('select_questions') }}" class="btn btn-outline-secondary">
133
+ <i class="bi bi-arrow-left"></i>
134
+ Back
135
+ </a>
136
+ <a href="{{ url_for('upload_pdf') }}" class="btn btn-primary">
137
+ <i class="bi bi-plus-circle"></i>
138
+ Generate More Questions
139
+ </a>
140
+ <button onclick="exportSavedQuestionsPdf(true)" class="btn btn-primary">
141
+ <i class="bi bi-file-earmark-pdf"></i>
142
+ Saved PDF (Q + Answers)
143
+ </button>
144
+ <button onclick="exportSavedQuestionsPdf(false)" class="btn btn-outline-primary">
145
+ <i class="bi bi-file-earmark-pdf"></i>
146
+ Saved PDF (Questions Only)
147
+ </button>
148
+ <a href="{{ url_for('index') }}" class="btn btn-outline">
149
+ <i class="bi bi-house"></i>
150
+ Back to Home
151
+ </a>
152
+ </div>
153
+ </div>
154
+ </div>
155
+ </div>
156
+ </div>
157
+ </div>
158
+ {% endblock %}
159
+
160
+ {% block scripts %}
161
+ <script>
162
+ document.addEventListener('DOMContentLoaded', () => {
163
+ // Initialize saved questions array
164
+ window.savedQuestions = JSON.parse(localStorage.getItem('savedQuestions') || '[]');
165
+
166
+ // Update save button states
167
+ updateSaveButtonStates();
168
+ });
169
+
170
+ function copyQuestion(button) {
171
+ const questionContent = button.closest('.question-row').querySelector('.question-content');
172
+ const text = htmlToPlainText(questionContent.innerHTML);
173
+
174
+ navigator.clipboard.writeText(text).then(() => {
175
+ // Show feedback
176
+ const originalText = button.innerHTML;
177
+ button.innerHTML = '<i class="bi bi-check"></i> Copied!';
178
+ button.classList.remove('btn-outline-primary');
179
+ button.classList.add('btn-success');
180
+
181
+ setTimeout(() => {
182
+ button.innerHTML = originalText;
183
+ button.classList.remove('btn-success');
184
+ button.classList.add('btn-outline-primary');
185
+ }, 2000);
186
+ });
187
+ }
188
+
189
+ function saveQuestion(button, topic, difficulty, qType) {
190
+ const row = button.closest('.question-row');
191
+ const questionContent = row.querySelector('.question-content');
192
+ const text = htmlToPlainText(questionContent.innerHTML);
193
+
194
+ const questionData = {
195
+ id: Date.now(),
196
+ topic: topic,
197
+ difficulty: difficulty,
198
+ type: qType,
199
+ content: text,
200
+ savedAt: new Date().toISOString()
201
+ };
202
+
203
+ // Add to saved questions
204
+ window.savedQuestions.push(questionData);
205
+ localStorage.setItem('savedQuestions', JSON.stringify(window.savedQuestions));
206
+
207
+ // Update button state
208
+ button.innerHTML = '<i class="bi bi-bookmark-check"></i> Saved';
209
+ button.classList.remove('btn-outline-success');
210
+ button.classList.add('btn-success');
211
+ button.disabled = true;
212
+
213
+ // Show notification
214
+ showNotification('Question saved successfully!', 'success');
215
+ }
216
+
217
+ function updateSaveButtonStates() {
218
+ const saveButtons = document.querySelectorAll('[onclick^="saveQuestion"]');
219
+ saveButtons.forEach(button => {
220
+ const onclick = button.getAttribute('onclick');
221
+ const match = onclick.match(/saveQuestion\(this, '([^']+)', '([^']+)', '([^']+)'\)/);
222
+ if (match) {
223
+ const [, topic, difficulty, qType] = match;
224
+ const questionId = `${topic}-${difficulty}-${qType}`;
225
+
226
+ if (window.savedQuestions.some(q => `${q.topic}-${q.difficulty}-${q.type}` === questionId)) {
227
+ button.innerHTML = '<i class="bi bi-bookmark-check"></i> Saved';
228
+ button.classList.remove('btn-outline-success');
229
+ button.classList.add('btn-success');
230
+ button.disabled = true;
231
+ }
232
+ }
233
+ });
234
+ }
235
+
236
+ function showNotification(message, type = 'info') {
237
+ const notification = document.createElement('div');
238
+ notification.className = `alert alert-${type} alert-dismissible fade show position-fixed`;
239
+ notification.style.cssText = 'top: 20px; right: 20px; z-index: 9999; min-width: 300px;';
240
+ notification.innerHTML = `
241
+ ${message}
242
+ <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
243
+ `;
244
+
245
+ document.body.appendChild(notification);
246
+
247
+ setTimeout(() => {
248
+ notification.remove();
249
+ }, 3000);
250
+ }
251
+
252
+ function htmlToPlainText(html) {
253
+ // Convert <br> to newlines, strip HTML tags
254
+ const tmp = document.createElement('div');
255
+ tmp.innerHTML = html.replaceAll('<br>', '\n');
256
+ return tmp.textContent || tmp.innerText || '';
257
+ }
258
+
259
+ function sanitizeQuestionOnly(text) {
260
+ // Keep questions and options, remove lines that start with Answer or Expected Answer
261
+ return text.split('\n')
262
+ .filter(line => !/^\s*(Answer:|Expected Answer:)\s*/i.test(line))
263
+ .join('\n');
264
+ }
265
+
266
+ function exportAllQuestions(includeAnswers = true) {
267
+ const rows = Array.from(document.querySelectorAll('.question-row'));
268
+ if (rows.length === 0) {
269
+ showNotification('No questions to export', 'warning');
270
+ return;
271
+ }
272
+
273
+ const data = [];
274
+ // CSV header
275
+ data.push(['QuestionType', 'Topic', 'Difficulty', 'Content']);
276
+
277
+ rows.forEach(row => {
278
+ const topic = row.getAttribute('data-topic');
279
+ const qType = row.getAttribute('data-type');
280
+ const difficulty = row.getAttribute('data-difficulty');
281
+ const htmlContent = row.querySelector('.question-content').innerHTML;
282
+ let textContent = htmlToPlainText(htmlContent);
283
+ if (!includeAnswers) {
284
+ textContent = sanitizeQuestionOnly(textContent);
285
+ }
286
+ data.push([qType, topic, difficulty, textContent]);
287
+ });
288
+
289
+ // Convert to CSV
290
+ const csv = data.map(cols => cols.map(cell => '"' + String(cell).replaceAll('"', '""') + '"').join(',')).join('\n');
291
+ const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
292
+ const url = URL.createObjectURL(blob);
293
+ const a = document.createElement('a');
294
+ a.href = url;
295
+ a.download = includeAnswers ? 'questions_with_answers.csv' : 'questions_only.csv';
296
+ document.body.appendChild(a);
297
+ a.click();
298
+ document.body.removeChild(a);
299
+ URL.revokeObjectURL(url);
300
+
301
+ showNotification(includeAnswers ? 'Downloaded Q + Answers' : 'Downloaded Questions Only', 'success');
302
+ }
303
+
304
+ // Export saved questions (from localStorage)
305
+ function exportSavedQuestions() {
306
+ if (window.savedQuestions.length === 0) {
307
+ showNotification('No saved questions to export', 'warning');
308
+ return;
309
+ }
310
+
311
+ const dataStr = JSON.stringify(window.savedQuestions, null, 2);
312
+ const dataBlob = new Blob([dataStr], {type: 'application/json'});
313
+ const url = URL.createObjectURL(dataBlob);
314
+ const link = document.createElement('a');
315
+ link.href = url;
316
+ link.download = 'saved_questions.json';
317
+ link.click();
318
+ URL.revokeObjectURL(url);
319
+
320
+ showNotification('Saved questions exported successfully!', 'success');
321
+ }
322
+
323
+ async function exportSavedQuestionsPdf(includeAnswers){
324
+ try{
325
+ const items = JSON.parse(localStorage.getItem('savedQuestions')||'[]');
326
+ if(!items.length){
327
+ showNotification('No saved questions to export', 'warning');
328
+ return;
329
+ }
330
+ const resp = await fetch('/download_saved_pdf', {
331
+ method: 'POST',
332
+ headers: { 'Content-Type': 'application/json' },
333
+ body: JSON.stringify({ items, include_answers: includeAnswers })
334
+ });
335
+ if(!resp.ok){
336
+ const text = await resp.text();
337
+ showNotification(text || 'Export failed', 'danger');
338
+ return;
339
+ }
340
+ const blob = await resp.blob();
341
+ const url = URL.createObjectURL(blob);
342
+ const a = document.createElement('a');
343
+ a.href = url;
344
+ a.download = includeAnswers ? 'saved_questions_with_answers.pdf' : 'saved_questions_only.pdf';
345
+ document.body.appendChild(a);
346
+ a.click();
347
+ a.remove();
348
+ URL.revokeObjectURL(url);
349
+ showNotification('Saved questions PDF downloaded', 'success');
350
+ }catch(e){
351
+ showNotification('Export failed', 'danger');
352
+ }
353
+ }
354
+ </script>
355
+ {% endblock %}
templates/select_pages.html ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+
3
+ {% block content %}
4
+ <div class="row justify-content-center">
5
+ <div class="col-lg-8">
6
+ <div class="card">
7
+ <div class="card-header text-center">
8
+ <h1 class="card-title text-gradient">
9
+ <i class="bi bi-file-text"></i>
10
+ Select Pages to Process
11
+ </h1>
12
+ <p class="card-subtitle">Choose which pages from your PDF to analyze</p>
13
+ </div>
14
+
15
+ <div class="card-body">
16
+ <form method="POST" id="pageForm">
17
+ <div class="row g-4">
18
+ <div class="col-md-6">
19
+ <label class="form-label">
20
+ <i class="bi bi-play-circle"></i>
21
+ Start Page
22
+ </label>
23
+ <input type="number" name="start" class="form-control form-control-lg"
24
+ min="1" max="{{ total_pages }}" value="1" required>
25
+ </div>
26
+
27
+ <div class="col-md-6">
28
+ <label class="form-label">
29
+ <i class="bi bi-stop-circle"></i>
30
+ End Page
31
+ </label>
32
+ <input type="number" name="end" class="form-control form-control-lg"
33
+ min="1" max="{{ total_pages }}" value="{{ total_pages }}" required>
34
+ </div>
35
+ </div>
36
+
37
+ <!-- Page Range Display -->
38
+ <div class="alert alert-info mt-4">
39
+ <div class="row align-items-center">
40
+ <div class="col-md-6">
41
+ <i class="bi bi-info-circle"></i>
42
+ <strong>Total Pages:</strong> {{ total_pages }}
43
+ </div>
44
+ <div class="col-md-6">
45
+ <strong>Selected Range:</strong>
46
+ <span class="badge bg-primary" id="pageRange">1-{{ total_pages }}</span>
47
+ </div>
48
+ </div>
49
+ </div>
50
+
51
+ <!-- Quick Selection Buttons -->
52
+ <div class="text-center mb-4">
53
+ <h6 class="mb-3">Quick Selection:</h6>
54
+ <div class="btn-group" role="group">
55
+ <button type="button" class="btn btn-outline btn-sm" onclick="selectAll()">
56
+ All Pages
57
+ </button>
58
+ <button type="button" class="btn btn-outline btn-sm" onclick="selectFirst()">
59
+ First Half
60
+ </button>
61
+ <button type="button" class="btn btn-outline btn-sm" onclick="selectLast()">
62
+ Last Half
63
+ </button>
64
+ </div>
65
+ </div>
66
+
67
+ <!-- Submit Button -->
68
+ <div class="d-grid">
69
+ <button type="submit" class="btn btn-success btn-lg">
70
+ <i class="bi bi-arrow-right"></i>
71
+ Process Selected Pages
72
+ </button>
73
+ </div>
74
+ </form>
75
+ </div>
76
+ </div>
77
+ </div>
78
+ </div>
79
+ {% endblock %}
80
+
81
+ {% block scripts %}
82
+ <script>
83
+ document.addEventListener('DOMContentLoaded', () => {
84
+ const startInput = document.querySelector('input[name="start"]');
85
+ const endInput = document.querySelector('input[name="end"]');
86
+ const pageRange = document.getElementById('pageRange');
87
+ const totalPages = {{ total_pages }};
88
+
89
+ function updateRange() {
90
+ const start = parseInt(startInput.value) || 1;
91
+ const end = parseInt(endInput.value) || 1;
92
+
93
+ // Validate range
94
+ if (start > end) {
95
+ endInput.value = start;
96
+ }
97
+ if (start < 1) {
98
+ startInput.value = 1;
99
+ }
100
+ if (end > totalPages) {
101
+ endInput.value = totalPages;
102
+ }
103
+
104
+ pageRange.textContent = `${startInput.value}-${endInput.value}`;
105
+ }
106
+
107
+ startInput.addEventListener('input', updateRange);
108
+ endInput.addEventListener('input', updateRange);
109
+
110
+ // Quick selection functions
111
+ window.selectAll = () => {
112
+ startInput.value = 1;
113
+ endInput.value = totalPages;
114
+ updateRange();
115
+ };
116
+
117
+ window.selectFirst = () => {
118
+ startInput.value = 1;
119
+ endInput.value = Math.ceil(totalPages / 2);
120
+ updateRange();
121
+ };
122
+
123
+ window.selectLast = () => {
124
+ startInput.value = Math.ceil(totalPages / 2) + 1;
125
+ endInput.value = totalPages;
126
+ updateRange();
127
+ };
128
+
129
+ updateRange();
130
+ });
131
+ </script>
132
+ {% endblock %}
templates/select_questions.html ADDED
@@ -0,0 +1,181 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+
3
+ {% block content %}
4
+ <div class="row justify-content-center">
5
+ <div class="col-lg-9">
6
+ <div class="card">
7
+ <div class="card-header text-center">
8
+ <h2 class="mb-1 text-gradient"><i class="bi bi-sliders2"></i> Question Settings</h2>
9
+ <div class="text-muted">Choose question types and how many you want per difficulty</div>
10
+ </div>
11
+
12
+ <div class="card-body">
13
+ <form method="POST" id="questionForm">
14
+ <!-- Question Types (Aesthetic cards) -->
15
+ <div class="mb-4">
16
+ <label class="form-label fw-semibold d-block mb-2"><i class="bi bi-grid-3x3-gap"></i> Question Types</label>
17
+ <div class="type-grid">
18
+ <!-- MCQs -->
19
+ <label class="type-card">
20
+ <input class="d-none" type="checkbox" name="q_types" value="mcqs" id="mcqCheck">
21
+ <div class="tc">
22
+ <div class="tc-icon bg-indigo"><i class="bi bi-list-ul"></i></div>
23
+ <div class="tc-text">
24
+ <div class="tc-title">MCQs</div>
25
+ <div class="tc-sub">Multiple choice questions</div>
26
+ </div>
27
+ </div>
28
+ </label>
29
+ <!-- Short Answer -->
30
+ <label class="type-card">
31
+ <input class="d-none" type="checkbox" name="q_types" value="short_qa" id="shortCheck">
32
+ <div class="tc">
33
+ <div class="tc-icon bg-teal"><i class="bi bi-chat-dots"></i></div>
34
+ <div class="tc-text">
35
+ <div class="tc-title">Short Answer</div>
36
+ <div class="tc-sub">Brief factual responses</div>
37
+ </div>
38
+ </div>
39
+ </label>
40
+ <!-- Descriptive -->
41
+ <label class="type-card">
42
+ <input class="d-none" type="checkbox" name="q_types" value="descriptive" id="descCheck">
43
+ <div class="tc">
44
+ <div class="tc-icon bg-amber"><i class="bi bi-pencil-square"></i></div>
45
+ <div class="tc-text">
46
+ <div class="tc-title">Descriptive</div>
47
+ <div class="tc-sub">Detailed long-form answers</div>
48
+ </div>
49
+ </div>
50
+ </label>
51
+ </div>
52
+ <div class="form-text mt-2">Tip: You can select multiple types at once.</div>
53
+ </div>
54
+
55
+ <!-- MCQs counts -->
56
+ <div id="mcqsBlock" class="qpanel" style="display:none;">
57
+ <div class="qpanel-h"><i class="bi bi-list-ul"></i> MCQs</div>
58
+ <div class="row g-3">
59
+ <div class="col-md-4"><label class="form-label">Easy</label><input type="number" name="mcqs_easy" class="form-control" min="0" max="10" value="1"></div>
60
+ <div class="col-md-4"><label class="form-label">Medium</label><input type="number" name="mcqs_medium" class="form-control" min="0" max="10" value="1"></div>
61
+ <div class="col-md-4"><label class="form-label">Hard</label><input type="number" name="mcqs_hard" class="form-control" min="0" max="10" value="1"></div>
62
+ </div>
63
+ </div>
64
+
65
+ <!-- Short Answer counts -->
66
+ <div id="shortBlock" class="qpanel" style="display:none;">
67
+ <div class="qpanel-h"><i class="bi bi-chat-dots"></i> Short Answer</div>
68
+ <div class="row g-3">
69
+ <div class="col-md-4"><label class="form-label">Easy</label><input type="number" name="short_qa_easy" class="form-control" min="0" max="10" value="1"></div>
70
+ <div class="col-md-4"><label class="form-label">Medium</label><input type="number" name="short_qa_medium" class="form-control" min="0" max="10" value="1"></div>
71
+ <div class="col-md-4"><label class="form-label">Hard</label><input type="number" name="short_qa_hard" class="form-control" min="0" max="10" value="1"></div>
72
+ </div>
73
+ </div>
74
+
75
+ <!-- Descriptive counts -->
76
+ <div id="descBlock" class="qpanel" style="display:none;">
77
+ <div class="qpanel-h"><i class="bi bi-pencil-square"></i> Descriptive</div>
78
+ <div class="row g-3">
79
+ <div class="col-md-4"><label class="form-label">Easy</label><input type="number" name="descriptive_easy" class="form-control" min="0" max="10" value="1"></div>
80
+ <div class="col-md-4"><label class="form-label">Medium</label><input type="number" name="descriptive_medium" class="form-control" min="0" max="10" value="1"></div>
81
+ <div class="col-md-4"><label class="form-label">Hard</label><input type="number" name="descriptive_hard" class="form-control" min="0" max="10" value="1"></div>
82
+ </div>
83
+ </div>
84
+
85
+ <div class="d-flex gap-2 mt-3 justify-content-between">
86
+ <a href="{{ url_for('manage_topics') }}" class="btn btn-outline-secondary">
87
+ <i class="bi bi-arrow-left"></i> Back
88
+ </a>
89
+ <button type="submit" class="btn btn-primary btn-lg" id="generateBtn">
90
+ <i class="bi bi-magic"></i>
91
+ Generate Questions
92
+ </button>
93
+ </div>
94
+ </form>
95
+ </div>
96
+ </div>
97
+ </div>
98
+ </div>
99
+
100
+ <!-- Loading Overlay -->
101
+ <div id="loadingOverlay" class="loading-overlay" style="display:none;">
102
+ <div class="cube-loader">
103
+ <div class="cube cube1"></div>
104
+ <div class="cube cube2"></div>
105
+ <div class="cube cube3"></div>
106
+ <div class="cube cube4"></div>
107
+ </div>
108
+ <div class="loading-msg">Generating your questions… This may take a moment.</div>
109
+ </div>
110
+ {% endblock %}
111
+
112
+ {% block scripts %}
113
+ <script>
114
+ document.addEventListener('DOMContentLoaded', () => {
115
+ const mcqCheck = document.getElementById('mcqCheck');
116
+ const shortCheck = document.getElementById('shortCheck');
117
+ const descCheck = document.getElementById('descCheck');
118
+
119
+ const mcqsBlock = document.getElementById('mcqsBlock');
120
+ const shortBlock = document.getElementById('shortBlock');
121
+ const descBlock = document.getElementById('descBlock');
122
+ const form = document.getElementById('questionForm');
123
+ const overlay = document.getElementById('loadingOverlay');
124
+ const generateBtn = document.getElementById('generateBtn');
125
+
126
+ function toggleBlocks() {
127
+ mcqsBlock.style.display = mcqCheck.checked ? 'block' : 'none';
128
+ shortBlock.style.display = shortCheck.checked ? 'block' : 'none';
129
+ descBlock.style.display = descCheck.checked ? 'block' : 'none';
130
+ }
131
+
132
+ [mcqCheck, shortCheck, descCheck].forEach(el => el.addEventListener('change', () => {
133
+ const lbl = el.closest('label.type-card');
134
+ if (lbl) lbl.classList.toggle('active', el.checked);
135
+ toggleBlocks();
136
+ }));
137
+
138
+ document.querySelectorAll('label.type-card input').forEach(el => {
139
+ const lbl = el.closest('label.type-card');
140
+ if (lbl) lbl.classList.toggle('active', el.checked);
141
+ });
142
+ toggleBlocks();
143
+
144
+ generateBtn.addEventListener('click', (e) => {
145
+ e.preventDefault();
146
+ overlay.style.display = 'flex';
147
+ setTimeout(() => form.requestSubmit(), 60);
148
+ });
149
+ });
150
+ </script>
151
+
152
+ <style>
153
+ /* Type card grid */
154
+ .type-grid{ display:grid; grid-template-columns: repeat(auto-fit,minmax(220px,1fr)); gap:12px; }
155
+ .type-card{ cursor:pointer; }
156
+ .type-card .tc{ display:flex; align-items:center; gap:12px; padding:14px 16px; border:1.5px solid var(--border-color); border-radius:12px; background:#fff; box-shadow: var(--shadow-sm); transition:.2s ease; }
157
+ .type-card:hover .tc{ border-color: var(--primary-color); box-shadow: var(--shadow-md); }
158
+ .type-card.active .tc{ border-color: var(--primary-color); background: linear-gradient(135deg,#eef2ff,#ffffff); box-shadow: var(--shadow-md); }
159
+ .tc-icon{ width:40px; height:40px; border-radius:10px; display:flex; align-items:center; justify-content:center; color:#fff; }
160
+ .bg-indigo{ background:#6366f1; }
161
+ .bg-teal{ background:#10b981; }
162
+ .bg-amber{ background:#f59e0b; }
163
+ .tc-title{ font-weight:600; }
164
+ .tc-sub{ font-size:.85rem; color: var(--text-secondary); }
165
+
166
+ /* Panels */
167
+ .qpanel{ border:1px solid var(--border-color); border-radius:12px; padding:16px; background:#fff; }
168
+ .qpanel-h{ font-weight:600; margin-bottom:8px; color:#111827; }
169
+
170
+ /* Loading overlay */
171
+ .loading-overlay{ position:fixed; inset:0; background:rgba(255,255,255,.9); display:flex; align-items:center; justify-content:center; flex-direction:column; gap:1.25rem; z-index:1050; }
172
+ .loading-msg{ color:#1e293b; font-weight:600; }
173
+ .cube-loader{ width:60px; height:60px; position:relative; transform:rotateX(45deg) rotateZ(45deg); transform-style:preserve-3d; }
174
+ .cube{ position:absolute; width:100%; height:100%; box-sizing:border-box; border:4px solid #6366f1; animation: cube-rotate 1.4s infinite ease-in-out; }
175
+ .cube1{ transform:translateZ(30px); }
176
+ .cube2{ transform:rotateY(90deg) translateZ(30px); animation-delay:.1s; }
177
+ .cube3{ transform:rotateX(90deg) translateZ(30px); animation-delay:.2s; }
178
+ .cube4{ transform:rotateY(180deg) translateZ(30px); animation-delay:.3s; }
179
+ @keyframes cube-rotate{ 0%,100%{ border-color:#6366f1; } 50%{ border-color:#4f46e5; } }
180
+ </style>
181
+ {% endblock %}
templates/select_topics.html ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Select Topics</title>
5
+ </head>
6
+ <body>
7
+ <h1>Detected Topics</h1>
8
+ <ul>
9
+ {% for topic in auto_topics %}
10
+ <li>{{ topic }}</li>
11
+ {% endfor %}
12
+ </ul>
13
+ <form method="POST">
14
+ <label>Add more topics (comma-separated, max 5):
15
+ <input type="text" name="manual_topics">
16
+ </label>
17
+ <button type="submit">Continue</button>
18
+ </form>
19
+ </body>
20
+ </html>
templates/topics_detected.html ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!-- templates/topics_detected.html -->
2
+ <!DOCTYPE html>
3
+ <html>
4
+ <head>
5
+ <title>Select Topics</title>
6
+ </head>
7
+ <body>
8
+ <h1>Detected Topics</h1>
9
+ <ul>
10
+ {% for topic in auto_topics %}
11
+ <li>{{ topic }}</li>
12
+ {% endfor %}
13
+ </ul>
14
+ <form method="POST">
15
+ <label>Add more topics (comma-separated, max 5):
16
+ <input type="text" name="manual_topics">
17
+ </label>
18
+ <button type="submit">Continue</button>
19
+ </form>
20
+ </body>
21
+ </html>
templates/upload.html ADDED
@@ -0,0 +1,167 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+
3
+ {% block content %}
4
+ <div class="row justify-content-center">
5
+ <div class="col-lg-8">
6
+ <div class="card">
7
+ <div class="card-header text-center">
8
+ <h1 class="card-title text-gradient">
9
+ <i class="bi bi-cloud-upload"></i>
10
+ Upload PDF Document
11
+ </h1>
12
+ <p class="card-subtitle">Upload your PDF to generate intelligent questions</p>
13
+ </div>
14
+
15
+ <div class="card-body">
16
+ <!-- Settings Link -->
17
+ <div class="text-center mb-4">
18
+ <a href="{{ url_for('configure_chunking') }}" class="btn btn-outline">
19
+ <i class="bi bi-gear"></i>
20
+ Configure Processing Settings
21
+ </a>
22
+ </div>
23
+
24
+ <!-- Upload Form -->
25
+ <form method="POST" enctype="multipart/form-data" id="uploadForm">
26
+ <div class="drop-zone" id="dropZone">
27
+ <input type="file" name="pdf" id="pdfInput" class="d-none" accept=".pdf" required>
28
+
29
+ <div class="drop-zone-icon">
30
+ <i class="bi bi-file-earmark-pdf"></i>
31
+ </div>
32
+
33
+ <h4 class="mb-3">Drag & Drop Your PDF Here</h4>
34
+ <p class="text-muted mb-4">or click to browse files</p>
35
+
36
+ <button type="button" class="btn btn-primary" id="browseBtn">
37
+ <i class="bi bi-folder2-open"></i>
38
+ Choose File
39
+ </button>
40
+
41
+ <div id="fileInfo" class="mt-4"></div>
42
+ </div>
43
+
44
+ <!-- Progress Bar -->
45
+ <div class="progress mt-4 d-none" id="uploadProgress">
46
+ <div class="progress-bar" role="progressbar" style="width: 0%"></div>
47
+ </div>
48
+
49
+ <!-- Upload Button (hidden once auto-submit is enabled) -->
50
+ <div class="d-grid mt-4">
51
+ <button type="submit" class="btn btn-success btn-lg" id="uploadBtn" disabled>
52
+ <i class="bi bi-upload"></i>
53
+ Process PDF
54
+ </button>
55
+ </div>
56
+ </form>
57
+ </div>
58
+ </div>
59
+ </div>
60
+ </div>
61
+ {% endblock %}
62
+
63
+ {% block scripts %}
64
+ <script>
65
+ document.addEventListener('DOMContentLoaded', function() {
66
+ const dropZone = document.getElementById('dropZone');
67
+ const fileInput = document.getElementById('pdfInput');
68
+ const browseBtn = document.getElementById('browseBtn');
69
+ const uploadBtn = document.getElementById('uploadBtn');
70
+ const fileInfo = document.getElementById('fileInfo');
71
+ const uploadProgress = document.getElementById('uploadProgress');
72
+ const progressBar = uploadProgress.querySelector('.progress-bar');
73
+ const form = document.getElementById('uploadForm');
74
+
75
+ let isSubmitting = false;
76
+
77
+ // Click to browse (only via button, not the entire drop-zone)
78
+ browseBtn.addEventListener('click', (e) => {
79
+ e.preventDefault();
80
+ if (isSubmitting) return;
81
+ fileInput.click();
82
+ });
83
+
84
+ // File input change
85
+ fileInput.addEventListener('change', (e) => handleFile(e.target.files[0]));
86
+
87
+ // Drag and drop
88
+ dropZone.addEventListener('dragover', (e) => {
89
+ e.preventDefault();
90
+ dropZone.classList.add('dragover');
91
+ });
92
+
93
+ dropZone.addEventListener('dragleave', () => {
94
+ dropZone.classList.remove('dragover');
95
+ });
96
+
97
+ dropZone.addEventListener('drop', (e) => {
98
+ e.preventDefault();
99
+ if (isSubmitting) return;
100
+ dropZone.classList.remove('dragover');
101
+ const file = e.dataTransfer.files && e.dataTransfer.files[0];
102
+ if (file) handleFile(file);
103
+ });
104
+
105
+ function autoSubmit() {
106
+ if (isSubmitting) return;
107
+ isSubmitting = true;
108
+ // Trigger form submit programmatically and disable controls
109
+ uploadBtn.innerHTML = '<span class="loading"></span> Processing...';
110
+ uploadBtn.disabled = true;
111
+ browseBtn.disabled = true;
112
+ uploadProgress.classList.remove('d-none');
113
+
114
+ // Simulate progress
115
+ let progress = 0;
116
+ const interval = setInterval(() => {
117
+ progress += Math.random() * 15;
118
+ if (progress > 90) progress = 90;
119
+ progressBar.style.width = progress + '%';
120
+ }, 200);
121
+
122
+ // Submit
123
+ form.submit();
124
+
125
+ // Clear interval after a short delay to avoid lingering timer
126
+ setTimeout(() => {
127
+ clearInterval(interval);
128
+ progressBar.style.width = '100%';
129
+ }, 2000);
130
+ }
131
+
132
+ function handleFile(file) {
133
+ if (file && file.type === 'application/pdf') {
134
+ const fileSize = (file.size / 1024 / 1024).toFixed(2);
135
+ fileInfo.innerHTML = `
136
+ <div class="alert alert-success">
137
+ <i class="bi bi-check-circle"></i>
138
+ <strong>${file.name}</strong> (${fileSize} MB)
139
+ <br>
140
+ <small>Uploading...</small>
141
+ </div>
142
+ `;
143
+ uploadBtn.disabled = true;
144
+ // Auto-submit immediately after a valid file is selected/dropped
145
+ // Use microtask to allow DOM to update before submit
146
+ setTimeout(autoSubmit, 0);
147
+ } else {
148
+ fileInfo.innerHTML = `
149
+ <div class="alert alert-danger">
150
+ <i class="bi bi-exclamation-triangle"></i>
151
+ Please select a valid PDF file
152
+ </div>
153
+ `;
154
+ uploadBtn.disabled = true;
155
+ }
156
+ }
157
+
158
+ // Prevent manual double submit
159
+ form.addEventListener('submit', function(e) {
160
+ if (isSubmitting) return; // allow the first programmatic submit
161
+ isSubmitting = true;
162
+ uploadBtn.disabled = true;
163
+ browseBtn.disabled = true;
164
+ }, { once: true });
165
+ });
166
+ </script>
167
+ {% endblock %}