Spaces:
Sleeping
Sleeping
Upload 12 files
Browse files- Dockerfile +11 -2
- templates/base.html +461 -0
- templates/configure_chunking.html +172 -0
- templates/configure_questions.html +27 -0
- templates/index.html +121 -0
- templates/manage_topics.html +134 -0
- templates/results.html +355 -0
- templates/select_pages.html +132 -0
- templates/select_questions.html +181 -0
- templates/select_topics.html +20 -0
- templates/topics_detected.html +21 -0
- templates/upload.html +167 -0
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 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 %}
|