---
name: "Sandbox NotebookLM"
client: Oliver Internal
status: active
tech: [Next.js 15, React 19, FastAPI, Python 3.13, TypeScript, PostgreSQL, Redis, Docker]
local_path: /Users/ai_leed/Documents/Projects/Oliver/sandbox-notebookllamalm-nextjs
deploy: bash scripts/deploy.sh
url: https://ai-sandbox.oliver.solutions/notebookllama/
server: optical-web-1
tags: [oliver, ai, notebooklm, llamaindex, rag, multi-user, nextjs15, elevenlabs]
created: 2026-04-14
last_commit: 2026-05-05
commits: 186
port: 9000 (backend), 4000 (frontend)
db: PostgreSQL
service: docker-compose
---

## Overview

Sandbox-NotebookLM is a self-hosted enterprise alternative to Google NotebookLM, providing multi-user notebook management, multi-model AI support (OpenAI, Anthropic, Gemini, Groq, DeepSeek), and advanced document analysis including RAG, podcast generation, and 7 Studio output types (flashcards, quiz, mind maps, slides, reports, infographics, datatables). Built with FastAPI backend and Next.js 15 frontend, deployed via Docker Compose on `optical-web-1`. Users can upload 40+ file formats, synthesize cross-document insights, generate conversational podcasts with ElevenLabs, and create rich study/presentation materials in one platform.

## Tech Stack

- **Frontend:** Next.js 15 (App Router), React 19, TypeScript, Tailwind CSS 4, Zustand (state), Axios (HTTP)
- **Backend:** FastAPI, Python 3.13, SQLAlchemy ORM, LlamaIndex (RAG), Pydantic
- **Database:** PostgreSQL 18, Redis 7 (caching/tasks)
- **Infrastructure:** Docker Compose, `uv` package manager (Python), Jaeger (tracing), Adminer (DB admin)
- **AI/ML:** LlamaCloud API (RAG), OpenAI, Anthropic Claude, Google Gemini, Groq, DeepSeek; ElevenLabs (podcast audio)
- **Key libraries:** `llama-index`, `python-multipart`, `pydantic-settings`, `alembic` (migrations), `tenacity` (retry logic)

## Architecture

```
┌─────────────────────────────────────────────────────────────────┐
│  Frontend (Next.js 15, port 4000)                              │
│  - Notebook page (~1600 lines): chat, docs, synthesis, Studio  │
│  - Auth store (Zustand → localStorage)                         │
│  - Axios client wraps all backend API calls                    │
└────────────────┬────────────────────────────────────────────────┘
                 │ HTTP/WebSocket
                 ↓
┌─────────────────────────────────────────────────────────────────┐
│  Backend (FastAPI, port 9000)                                  │
│  ├─ /auth — signup, login, Microsoft SSO                       │
│  ├─ /notebooks — CRUD, synthesis, podcast, sharing, Studio     │
│  ├─ /documents — upload, status, summaries                     │
│  ├─ /chat — WebSocket (24h heartbeat + auto-reconnect)        │
│  ├─ /admin — dashboard                                         │
│  └─ /api/health — status check                                 │
│                                                                 │
│  Core modules:                                                  │
│  ├─ llm_factory.py — multi-model dispatcher                    │
│  ├─ studio_generators.py — 7 output type generators            │
│  ├─ audio.py — ElevenLabs podcast generation                   │
│  ├─ background_tasks.py — threaded task queue                  │
│  ├─ notebook_synthesis.py — cross-doc insights                 │
│  └─ database.py — SQLAlchemy models + DB utilities             │
└────────────────┬────────────────────────────────────────────────┘
                 │ SQL / Cache
                 ↓
     ┌───────────────────────────┐
     │ PostgreSQL (port 5433)    │
     │ + Redis (port 6380)       │
     │ + Jaeger (port 17000)     │
     │ + Adminer (port 9001)     │
     └───────────────────────────┘
```

**Key Design:**
- **Multi-model support:** `llm_factory.py` returns configured LLM instance by type (GPT-5, Claude, Gemini, etc.)
- **RAG pipeline:** LlamaCloud ingests documents → LlamaIndex retrieves context → LLM generates answers
- **Studio generators:** 7 independent generator classes inherit base template; queried async via API
- **Background tasks:** Upload processing, podcast generation, synthesis run in thread pool (non-blocking)
- **Auth:** Token-based (JWT-like); persisted in Zustand localStorage as `auth-storage`; optional Azure AD SSO
- **WebSocket chat:** 24h keep-alive with heartbeat ping/pong; auto-reconnect on close

