Spaces:
Runtime error
Runtime error
Upload 4 files
Browse files- README.md +14 -11
- app.py +259 -0
- index.html +327 -0
- 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|