A modern web application for managing, querying, and analyzing research papers. Features a clean NotebookLM-inspired UI with notebook management, paper organization, and AI-powered chat backed by a full RAG pipeline.
Frontend: β Fully Functional | Backend: β Functional (Agentic RAG Pipeline + Paper to Code Active)
The frontend is complete with a production-ready UI. The backend runs a full agentic vision RAG pipeline: upload a PDF β pages are extracted by a VLM (metadata including a paper description is stored in memory_store.json, text chunks go to Qdrant) β at query time a planner LLM decides which actions to run (read_metadata, retrieve with optional per-paper scoping) β retrieved results are reranked by a cross-encoder β the top result's surrounding pages per paper are passed as images to a VLM for answering. The Paper to Code Lab feature is also fully implemented β a 3-stage LLM pipeline generates a runnable code repository from any uploaded paper, downloadable as a ZIP.
- Multiple Notebooks: Create, rename, delete notebooks (ChatGPT-style sidebar)
- Isolated Data: Each notebook has its own sources, chat history, and notes
- Smart Navigation: Toggle between "Your Notebooks" and "Sources" views
- PDF Upload: Drag-and-drop upload with real backend processing
- Paper Actions: Rename and delete papers via 3-dot menu
- Upload Feedback: Shows chunks indexed after successful upload
- Agentic Planner: A small LLM decides which actions to run before answering β
read_metadata(fetch stored paper info) and/orretrieve(vector search, optionally scoped to a specific paper by ID) - Multi-Paper Awareness: For comparison questions, the planner issues one
retrieveaction per paper; Qdrant search and reranking run independently per paper so results from all targeted papers are included - VLM-Powered Q&A: Final answer generated by a vision model reading page images
- Hybrid Answers: When both metadata and retrieved content are needed, the metadata block is prepended to the question so the model cites both
(metadata)and(Page X)sources - Semantic Search: OpenRouter embeddings (
openai/text-embedding-3-small, 4096-dim, via API) + Qdrant vector search with optional per-paperpaper_idfilter - Cross-Encoder Reranking: Retrieved chunks reranked by BAAI/bge-reranker-base before answer generation
- 3-Page Context Window: VLM receives pages N-1, N, N+1 around the best match per paper
- Duplicate Upload Guard: Re-uploading the same filename returns HTTP 409
- Markdown Support: Rich text rendering with
marked.js - Citation Badges: Clickable [1], [2] badges linked to source pages
- Message History: Persistent per-notebook chat history
- Paper to Code β β 3-stage LLM pipeline (Planning β Analyzing β Coding) generates a runnable code repository from a paper; progress bar during generation; download result as ZIP; cancel support
- Paper to Poster, Paper to Web β UI complete, generation logic TBD
ββ INGESTION (Upload) ββββββββββββββββββββββββββββββββββββββββββββββββββ
PDF
ββ PyMuPDF β page PNGs saved to disk
For each page (one at a time via VLM):
VLM (RAG_VISION_MODEL)
β page image
β plain text (tables described in prose)
β metadata on page 0: title, authors, year, venue, abstract,
keywords, description (2-3 sentence summary)
stored in memory_store.json (not Qdrant)
RecursiveCharacterTextSplitter (chunk_size=500, overlap=75)
β N chunks
OpenRouter Embeddings API (openai/text-embedding-3-small, 4096-dim)
β dense vector per chunk
Qdrant (local on-disk)
β upsert {type, paper_id, page_num, content, page_text, vector}
ββ RETRIEVAL (Chat) βββββββββββββββββββββββββββββββββββββββββββββββββββββ
Question
ββ Planner LLM (RAG_PLANNER_MODEL)
β list of actions: read_metadata / retrieve (with paper_id scope)
(actions run in parallel via asyncio.gather)
read_metadata action:
ββ fetch paper(s) from memory_store.json
β title, authors, year, abstract, description, keywords
retrieve action (one per targeted paper for multi-doc queries):
ββ OpenRouter Embeddings API β 4096-dim query vector
ββ Qdrant cosine search (optionally filtered by paper_id) β top-50
ββ Cross-encoder reranker (BAAI/bge-reranker-base, ONNX) β top-5
ββ best result at page N per paper
β load images: page N-1, page N, page N+1 from disk
VLM (RAG_ANSWER_MODEL)
β images from all targeted papers + question (+ metadata block if read_metadata ran)
β answer citing (Page X) for image facts, (metadata) for bibliographic facts
| Decision | Reason |
|---|---|
| Agentic planner instead of classifier router | Explicit paper_id-scoped actions enable correct multi-doc retrieval; old router couldn't target specific papers |
Metadata stored in memory_store.json, not Qdrant |
Metadata is structured (title/authors/year) and fetched wholesale β doesn't benefit from vector search |
Paper description extracted on upload |
Lets the planner identify which papers to retrieve for a given query without reading all abstracts |
| Per-paper independent reranking | Pooling results from all papers before reranking would consistently suppress lower-scored papers |
| Best result per paper for image selection | Multi-doc comparison queries need visual evidence from each paper, not just the globally highest-scored one |
| VLM reads images for answering | Avoids lossy text extraction for final answer; model sees original layout, tables, and figures |
| Tables described in prose during extraction | Avoids Markdown table embedding issues; prose embeds better |
| 3-page window (N-1, N, N+1) | Catches content that spans a page boundary |
| OpenRouter embeddings instead of local fastembed | No local model to load; consistent with all other API calls; higher-dim vectors (4096) capture richer semantics |
| fastembed local embeddings removed | Replaced by OpenRouter API embeddings |
| Qdrant local on-disk | No Docker needed; resets cleanly on re-upload |
- Vue 3 (Composition API with
<script setup>) - Vite Β· Pinia Β· Vue Router Β· Tailwind CSS v3
- Lucide Vue Next Β· Marked.js
- FastAPI + Uvicorn (ASGI)
- Pydantic / pydantic-settings
- PyMuPDF β PDF β page images
- fastembed TextCrossEncoder β reranking only (BAAI/bge-reranker-base, ONNX)
- Qdrant Client β local on-disk vector store (4096-dim)
- OpenAI SDK β OpenRouter-compatible client (chat, vision, embeddings)
- LangChain Text Splitters β RecursiveCharacterTextSplitter
- aiofiles Β· python-multipart
| Role | Default Model | Config Key |
|---|---|---|
| Page extraction (VLM) | google/gemini-flash-1.5 |
RAG_VISION_MODEL |
| Answer generation (VLM) | google/gemini-flash-1.5 |
RAG_ANSWER_MODEL |
| Planner + metadata answers (text-only) | openai/gpt-4o-mini |
RAG_PLANNER_MODEL |
| Text embeddings | openai/text-embedding-3-small |
RAG_EMBEDDING_MODEL |
| Paper to Code generation | anthropic/claude-3.5-sonnet |
PAPER2CODE_CODE_MODEL |
VibeProject/
βββ frontend/
β βββ src/
β βββ views/Home.vue # Main UI (3-column layout)
β βββ stores/app.js # Pinia store + API calls
β βββ router/index.js
βββ backend/
β βββ app/
β β βββ main.py # FastAPI app, CORS, logging
β β βββ config.py # Settings (env vars + defaults)
β β βββ routers/
β β β βββ papers.py # Upload, list, delete, /chunks debug
β β β βββ chat.py # RAG chat endpoint
β β β βββ generate.py # Paper to Code: start/status/cancel/download
β β βββ services/
β β βββ openrouter_service.py # VLM extraction, planner, answer generation
β β βββ paper2code_service.py # 3-stage Paper2Code pipeline
β β βββ embedding_service.py # OpenRouter embeddings API (async)
β β βββ reranker_service.py # Cross-encoder reranking (bge-reranker-base)
β β βββ qdrant_service.py # Qdrant local client + search (paper_id filter)
β β βββ memory_store.py # JSON persistence for paper metadata
β β βββ pdf_service.py # PDF β PIL page images
β βββ requirements.txt
β βββ .env # API keys (gitignored)
β βββ .env.example # Template for .env
βββ paper2code_outputs/ # Generated repos + ZIPs (outside backend/ to avoid reload)
βββ README.md
- Node.js 20+
- Python 3.11+
- OpenRouter API key β get one at https://openrouter.ai/keys
cd backend
# Create and activate virtual environment
python -m venv venv
venv\Scripts\activate # Windows
# source venv/bin/activate # macOS/Linux
# Install dependencies
pip install -r requirements.txt
# Configure environment
copy .env.example .env
# Edit .env and set OPENROUTER_API_KEY=your_key_here
# Start server
uvicorn app.main:app --reload
# API available at http://localhost:8000
# Docs at http://localhost:8000/docscd frontend
npm install
npm run dev
# App at http://localhost:5173OPENROUTER_API_KEY=sk-or-...
RAG_VISION_MODEL=google/gemini-flash-1.5 # for page extraction
RAG_ANSWER_MODEL=google/gemini-flash-1.5 # for VLM answer generation
RAG_PLANNER_MODEL=openai/gpt-4o-mini # for planner + metadata answers
RAG_EMBEDDING_MODEL=openai/text-embedding-3-small # for text embeddings (4096-dim)
PAPER2CODE_CODE_MODEL=anthropic/claude-3.5-sonnet # for Paper to Code generation| Method | Endpoint | Description |
|---|---|---|
GET |
/api/v1/health |
Health check |
GET |
/api/v1/notebooks/{id}/papers |
List papers in notebook |
POST |
/api/v1/notebooks/{id}/papers/upload |
Upload PDF (triggers ingestion) |
DELETE |
/api/v1/notebooks/{id}/papers/{pid} |
Delete paper + Qdrant points |
POST |
/api/v1/notebooks/{id}/chat |
Ask a question (RAG) |
GET |
/api/v1/notebooks/{id}/chunks |
Debug: browse indexed chunks |
POST |
/api/v1/notebooks/{id}/papers/{pid}/generate/code |
Start Paper to Code job β returns job_id |
GET |
/api/v1/generate/code/{job_id}/status |
Poll job progress (running/done/error/cancelled) |
POST |
/api/v1/generate/code/{job_id}/cancel |
Cancel a running job |
GET |
/api/v1/generate/code/{job_id}/download |
Download generated repo as ZIP |
- In-memory metadata: Notebooks and paper metadata reset on server restart (no database)
- No auth: Notebook IDs passed directly in URL
- Qdrant storage reset required: If
RAG_EMBEDDING_MODELis changed, deleteqdrant_storage/and re-upload all papers (vector dimensions must match)
Browse stored chunks at:
GET /api/v1/notebooks/{id}/chunks?type=text&limit=20
Per-request debug logs show the planner actions and which pages are sent to the answer VLM:
DEBUG app.routers.chat: Actions planned: [{"action": "retrieve", "paper_id": "abc...", "query": "..."}, ...]
DEBUG app.services.openrouter_service: images sent: ['327dcba8/page_1.png', '326633b7/page_2.png', ...]
- Naming: camelCase for JS/Vue state, snake_case for Python
- Icons: Lucide Vue Next throughout
- Event handling:
@click.stopto prevent bubbling on 3-dot menus - Async: All OpenRouter calls are
async/await; page processing usesasyncio.gather
Last Updated: March 2026
Status: Frontend complete Β· Agentic RAG pipeline active Β· Reranking active Β· Multi-doc comparison active Β· Paper to Code active