Asalun commited on
Commit
0c86a5d
Β·
verified Β·
1 Parent(s): 88a019c

Upload 4 files

Browse files
Files changed (4) hide show
  1. README.md +14 -11
  2. app.py +259 -0
  3. index.html +327 -0
  4. requirement.txt +8 -0
README.md CHANGED
@@ -1,13 +1,16 @@
 
1
  ---
2
- title: Study RAG Final
3
- emoji: πŸ“Š
4
- colorFrom: purple
5
- colorTo: red
6
- sdk: gradio
7
- sdk_version: 6.5.1
8
- app_file: app.py
9
- pinned: false
10
- license: mit
11
- ---
12
 
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
  ---
 
 
 
 
 
 
 
 
 
 
3
 
4
+ ## 🧩 Notes / Next Step (when ready)
5
+
6
+ To make this a real RAG app, you’ll later add:
7
+ - Backend API (FastAPI)
8
+ - Notebook + PDF parsers
9
+ - Chunking + embeddings + vector store
10
+ - Endpoints:
11
+ - `/chat`
12
+ - `/generate_notes`
13
+ - `/generate_quiz`
14
+ - Then connect the frontend `sendMessage()` to your backend with `fetch()`.
15
+
16
+ For now, this Space is intentionally lightweight and fast.
app.py ADDED
@@ -0,0 +1,259 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import re
2
+ import json
3
+ import numpy as np
4
+ import gradio as gr
5
+
6
+ from pypdf import PdfReader
7
+ import nbformat
8
+
9
+ import faiss
10
+ from sentence_transformers import SentenceTransformer
11
+ from transformers import pipeline
12
+
13
+ # ----------------------------
14
+ # Globals (kept in memory)
15
+ # ----------------------------
16
+ EMBED_MODEL_NAME = "sentence-transformers/all-MiniLM-L6-v2"
17
+ GEN_MODEL_NAME = "google/flan-t5-base" # CPU-friendly. Not as strong as GPT, but works.
18
+
19
+ embedder = SentenceTransformer(EMBED_MODEL_NAME)
20
+ generator = pipeline("text2text-generation", model=GEN_MODEL_NAME)
21
+
22
+ INDEX = None
23
+ CHUNKS = [] # list of dict: {text, source, loc}
24
+ EMBEDS = None
25
+
26
+ # ----------------------------
27
+ # Helpers
28
+ # ----------------------------
29
+ def clean_text(t: str) -> str:
30
+ t = t.replace("\u00a0", " ")
31
+ t = re.sub(r"\s+", " ", t).strip()
32
+ return t
33
+
34
+ def chunk_text(text: str, chunk_size: int = 900, overlap: int = 150):
35
+ # chunk_size/overlap are characters here (simpler + stable)
36
+ text = clean_text(text)
37
+ if not text:
38
+ return []
39
+ chunks = []
40
+ start = 0
41
+ while start < len(text):
42
+ end = min(len(text), start + chunk_size)
43
+ chunk = text[start:end]
44
+ chunks.append(chunk)
45
+ if end == len(text):
46
+ break
47
+ start = max(0, end - overlap)
48
+ return chunks
49
+
50
+ def read_pdf(path: str):
51
+ reader = PdfReader(path)
52
+ pages = []
53
+ for i, page in enumerate(reader.pages):
54
+ txt = page.extract_text() or ""
55
+ txt = clean_text(txt)
56
+ if txt:
57
+ pages.append((i + 1, txt))
58
+ return pages
59
+
60
+ def read_ipynb(path: str):
61
+ nb = nbformat.read(path, as_version=4)
62
+ cells = []
63
+ for i, cell in enumerate(nb.cells):
64
+ if cell.get("cell_type") in ("markdown", "code"):
65
+ src = cell.get("source", "")
66
+ src = clean_text(src)
67
+ if src:
68
+ cells.append((i + 1, cell.get("cell_type"), src))
69
+ return cells
70
+
71
+ def build_index(file_objs, chunk_size, overlap):
72
+ global INDEX, CHUNKS, EMBEDS
73
+
74
+ CHUNKS = []
75
+ texts_for_embed = []
76
+
77
+ for f in file_objs:
78
+ path = f.name
79
+ name = path.split("/")[-1].split("\\")[-1]
80
+
81
+ if name.lower().endswith(".pdf"):
82
+ for page_no, page_text in read_pdf(path):
83
+ for j, ch in enumerate(chunk_text(page_text, chunk_size, overlap), start=1):
84
+ CHUNKS.append({
85
+ "text": ch,
86
+ "source": name,
87
+ "loc": f"page {page_no} Β· chunk {j}"
88
+ })
89
+ texts_for_embed.append(ch)
90
+
91
+ elif name.lower().endswith(".ipynb"):
92
+ for cell_no, cell_type, cell_text in read_ipynb(path):
93
+ for j, ch in enumerate(chunk_text(cell_text, chunk_size, overlap), start=1):
94
+ CHUNKS.append({
95
+ "text": ch,
96
+ "source": name,
97
+ "loc": f"{cell_type} cell {cell_no} Β· chunk {j}"
98
+ })
99
+ texts_for_embed.append(ch)
100
+
101
+ if not texts_for_embed:
102
+ INDEX = None
103
+ EMBEDS = None
104
+ return "❌ No readable text found in the uploaded files."
105
+
106
+ # Embed
107
+ X = embedder.encode(texts_for_embed, normalize_embeddings=True, show_progress_bar=False)
108
+ EMBEDS = X.astype("float32")
109
+
110
+ # FAISS index (cosine via inner product with normalized vectors)
111
+ dim = EMBEDS.shape[1]
112
+ INDEX = faiss.IndexFlatIP(dim)
113
+ INDEX.add(EMBEDS)
114
+
115
+ return f"βœ… Indexed {len(file_objs)} files β†’ {len(CHUNKS)} chunks."
116
+
117
+ def retrieve(query: str, k: int = 4):
118
+ if INDEX is None:
119
+ return []
120
+ q = embedder.encode([query], normalize_embeddings=True)
121
+ q = q.astype("float32")
122
+ scores, idxs = INDEX.search(q, k)
123
+ out = []
124
+ for score, idx in zip(scores[0], idxs[0]):
125
+ if idx < 0:
126
+ continue
127
+ item = CHUNKS[idx]
128
+ out.append({**item, "score": float(score)})
129
+ return out
130
+
131
+ def make_context_snippets(items):
132
+ # Keep context small so flan-t5 doesn't choke
133
+ parts = []
134
+ for i, it in enumerate(items, start=1):
135
+ snippet = it["text"]
136
+ if len(snippet) > 700:
137
+ snippet = snippet[:700] + "..."
138
+ parts.append(f"[{i}] Source: {it['source']} ({it['loc']})\n{snippet}")
139
+ return "\n\n".join(parts)
140
+
141
+ def gen_answer(question: str, retrieved):
142
+ context = make_context_snippets(retrieved)
143
+ prompt = (
144
+ "You are a study assistant. Answer ONLY using the provided sources.\n"
145
+ "If the sources do not contain enough info, say: 'Not enough information in the provided files.'\n\n"
146
+ f"SOURCES:\n{context}\n\n"
147
+ f"QUESTION: {question}\n"
148
+ "ANSWER (with short bullet points, then 1-line summary):"
149
+ )
150
+ out = generator(prompt, max_new_tokens=256, do_sample=False)[0]["generated_text"]
151
+ return out
152
+
153
+ def gen_notes(retrieved):
154
+ context = make_context_snippets(retrieved)
155
+ prompt = (
156
+ "Create clean study notes ONLY from the sources.\n"
157
+ "Output as headings + bullets. Keep it concise.\n\n"
158
+ f"SOURCES:\n{context}\n\n"
159
+ "NOTES:"
160
+ )
161
+ out = generator(prompt, max_new_tokens=256, do_sample=False)[0]["generated_text"]
162
+ return out
163
+
164
+ def gen_quiz(retrieved, n_q: int):
165
+ context = make_context_snippets(retrieved)
166
+ prompt = (
167
+ "Create a tricky quiz ONLY from the sources.\n"
168
+ f"Generate {n_q} questions.\n"
169
+ "Mix: MCQ, True/False, short answer.\n"
170
+ "Provide ANSWER KEY at the end.\n\n"
171
+ f"SOURCES:\n{context}\n\n"
172
+ "QUIZ:"
173
+ )
174
+ out = generator(prompt, max_new_tokens=512, do_sample=False)[0]["generated_text"]
175
+ return out
176
+
177
+ # ----------------------------
178
+ # Gradio callbacks
179
+ # ----------------------------
180
+ def on_index(files, chunk_size, overlap):
181
+ if not files:
182
+ return "❌ Upload at least 1 PDF or IPYNB."
183
+ return build_index(files, int(chunk_size), int(overlap))
184
+
185
+ def on_chat(user_msg, history, k):
186
+ if INDEX is None:
187
+ return history + [[user_msg, "❌ Please upload files and click **Index Files** first."]]
188
+ retrieved = retrieve(user_msg, k=int(k))
189
+ answer = gen_answer(user_msg, retrieved)
190
+
191
+ # Add citations
192
+ cites = "\n".join([f"- {i+1}. {it['source']} β€” {it['loc']}" for i, it in enumerate(retrieved)])
193
+ final = f"{answer}\n\nCitations:\n{cites}" if retrieved else (answer + "\n\nCitations:\n- (none)")
194
+ return history + [[user_msg, final]]
195
+
196
+ def on_notes(topic, k):
197
+ if INDEX is None:
198
+ return "❌ Please upload files and click **Index Files** first."
199
+ query = topic.strip() if topic.strip() else "main topics summary"
200
+ retrieved = retrieve(query, k=int(k))
201
+ notes = gen_notes(retrieved)
202
+ cites = "\n".join([f"- {i+1}. {it['source']} β€” {it['loc']}" for i, it in enumerate(retrieved)])
203
+ return f"{notes}\n\nCitations:\n{cites}"
204
+
205
+ def on_quiz(topic, n_q, k):
206
+ if INDEX is None:
207
+ return "❌ Please upload files and click **Index Files** first."
208
+ query = topic.strip() if topic.strip() else "important concepts"
209
+ retrieved = retrieve(query, k=int(k))
210
+ quiz = gen_quiz(retrieved, int(n_q))
211
+ cites = "\n".join([f"- {i+1}. {it['source']} β€” {it['loc']}" for i, it in enumerate(retrieved)])
212
+ return f"{quiz}\n\nCitations:\n{cites}"
213
+
214
+ # ----------------------------
215
+ # UI
216
+ # ----------------------------
217
+ with gr.Blocks(title="Study RAG Assistant (Functional MVP)") as demo:
218
+ gr.Markdown("## πŸ“š Study RAG Assistant (Functional MVP)\nUpload PDFs + IPYNBs β†’ Index β†’ Chat/Notes/Quiz grounded in your files.")
219
+
220
+ with gr.Row():
221
+ files = gr.File(
222
+ label="Upload (up to 10 .ipynb + 5 .pdf)",
223
+ file_count="multiple",
224
+ file_types=[".pdf", ".ipynb"]
225
+ )
226
+
227
+ with gr.Column():
228
+ chunk_size = gr.Slider(300, 2000, value=900, step=50, label="Chunk size (chars)")
229
+ overlap = gr.Slider(0, 500, value=150, step=10, label="Chunk overlap (chars)")
230
+ index_btn = gr.Button("Index Files", variant="primary")
231
+ index_status = gr.Textbox(label="Index status", interactive=False)
232
+
233
+ index_btn.click(on_index, inputs=[files, chunk_size, overlap], outputs=index_status)
234
+
235
+ with gr.Tabs():
236
+ with gr.Tab("Chat"):
237
+ k_chat = gr.Slider(2, 8, value=4, step=1, label="Top-k retrieved chunks")
238
+ chatbot = gr.Chatbot(height=420)
239
+ msg = gr.Textbox(label="Ask something about your files", placeholder="e.g., Explain backpropagation from my lecture notes")
240
+ send = gr.Button("Ask", variant="primary")
241
+ send.click(on_chat, inputs=[msg, chatbot, k_chat], outputs=chatbot)
242
+ msg.submit(on_chat, inputs=[msg, chatbot, k_chat], outputs=chatbot)
243
+
244
+ with gr.Tab("Notes"):
245
+ k_notes = gr.Slider(2, 8, value=4, step=1, label="Top-k retrieved chunks")
246
+ topic_notes = gr.Textbox(label="Topic (optional)", placeholder="e.g., activation functions")
247
+ notes_btn = gr.Button("Generate Notes", variant="primary")
248
+ notes_out = gr.Textbox(label="Notes output", lines=18)
249
+ notes_btn.click(on_notes, inputs=[topic_notes, k_notes], outputs=notes_out)
250
+
251
+ with gr.Tab("Quiz"):
252
+ k_quiz = gr.Slider(2, 8, value=4, step=1, label="Top-k retrieved chunks")
253
+ topic_quiz = gr.Textbox(label="Topic (optional)", placeholder="e.g., CNN vs RNN")
254
+ n_q = gr.Slider(10, 50, value=10, step=1, label="Number of questions")
255
+ quiz_btn = gr.Button("Generate Quiz", variant="primary")
256
+ quiz_out = gr.Textbox(label="Quiz output", lines=18)
257
+ quiz_btn.click(on_quiz, inputs=[topic_quiz, n_q, k_quiz], outputs=quiz_out)
258
+
259
+ demo.launch()
index.html ADDED
@@ -0,0 +1,327 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>Study RAG Assistant</title>
7
+ <style>
8
+ :root {
9
+ --primary: #2563EB;
10
+ --primary-hover: #1D4ED8;
11
+ --bg-body: #F3F4F6;
12
+ --bg-white: #FFFFFF;
13
+ --text-main: #1F2937;
14
+ --text-muted: #6B7280;
15
+ --border: #E5E7EB;
16
+ --success: #10B981;
17
+ --warning: #F59E0B;
18
+ --error: #EF4444;
19
+ --font-main: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
20
+ --font-mono: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
21
+ }
22
+
23
+ * { box-sizing: border-box; }
24
+ body { margin: 0; font-family: var(--font-main); background: var(--bg-body); color: var(--text-main); height: 100vh; display: flex; flex-direction: column; overflow: hidden; }
25
+
26
+ header {
27
+ height: 60px; background: var(--bg-white); border-bottom: 1px solid var(--border);
28
+ display: flex; align-items: center; justify-content: space-between; padding: 0 20px;
29
+ box-shadow: 0 1px 2px rgba(0,0,0,0.05); z-index: 10;
30
+ }
31
+ .brand { font-weight: 700; font-size: 18px; color: var(--primary); display: flex; align-items: center; gap: 8px; }
32
+ .header-controls { display: flex; gap: 20px; align-items: center; }
33
+ .control-group { display: flex; align-items: center; gap: 8px; font-size: 13px; }
34
+ select, input[type="number"] {
35
+ padding: 4px 8px; border: 1px solid var(--border); border-radius: 4px; font-size: 13px; background: var(--bg-white);
36
+ }
37
+ .stats-pill {
38
+ background: #EEF2FF; color: var(--primary); padding: 4px 12px; border-radius: 99px; font-size: 12px; font-weight: 600;
39
+ }
40
+
41
+ .app-container { display: flex; flex: 1; overflow: hidden; }
42
+
43
+ aside {
44
+ width: 280px; background: var(--bg-white); border-right: 1px solid var(--border);
45
+ display: flex; flex-direction: column;
46
+ }
47
+ .upload-zone {
48
+ margin: 15px; padding: 20px; border: 2px dashed var(--border); border-radius: 8px;
49
+ text-align: center; color: var(--text-muted); font-size: 13px; cursor: pointer; transition: 0.2s;
50
+ }
51
+ .upload-zone:hover { border-color: var(--primary); background: #F9FAFB; }
52
+
53
+ .file-list { flex: 1; overflow-y: auto; padding: 0 15px; }
54
+ .file-item {
55
+ display: flex; align-items: center; padding: 10px; border-radius: 6px; margin-bottom: 4px;
56
+ border: 1px solid transparent; cursor: pointer;
57
+ }
58
+ .file-item:hover { background: #F9FAFB; }
59
+ .file-icon { width: 32px; height: 32px; background: #EFF6FF; color: var(--primary); border-radius: 4px; display: flex; align-items: center; justify-content: center; margin-right: 10px; flex-shrink: 0; }
60
+ .file-info { flex: 1; min-width: 0; }
61
+ .file-name { font-size: 13px; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
62
+ .file-meta { display: flex; align-items: center; gap: 6px; margin-top: 2px; }
63
+ .status-badge { font-size: 10px; padding: 2px 6px; border-radius: 4px; font-weight: 600; }
64
+ .status-ready { background: #D1FAE5; color: #065F46; }
65
+ .status-indexing { background: #DBEAFE; color: #1E40AF; }
66
+ .status-error { background: #FEE2E2; color: #991B1B; }
67
+
68
+ .file-actions { opacity: 0; transition: 0.2s; }
69
+ .file-item:hover .file-actions { opacity: 1; }
70
+ .btn-icon { background: none; border: none; cursor: pointer; color: var(--text-muted); padding: 2px; }
71
+ .btn-icon:hover { color: var(--text-main); }
72
+
73
+ .sidebar-footer { padding: 15px; border-top: 1px solid var(--border); }
74
+ .toggle-row { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; font-size: 13px; }
75
+ .btn-glossary { width: 100%; padding: 8px; background: var(--bg-body); border: 1px solid var(--border); border-radius: 6px; text-align: left; font-size: 13px; display: flex; justify-content: space-between; cursor: pointer; }
76
+
77
+ main { flex: 1; display: flex; flex-direction: column; background: #fff; position: relative; }
78
+
79
+ .tabs { display: flex; border-bottom: 1px solid var(--border); background: var(--bg-white); padding: 0 20px; }
80
+ .tab-btn {
81
+ padding: 15px 20px; background: none; border: none; border-bottom: 2px solid transparent; font-size: 14px; font-weight: 500; color: var(--text-muted); cursor: pointer;
82
+ }
83
+ .tab-btn.active { color: var(--primary); border-bottom-color: var(--primary); }
84
+ .tab-btn:hover { color: var(--text-main); }
85
+
86
+ .tab-content { display: none; flex: 1; overflow: hidden; flex-direction: column; }
87
+ .tab-content.active { display: flex; }
88
+
89
+ .chat-area { flex: 1; padding: 20px; overflow-y: auto; background: #FAFAFA; display: flex; flex-direction: column; gap: 20px; }
90
+ .message { max-width: 80%; display: flex; flex-direction: column; gap: 6px; }
91
+ .message.user { align-self: flex-end; align-items: flex-end; }
92
+ .message.ai { align-self: flex-start; align-items: flex-start; }
93
+
94
+ .bubble {
95
+ padding: 12px 16px; border-radius: 12px; font-size: 14px; line-height: 1.5; box-shadow: 0 1px 2px rgba(0,0,0,0.05);
96
+ }
97
+ .user .bubble { background: var(--primary); color: white; border-bottom-right-radius: 2px; }
98
+ .ai .bubble { background: white; border: 1px solid var(--border); border-bottom-left-radius: 2px; }
99
+
100
+ .input-area {
101
+ padding: 20px; background: white; border-top: 1px solid var(--border);
102
+ }
103
+ .quick-actions { display: flex; gap: 8px; margin-bottom: 10px; overflow-x: auto; padding-bottom: 5px; }
104
+ .pill { padding: 4px 12px; background: #EFF6FF; color: var(--primary); border-radius: 99px; font-size: 12px; border: 1px solid #BFDBFE; cursor: pointer; white-space: nowrap; }
105
+ .pill:hover { background: #DBEAFE; }
106
+
107
+ .chat-input-wrapper { display: flex; gap: 10px; }
108
+ textarea { flex: 1; padding: 10px; border: 1px solid var(--border); border-radius: 8px; resize: none; height: 50px; font-family: inherit; }
109
+ .btn-primary { background: var(--primary); color: white; border: none; padding: 0 20px; border-radius: 8px; font-weight: 600; cursor: pointer; }
110
+ .btn-primary:hover { background: var(--primary-hover); }
111
+
112
+ .chat-controls { display: flex; justify-content: space-between; align-items: center; margin-top: 8px; }
113
+ .toggle-label { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--text-muted); cursor: pointer; }
114
+
115
+ .citation-group { margin-top: 8px; width: 100%; border-top: 1px solid #f0f0f0; padding-top: 8px; }
116
+ .citation-trigger { font-size: 11px; color: var(--text-muted); cursor: pointer; display: flex; align-items: center; gap: 4px; }
117
+ .citation-trigger:hover { text-decoration: underline; color: var(--primary); }
118
+
119
+ .citation-details { background: #F8FAFC; border: 1px solid #E2E8F0; padding: 8px; border-radius: 6px; font-size: 12px; margin-top: 4px; font-family: var(--font-mono); color: #334155; display: none; }
120
+ .citation-details.open { display: block; }
121
+ .source-tag { color: var(--primary); font-weight: 600; cursor: pointer; }
122
+ .source-tag:hover { text-decoration: underline; }
123
+
124
+ .action-buttons { display: flex; gap: 8px; margin-top: 8px; }
125
+ .btn-sm { font-size: 11px; padding: 4px 10px; border: 1px solid var(--border); background: white; border-radius: 4px; cursor: pointer; }
126
+ .btn-sm:hover { border-color: var(--primary); color: var(--primary); }
127
+
128
+ .notes-toolbar { padding: 15px 20px; border-bottom: 1px solid var(--border); display: flex; justify-content: space-between; align-items: center; background: #fff; }
129
+ .notes-content { flex: 1; padding: 40px; overflow-y: auto; max-width: 800px; margin: 0 auto; width: 100%; }
130
+ .notes-topic { font-size: 18px; font-weight: 700; margin-bottom: 15px; margin-top: 30px; color: var(--text-main); padding-bottom: 5px; border-bottom: 2px solid var(--primary); display: inline-block; }
131
+ .notes-bullet { margin-bottom: 8px; line-height: 1.6; font-size: 15px; display: flex; gap: 8px; }
132
+ .citation-ref { font-size: 10px; background: #E5E7EB; padding: 1px 4px; border-radius: 4px; color: var(--text-muted); margin-top: 3px; white-space: nowrap; }
133
+
134
+ .quiz-settings { padding: 20px; background: #F9FAFB; border-bottom: 1px solid var(--border); display: flex; gap: 20px; flex-wrap: wrap; align-items: center; }
135
+ .quiz-area { flex: 1; padding: 40px; overflow-y: auto; display: flex; justify-content: center; }
136
+ .quiz-card { width: 100%; max-width: 700px; background: white; border: 1px solid var(--border); border-radius: 12px; padding: 30px; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); }
137
+
138
+ .modal-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); display: none; justify-content: center; align-items: center; z-index: 100; }
139
+ .modal { background: white; width: 800px; height: 80vh; border-radius: 12px; display: flex; flex-direction: column; overflow: hidden; box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1); }
140
+ .modal-header { padding: 15px 20px; border-bottom: 1px solid var(--border); display: flex; justify-content: space-between; align-items: center; background: #F9FAFB; }
141
+ .modal-body { flex: 1; padding: 20px; overflow-y: auto; font-family: var(--font-mono); font-size: 13px; }
142
+
143
+ </style>
144
+ </head>
145
+ <body>
146
+
147
+ <header>
148
+ <div class="brand">Study RAG Assistant</div>
149
+ <div class="header-controls">
150
+ <div class="control-group">
151
+ <span>Model:</span>
152
+ <select><option>GPT-4o</option><option>Claude 3.5 Sonnet</option></select>
153
+ </div>
154
+ <div class="control-group">
155
+ <span>Chunk:</span>
156
+ <input type="number" value="512" style="width: 60px">
157
+ <span>Overlap:</span>
158
+ <input type="number" value="50" style="width: 50px">
159
+ </div>
160
+ <div class="stats-pill">4 Files β€’ 1,240 Chunks β€’ Indexed 2m ago</div>
161
+ </div>
162
+ </header>
163
+
164
+ <div class="app-container">
165
+ <aside>
166
+ <div class="upload-zone" onclick="alert('Mock Upload: Files added to queue.')">
167
+ Drag & Drop <br>.ipynb or .pdf
168
+ </div>
169
+
170
+ <div class="file-list">
171
+ <div class="file-item">
172
+ <div class="file-icon">PDF</div>
173
+ <div class="file-info">
174
+ <div class="file-name">Deep_Learning_Lec1.pdf</div>
175
+ <div class="file-meta">
176
+ <span class="status-badge status-ready">Ready</span>
177
+ </div>
178
+ </div>
179
+ <input type="checkbox" checked style="margin-left: 8px;">
180
+ </div>
181
+
182
+ <div class="file-item">
183
+ <div class="file-icon">NB</div>
184
+ <div class="file-info">
185
+ <div class="file-name">Neural_Nets_Practice.ipynb</div>
186
+ <div class="file-meta">
187
+ <span class="status-badge status-ready">Ready</span>
188
+ </div>
189
+ </div>
190
+ <input type="checkbox" checked style="margin-left: 8px;">
191
+ </div>
192
+
193
+ <div class="file-item">
194
+ <div class="file-icon">PDF</div>
195
+ <div class="file-info">
196
+ <div class="file-name">Stats_Review.pdf</div>
197
+ <div class="file-meta">
198
+ <span class="status-badge status-indexing">Indexing...</span>
199
+ </div>
200
+ </div>
201
+ <input type="checkbox" checked style="margin-left: 8px;">
202
+ </div>
203
+ </div>
204
+
205
+ <div class="sidebar-footer">
206
+ <div class="toggle-row">
207
+ <label>Show Selected Only</label>
208
+ <input type="checkbox">
209
+ </div>
210
+ <button class="btn-glossary" onclick="alert('Glossary Drawer would open here.')">
211
+ <span>πŸ“– Memory / Glossary</span>
212
+ <span>β€Ί</span>
213
+ </button>
214
+ </div>
215
+ </aside>
216
+
217
+ <main>
218
+ <div class="tabs">
219
+ <button class="tab-btn active" onclick="switchTab('chat', event)">Chat</button>
220
+ <button class="tab-btn" onclick="switchTab('notes', event)">Notes</button>
221
+ <button class="tab-btn" onclick="switchTab('quiz', event)">Quiz</button>
222
+ </div>
223
+
224
+ <div id="tab-chat" class="tab-content active">
225
+ <div class="chat-area" id="chat-container">
226
+ <div class="message ai">
227
+ <div class="bubble">
228
+ Hi! I've indexed your documents. Ask me to explain concepts, generate a quiz, or summarize topics.
229
+ </div>
230
+ </div>
231
+ </div>
232
+
233
+ <div class="input-area">
234
+ <div class="quick-actions">
235
+ <div class="pill" onclick="setInput('Explain backpropagation like I\\'m 5')">Explain Like I'm 5</div>
236
+ <div class="pill" onclick="setInput('Give me 3 code examples of RNNs')">Give Code Examples</div>
237
+ <div class="pill" onclick="setInput('What are the key differences between CNN and RNN?')">Compare Concepts</div>
238
+ </div>
239
+ <div class="chat-input-wrapper">
240
+ <textarea id="chat-input" placeholder="Ask a question about your files..."></textarea>
241
+ <button class="btn-primary" onclick="sendMessage()">Ask</button>
242
+ </div>
243
+ <div class="chat-controls">
244
+ <label class="toggle-label">
245
+ <input type="checkbox" checked> Strictly Grounded (No Guessing)
246
+ </label>
247
+ </div>
248
+ </div>
249
+ </div>
250
+
251
+ <div id="tab-notes" class="tab-content">
252
+ <div class="notes-toolbar">
253
+ <button class="btn-primary" onclick="alert('Mock: regenerate notes')">Regenerate Notes</button>
254
+ <div style="display:flex; gap:10px; align-items:center;">
255
+ <span>Format:</span>
256
+ <select><option>Bullet Notes</option><option>Cornell Notes</option><option>Outline</option></select>
257
+ <button class="btn-sm" onclick="alert('Mock export')">Export MD</button>
258
+ </div>
259
+ </div>
260
+ <div class="notes-content">
261
+ <div class="notes-topic">1. Introduction to Neural Networks</div>
262
+ <div class="notes-bullet"><span>β€’</span><div>Neural networks are computing systems inspired by biological neural networks.<div class="citation-ref">[Deep_Learning_Lec1.pdf - pg 2]</div></div></div>
263
+ <div class="notes-topic">2. Backpropagation</div>
264
+ <div class="notes-bullet"><span>β€’</span><div>Backpropagation computes gradients of the loss w.r.t. weights.<div class="citation-ref">[Deep_Learning_Lec1.pdf - pg 14]</div></div></div>
265
+ </div>
266
+ </div>
267
+
268
+ <div id="tab-quiz" class="tab-content">
269
+ <div class="quiz-settings">
270
+ <div class="control-group">
271
+ <label>Questions:</label>
272
+ <input type="range" min="5" max="50" value="10" oninput="this.nextElementSibling.innerText = this.value">
273
+ <span>10</span>
274
+ </div>
275
+ <div class="control-group">
276
+ <label>Type:</label>
277
+ <select><option>Multiple Choice</option><option>True/False</option><option>Code Reading</option></select>
278
+ </div>
279
+ <button class="btn-primary" style="margin-left:auto;" onclick="alert('Mock: generate quiz')">Generate Quiz</button>
280
+ </div>
281
+ <div class="quiz-area">
282
+ <div class="quiz-card">
283
+ <div class="q-text">Mock quiz question will show here.</div>
284
+ </div>
285
+ </div>
286
+ </div>
287
+ </main>
288
+ </div>
289
+
290
+ <script>
291
+ function switchTab(tabId, evt) {
292
+ document.querySelectorAll('.tab-content').forEach(el => el.classList.remove('active'));
293
+ document.querySelectorAll('.tab-btn').forEach(el => el.classList.remove('active'));
294
+ document.getElementById('tab-' + tabId).classList.add('active');
295
+ if (evt && evt.target) evt.target.classList.add('active');
296
+ }
297
+
298
+ function setInput(text) {
299
+ document.getElementById('chat-input').value = text;
300
+ }
301
+
302
+ function sendMessage() {
303
+ const input = document.getElementById('chat-input');
304
+ const text = input.value;
305
+ if (!text.trim()) return;
306
+
307
+ const container = document.getElementById('chat-container');
308
+
309
+ const userMsg = document.createElement('div');
310
+ userMsg.className = 'message user';
311
+ userMsg.innerHTML = `<div class="bubble">${text}</div>`;
312
+ container.appendChild(userMsg);
313
+
314
+ input.value = '';
315
+
316
+ setTimeout(() => {
317
+ const aiMsg = document.createElement('div');
318
+ aiMsg.className = 'message ai';
319
+ aiMsg.innerHTML = `<div class="bubble">Mock response (backend not connected yet).</div>`;
320
+ container.appendChild(aiMsg);
321
+ container.scrollTop = container.scrollHeight;
322
+ }, 400);
323
+ }
324
+ </script>
325
+
326
+ </body>
327
+ </html>
requirement.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ gradio==4.44.0
2
+ sentence-transformers==3.0.1
3
+ faiss-cpu==1.8.0.post1
4
+ pypdf==4.3.1
5
+ nbformat==5.10.4
6
+ transformers==4.44.2
7
+ torch==2.4.0
8
+ numpy==1.26.4