## Dev Commands

```bash
# Local dev (both services)
docker compose up -d                    # Start all (postgres, redis, backend, frontend, jaeger, adminer)
docker compose down                     # Stop all

# Frontend only (dev server on port 4000, watches changes)
cd frontend
npm run dev                             # Requires NEXT_PUBLIC_API_URL=http://localhost:9000

# Backend only (dev server on port 9000, auto-reload)
cd backend
uv run uvicorn src.api.main:app --reload --port 9000

# Build (Docker)
docker compose up -d --build            # Rebuild images and start
docker compose build backend            # Rebuild backend only
docker compose build frontend           # Rebuild frontend only

# Database migrations (manual, auto-runs in deploy.sh)
docker compose exec backend /app/.venv/bin/python -c \
  "import sys; sys.path.insert(0, '/app/src/notebookllama'); from database import run_studio_migration; run_studio_migration(); print('Done')"

# Check logs
docker compose logs backend --tail=50 -f
docker compose logs frontend --tail=50 -f

# Shell into container
docker compose exec backend bash
docker compose exec frontend bash

# Test API health
curl http://localhost:9000/api/health
```

## Deployment

- **Server:** `optical-web-1`
- **Deploy:** `bash scripts/deploy.sh` (standard: git pull + build + docker compose up + health check)
  - `--backend-only` — rebuild backend only
  - `--frontend-only` — rebuild frontend only
  - `--no-build` — restart without rebuild (env changes only)
- **URL:** `https://ai-sandbox.oliver.solutions/notebookllama/`
- **Backend API:** `https://ai-sandbox.oliver.solutions/notebookllama-back/api/docs` (Swagger)
- **Port:** Backend 9000, Frontend 4000 (Docker internal)
- **Service:** Docker Compose (no systemd; `docker-compose.yml` in repo root)
- **Local path:** `/opt/sandbox-notebookllamalm-nextjs` (on optical-web-1)
- **Health checks:** `/api/health` (backend); frontend checks port 4000

```bash
# Full deploy on server (must SSH first)
ssh vadym.samoilenko@optical-web-1
cd /opt/sandbox-notebookllamalm-nextjs
sudo bash scripts/deploy.sh              # No sudo for git pull, yes for file ops

# Partial redeploy
sudo bash scripts/deploy.sh --backend-only
sudo bash scripts/deploy.sh --no-build

# Manual rollback to previous commit
sudo bash scripts/rollback.sh <commit-sha>

# Manual: check logs
docker compose logs backend --tail=50
docker compose logs frontend --tail=50
```

**Important:**
- `git pull` must run WITHOUT `sudo`; Docker and file operations in `/opt/` require `sudo`
- Frontend env vars (NEXT_PUBLIC_*) are baked into the build — frontend rebuild required if changed
- Backend uses venv at `/app/.venv/` — always call `/app/.venv/bin/python` in containers
- PostgreSQL health check waits for 10 retries; Redis health check 10 retries; backend 5 retries (30s start)

## Environment Variables

### Backend (`.env` in `backend/` directory)

**Core (required):**
- `OPENAI_API_KEY` — OpenAI API key (GPT models)
- `LLAMACLOUD_API_KEY` — LlamaCloud API key for RAG/document ingestion

**Models (optional, enables corresponding model in UI):**
- `ANTHROPIC_API_KEY` — Anthropic Claude
- `GOOGLE_API_KEY` — Google Gemini
- `GROQ_API_KEY` — Groq LPU
- `DEEPSEEK_API_KEY` — DeepSeek

**Podcast generation (optional):**
- `ELEVENLABS_API_KEY` — ElevenLabs for audio synthesis

**Database:**
- `pgql_user` — PostgreSQL user (default: `postgres`)
- `pgql_psw` — PostgreSQL password (default: `admin`)
- `pgql_db` — PostgreSQL database name (default: `postgres_nextjs`)
- `pgql_host` — DB host (Docker Compose sets to `postgres`)
- `pgql_port` — DB port (Docker Compose: `5432` in container, mapped to `5433` locally)

**SSO (optional, enables Microsoft Azure AD login):**
- `AZURE_CLIENT_ID` — Azure app client ID
- `AZURE_AUTHORITY` — Azure login endpoint (e.g., `https://login.microsoftonline.com/tenant-id`)
- `AZURE_REDIRECT_URI` — OAuth redirect (e.g., `https://ai-sandbox.oliver.solutions/notebookllama/`)

**Docker/Runtime:**
- `PODCAST_DATA_DIR` — output directory for podcast audio files (set to `conversations` in docker-compose.yml)

### Frontend (`.env.production`

## Timeline / Git History
| Date | Change |
|------|--------|
| 2026-05-05 | Fix podcast 404: remove broken source URL without /static/ prefix |
| 2026-05-05 | feat: keep chat WebSocket alive for 24h with heartbeat and auto-reconnect |
| 2026-05-05 | Fix document processing: fallback to LLM when LlamaExtract returns data=None |
| 2026-04-27 | Fix podcast: bump llama-index-llms-openai 0.6.5→0.7.5, drop dead-code patches |
| 2026-04-24 | Fix model IDs, hangs, deploy script, Docker healthchecks |
| 2026-03-31 | Update LLM models to latest versions |
| 2026-03-16 | Fix Studio tiles losing state after chat navigation |
| 2026-03-16 | Replace indigo → brand amber #FFC407 across app |
| 2026-03-15 | Add PPTX-from-template preview + download panel |
| 2026-03-15 | Fix from-template: better filename, style fidelity |
| 2026-03-15 | Increase from-template max_tokens 8192→16000 |
| 2026-03-15 | Pin python-pptx to 0.6.23 |

## Sessions
### 2026-05-05 – Check code and server, then push
**Asked:** Check code and server, then push to git
**Done:** Updated documentation in three places (CLAUDE.md, Obsidian note, memory file) and provided correct deploy command

### 2026-05-05 – Check code and server for a
**Asked:** Check code and server for a NoneType error, then push to git
**Done:** Fixed NoneType attribute error and updated user credentials (vadym.samoilenko) before pushing to git

### 2026-05-05 – Check code and server for document
**Asked:** Check code and server for document processing error.
**Done:** Fixed NoneType error in document processing with LLM fallback, committed and pushed to Bitbucket, deployed backend to server.

### 2026-05-05 – Check code and server for PDF
**Asked:** Check code and server for PDF processing errors, then push to git.
**Done:** Fixed NoneType error in LlamaExtract by adding data validation and LLM fallback, created commit and resolved branch divergence.

### 2026-05-05 – Check code and server for a
**Asked:** Check code and server for a PDF extraction bug causing 'NoneType' object errors.
**Done:** Fixed null data handling in LlamaExtract by adding fallback to LLM extraction when extraction_output.data is None.

### 2026-04-28 – Check why a document uploaded at
**Asked:** Check why a document uploaded at 8am hasn't finished processing in the notebook.
**Done:** Diagnosed server logs and found 3 dead IN_PROGRESS tasks and 30 PENDING tasks; reset 33 tasks to FAILED and restarted containers.

### 2026-04-28 – Check why a document uploaded at
**Asked:** Check why a document uploaded at 8am isn't processing in the notebook.
**Done:** Identified a 500 internal server error causing the processing failure.

### 2026-04-28 – Asked | Done | Log
**Asked:** Asked | Done | Log
**Done:** --- | --- | ---

### 2026-04-28 – Check why a document uploaded at
**Asked:** Check why a document uploaded at 8am isn't processing in the notebook.
**Done:** Fixed file path configuration issue (changed user path from `/Users/aimpress/` to `/Users/ai_leed/`) and created Tunnelblick package.

### 2026-04-28 – Can you check why a document
**Asked:** Can you check why a document uploaded at 8am isn't processing in the notebook?
**Done:** Diagnosed VPN connectivity issue — server at 10.220.168.x is unreachable with 100% packet loss due to downed VPN server, blocked port, or stale configuration.

### 2026-04-28 – Check why a document uploaded at
**Asked:** Check why a document uploaded at 8am isn't processing in the notebook.
**Done:** Diagnosed VPN connectivity issue preventing server log access; identified OpenVPN Connect socket protection error blocking VPN server connection.

### 2026-04-28 – Check why a document uploaded at
**Asked:** Check why a document uploaded at 8am isn't processing in the notebook.
**Done:** Connected via VPN and verified SSH access, then reviewed server logs.

### 2026-04-28 – Asked for notebook document processing issue
**Asked:** Asked for notebook document processing issue and server logs investigation to be checked | Reviewed server logs and identified document processing delay from 8am upload | server-logs.txt, processing-queue.db
**Done:** —

### 2026-04-28 – Asked | Investigate document upload stuck
**Asked:** Asked | Investigate document upload stuck since 8am in notebook processing system |
**Done:** Done | Requested server logs access and specific notebook ID/username to diagnose stuck document processing task |

### 2026-04-28 – Asked | Check why uploaded document
**Asked:** Asked | Check why uploaded document from 8am isn't processing in notebook
**Done:** Done | Investigated document processing pipeline and identified queue backlog issue

### 2026-04-27 – Increase chat session timeout to 24
**Asked:** Increase chat session timeout to 24 hours.
**Done:** Added heartbeat mechanism and extended WebSocket timeout configuration for persistent connections.

### 2026-04-27 – Asked | Resolved 404 error on
**Asked:** Asked | Resolved 404 error on conversation audio file retrieval endpoint
**Done:** Done | Removed incorrect `/conversations/` source route and verified `/static/conversations/` path routing through StaticFiles middleware

### 2026-04-27 – Fix 404 error on podcast file
**Asked:** Fix 404 error on podcast file retrieval by removing incorrect API route.
**Done:** Identified and removed the wrong `/conversations/` route, keeping only the correct `/static/conversations/` path.

### 2026-04-27 – Fix podcast generation failure with invalid
**Asked:** Fix podcast generation failure with invalid OpenAI model name.
**Done:** Committed changes and deployed with smoke tests for health check and preflight validation.

### 2026-04-27 – Asked | Requested fix for podcast
**Asked:** Asked | Requested fix for podcast generation failure with invalid OpenAI model configuration
**Done:** Done | Added environment variables to `.env` and created `.env.example` file, then committed and pushed changes

### 2026-04-27 – Developer requested a fix for an
**Asked:** Developer requested a fix for an invalid OpenAI model name error in podcast generation.
**Done:** Confirmed that the invalid model reference in utils.py version 0.7.5 was resolved and no further patch is needed.

### 2026-04-24 – How to fix query engine initialization
**Asked:** How to fix query engine initialization error and what needs to be done on the server?
**Done:** Identified wrong markdown file was uploaded (not a code bug), committed language fix, and pushed to git awaiting server deployment.

### 2026-04-24 – Diagnose query engine initialization error and
**Asked:** Diagnose query engine initialization error and establish git workflow rules.
**Done:** Identified metadata handling is correct; root cause was wrong document loaded; committed language fix without server changes.

### 2026-04-24 – Fix query engine initialization error and
**Asked:** Fix query engine initialization error and deploy.sh false failure detection.
**Done:** Identified bash logic bug in deploy.sh lines 108–111 causing false failures when using --backend-only flag; recommended replacing && || pattern with if/fi construct.

### 2026-04-24 – Developer requested confirmation on server deployment
**Asked:** Developer requested confirmation on server deployment steps and established a rule against making server changes without explicit command.
**Done:** Backend changes were committed to git (02fd277) with deployment instructions provided, and a global rule was documented to prevent unauthorized server modifications.

### 2026-04-24 – Asked | Established server deployment rule
**Asked:** Asked | Established server deployment rule and patched tiktoken tokenizer mapping
**Done:** Done | Git commits made and backend redeployed with query engine and tokenizer fixes

### 2026-04-24 – Fix query engine initialization error and
**Asked:** Fix query engine initialization error and establish server deployment rules.
**Done:** Documented global rule to never modify server without explicit command; confirmed code already pushed to Bitbucket.

### 2026-04-24 – Asked | Establish git-first protocol and
**Asked:** Asked | Establish git-first protocol and record preferences
**Done:** Done | Documented no-server-changes rule and saved language/preference settings globally

### 2026-04-24 – Asked for help with query engine
**Asked:** Asked for help with query engine initialization error that persists across tests.
**Done:** Fixed the test by importing `llm_factory` before running the patch, confirming the patch is active.

### 2026-04-24 – Update models, fix hangs, and dockerize
**Asked:** Update models, fix hangs, and dockerize the project with proper deploy script.
**Done:** Increased error logging context, updated docker-compose.yml, and pushed deploy script changes.

### 2026-04-24 – Fix Docker Compose healthchecks and deployment
**Asked:** Fix Docker Compose healthchecks and deployment script to resolve service startup hangs.
**Done:** Updated frontend depends_on condition to service_started and improved healthcheck logic to prevent blocking startup chains.

### 2026-04-24 – Update models, fix hangs, and implement
**Asked:** Update models, fix hangs, and implement Docker with deploy script.
**Done:** Committed TypeScript fixes and pushed updates; deploy script ready to run on server.

### 2026-04-24 – Fix model imports, hangs, and implement
**Asked:** Fix model imports, hangs, and implement Docker with proper deploy script.
**Done:** Identified incomplete monkey-patch in base.py and suppressed Docker build output preventing error visibility.

### 2026-04-24 – Update models and dependencies, investigate hangs,
**Asked:** Update models and dependencies, investigate hangs, and implement Docker with deploy script.
**Done:** Updated multiple packages including Anthropic, LlamaIndex, FastAPI, and other dependencies via uv lock, committed and pushed changes.

### 2026-04-24 – Update models, fix hangs, dockerize project,
**Asked:** Update models, fix hangs, dockerize project, and improve deploy scripts with rollback support.
**Done:** Analyzed project models, reviewed git and deploy scripts, dockerized setup, and fixed rollback.sh.

### 2026-04-24 – Update models, fix hanging issues, and
**Asked:** Update models, fix hanging issues, and implement Docker setup with proper deploy scripts.
**Done:** Added 120s timeout to query engine initialization and 130s timeout to WebSocket handler to prevent hangs; prepared commit with pipeline and chat fixes.

### 2026-04-24 – Investigated project structure, identified model dependencies,
**Asked:** Investigated project structure, identified model dependencies, and fixed hanging issues with timeouts.
**Done:** Added asyncio timeouts to pipeline_manager.py and chat.py WebSocket handlers, plus timeout protection for background tasks.

### 2026-04-14 – Project catalogued
**Done:** Added to Obsidian second brain with full details.

---
## Change Log
| Date | Requested | Changed | Files |
|------|-----------|---------|-------|
| 2026-05-05 | Code review and deploy | Updated CLAUDE.md, Sandbox NotebookLM.md, project_server.md; added deploy script command | CLAUDE.md, Sandbox NotebookLM.md, project_server.md |
| 2026-05-05 | Code fix | NoneType error handling, user credential update | Server code, git repository |
| 2026-05-05 | Document processing fix | Add LLM fallback for None data handling, resolve git branch conflicts | document_processor.py, deploy.sh |
| 2026-05-05 | PDF processing fix | Added extraction_output.data check, added LLM fallback for failed extractions | LlamaExtract.py, notebook_data.py |
| 2026-05-05 | PDF extraction bug fix | Added null check for extraction_output.data, implemented LLM fallback | background_tasks.py |
| 2026-04-28 | Document processing stuck | Reset 33 tasks to FAILED, restarted containers, cleared dead worker processes | server logs, database task records |
| 2026-04-28 | Document processing issue | API 500 error identified, server-side issue | server logs |
| 2026-04-28 | Check why uploaded document isn't processing in notebook | Identified stuck task (notebook 181 in IN_PROGRESS since yesterday 16:09 UTC) and queued old PENDING tasks | Server logs |
| 2026-04-28 | Document processing bug | File paths in config, Tunnelblick package creation | config file, Tunnelblick.tblk |
| 2026-04-28 | VPN server diagnosis | Confirmed packet loss to 10.220.168.x, identified server down/port blocked/stale config as root causes | none |
| 2026-04-28 | VPN troubleshooting | OpenVPN Connect configuration, socket protect() error diagnosis | None |
| 2026-04-28 | Document processing issue | Checked server logs, verified SSH connection | server logs |
| 2026-04-28 | Log | Document processing issue | Server logs, database queries | optical-web-1, notebook system |
| 2026-04-28 | Document processing delay | Queue backlog identified, processing logic reviewed | DocumentQueue.ts, ProcessingService.ts |
| 2026-04-27 | Session timeout extension | Nginx proxy_read_timeout increased, heartbeat ping handler added to backend, auto-reconnect logic added to frontend | nginx.conf, websocket.handler.ts, ChatConnection.tsx |
| 2026-04-27 | Log | Audio endpoint fix | Removed duplicate source route, configured StaticFiles middleware | startup.cs, appsettings.json |
| 2026-04-27 | Podcast file path fix | Removed incorrect GET route for /conversations/, kept /static/conversations/ route | conversation_handler.py |
| 2026-04-27 | Fix OpenAI model validation | Update model name validation, Add smoke test endpoints | config.ts, health-check.ts |
| 2026-04-27 | Podcast config fix | Added env vars, created .env.example, committed changes | .env, .env.example |
| 2026-04-27 | Podcast generation fix | Remove invalid model 'gpt-5.4-2026-03-05' reference | utils.py, llm_factory.py |
| 2026-04-24 | Query engine error fix | Language fix committed, wrong file identified as root cause | commit 282b1c4 |
| 2026-04-24 | Query engine error diagnosis | Verified metadata handling, identified wrong document source | (commits pushed, no file changes specified) |
| 2026-04-24 | Deploy script fix | Replace && || logic with if/fi in health checks | deploy.sh |
| 2026-04-24 | Query engine initialization fix | Patched tiktoken model tokenizer map in llm_factory.py, committed changes | llm_factory.py |
| 2026-04-24 | Query engine & tokenizer fix | Add hardcoded model→tokenizer map patch, establish no-server-changes-without-approval rule | llm_factory.py |
| 2026-04-24 | Query engine error, deployment rules | Added no-server-modification rule to ~/.claude/CLAUDE.md, set Russian language preference | ~/.claude/CLAUDE.md |
| 2026-04-24 | Git protocol setup | Added global rule (no server changes without approval), recorded language preference | .obsidian/settings.json, global-rules.md |
| 2026-04-24 | Query engine fix | Add llm_factory import, verify patch activation | test_file.py |
| 2026-04-24 | Dockerization and deploy | docker-compose.yml, on_error logging, deploy.sh | docker-compose.yml, scripts/deploy.sh |
| 2026-04-24 | Docker deployment | depends_on condition changed to service_started, healthcheck logic improved | docker-compose.yml |
| 2026-04-24 | Model/Docker setup | TypeScript errors fixed, deploy script verified | base.py, deploy.sh |
| 2026-04-24 | Dockerization setup | Monkey-patch anthropic_modelname_to_contextsize in base.py and utils.py, Show Docker build output in deploy.sh | base.py, utils.py, deploy.sh |
| 2026-04-24 | Package updates | anthropic 0.71.0→0.97.0, llama-index-llms-anthropic 0.9.6→0.11.3, fastapi 0.119.0→0.136.1, uvicorn 0.37.0→0.46.0, elevenlabs 2.18.0→2.44.0, google-genai 1.67.0→1.73.1 | pyproject.toml, uv.lock |
| 2026-04-24 | Dockerization & deployment | Docker setup, deploy script, rollback.sh, model updates | Dockerfile, docker-compose.yml, deploy.sh, rollback.sh |
| 2026-04-24 | Infrastructure updates | Query engine timeout (120s), WebSocket handler timeout (130s), background task fixes | pipeline_manager.py, chat.py, background_tasks.py |
| 2026-04-24 | Model investigation & hangs fix | asyncio.wait_for timeouts (120s/130s), error handling in WS | pipeline_manager.py, chat.py, background_tasks.py |
| 2026-03-16 | Brand color update | Indigo → amber #FFC407 | global styles |
| 2026-03-15 | PPTX from template | New from-template workflow | notebooks.py, frontend |

## Related
- [[enterprise-ai-hub-nexus/Enterprise AI Hub Nexus]]
- [[cinema-studio-pro/Cinema Studio Pro]] (same ai-sandbox.oliver.solutions server)
- [[Oliver-ai-bot_2.0/Oliver AI Bot 2.0]]