open-notebook
Applicationlfnovo/open-notebook
Open-source, privacy-focused Notebook LM alternative with multi-model AI support.
Overview
An open-source, self-hosted alternative to Google Notebook LM that prioritizes privacy and flexibility. Supports 18+ AI providers, multi-modal content (PDFs, videos, audio, web), and customizable podcast generation.
README Preview
\n\n\n[![Forks][forks-shield]][forks-url]\n[![Stargazers][stars-shield]][stars-url]\n[![Issues][issues-shield]][issues-url]\n[![MIT License][license-shield]][license-url]\n\n\n\n\n\n\n \n \n \n\n Open Notebook\n\n \n An open source, privacy-focused alternative to Google's Notebook LM!\n Join our Discord server for help, to share workflow ideas, and suggest features!\n \n Checkout our website »\n \n \n 📚 Get Started\n ·\n 📖 User Guide\n ·\n ✨ Features\n ·\n 🚀 Deploy\n \n\n\n\n\n\n\n\n \n Deutsch | \n Español | \n français | \n 日本語 | \n 한국어 | \n Português | \n Русский | \n 中文\n\n\n## A private, multi-model, 100% local, full-featured alternative to Notebook LM\n\n\n\nIn a world dominated by Artificial Intelligence, having the ability to think 🧠 and acquire new knowledge 💡, is a skill that should not be a privilege for a few, nor restricted to a single provider.\n\n**Open Notebook empowers you to:**\n- 🔒 **Control your data** - Keep your research private and secure\n- 🤖 **Choose your AI models** - Support for 18+ providers including OpenAI, Anthropic, Ollama, LM Studio, and more\n- 📚 **Organize multi-modal content** - PDFs, videos, audio, web pages, and more\n- 🎙️ **Generate professional podcasts** - Advanced multi-speaker podcast generation\n- 🔍 **Search intelligently** - Full-text and vector search across all your content\n- 💬 **Chat with context** - AI conversations powered by your research\n- 🌐 **Multi-language UI** - English, Portuguese, Chinese (Simplified & Traditional), Japanese, Russian, and Bengali support\n\nLearn more about our project at [https://www.open-notebook.ai](https://www.open-notebook.ai)\n\n---\n\n## 🆚 Open Notebook vs Google Notebook LM\n\n| Feature | Open Notebook | Google Notebook LM | Advantage |\n|---------|---------------|--------------------|-----------|\n| **Privacy & Control** | Self-hosted, your data | Google cloud only | Complete data sovereignty |\n| **AI Provider Cho
FAQ (5)
TroubleshootingHow do I fix source chat streaming repeating all previous answers and the context badge disappearing after refresh?
Fix both issues in the source chat streaming and session-load paths.
1. Stop sending the full checkpointed message history during streaming. In api/routers/source_chat.py, around lines 376–384, do not iterate over every entry in result["messages"]. Emit only the latest AI message:
last_message = result["messages"][-1]
# Emit the ai_message event for last_message only
2. Restore context indicators when an existing session is loaded. In frontend/src/lib/hooks/use-source-chat.ts, update the session-load effect around lines 41–45:
setContextIndicators(currentSession.context_indicators ?? null);
The backend already returns context_indicators from GET session in api/routers/source_chat.py; the hook was only setting them from the SSE event. After these changes, subsequent streamed answers contain only the new answer, and the source count remains visible after a page refresh. Verified against main commit 3959eb0.
TroubleshootingWhy does a source appear embedded but vector search only returns partial document after a batched insert failure?
Batched repo_insert calls are not atomic: if a batch after the first one fails, earlier source_embedding rows remain. Because the embedded check only looks for one row (LIMIT 1), the source is incorrectly marked as fully embedded. Fix embed_source in commands/embedding_commands.py by wrapping the insert in try/except, deleting the source's embeddings on failure, and re-raising the original exception:
try:
await repo_insert(...)
except Exception:
await db.execute(
"DELETE FROM source_embedding WHERE source = $source_id",
{"source_id": source_id},
)
raise
Add a test that forces a later batch to fail, then assert that source_embedding contains zero rows for the source and the original exception is propagated unchanged.
TroubleshootingHow do I fix an empty notebook or transformation description not persisting after refresh?
The empty string is being removed before the PATCH request. In frontend/src/app/(dashboard)/notebooks/components/NotebookHeader.tsx, replace description: description || undefined with description: description. Apply the same change in frontend/src/app/(dashboard)/transformations/components/TransformationEditorDialog.tsx for the title and description fields. On the backend, update api/routers/notebooks.py and api/routers/transformations.py to distinguish an omitted field from a field explicitly set to ""; use Pydantic's model_fields_set or model_dump(exclude_unset=True) instead of checking only is not None. Verify with a PATCH containing description: "", which should clear the field, while omitting description should leave the existing value unchanged.
TroubleshootingHow do I fix blocked YouTube transcript requests on IP-flagged networks?
Use content-core 2.2 or later and configure YouTube cookies plus a residential proxy. Export YouTube cookies in Netscape cookies.txt format, mount the file into the container, and set:
-e CCORE_YOUTUBE_COOKIES_FILE=/run/secrets/cookies.txt \
-e CCORE_YOUTUBE_PROXY=http://user:password@residential-proxy:port \
-v /path/to/cookies.txt:/run/secrets/cookies.txt:ro
The proxy is used for transcript retrieval, the pytubefix fallback, and title lookup. Use a residential proxy; datacenter proxies are blocked. A missing or unreadable cookies file causes a ConfigurationError, so verify the mounted path and file permissions, then restart the worker/container.
TroubleshootingHow do I fix LLM calls timing out after 60 seconds in Open Notebook, especially with Ollama or reasoning models?
With esperanto 2.28+, to_langchain() enforces ESPERANTO_LLM_TIMEOUT for every provider. If it is unset, the effective fallback is 60 seconds, so long responses or slow local models can fail. Upgrade to the Open Notebook release that defaults this value to 180 seconds, or set it explicitly before starting the API and worker:
export ESPERANTO_LLM_TIMEOUT=180
For slow local models, use a larger value and increase API_CLIENT_TIMEOUT as well, keeping it above the LLM timeout plus a buffer:
export ESPERANTO_LLM_TIMEOUT=600
export API_CLIENT_TIMEOUT=720
In code, the default can be applied before any model is created with:
os.environ.setdefault("ESPERANTO_LLM_TIMEOUT", "180")
An explicitly set ESPERANTO_LLM_TIMEOUT always takes precedence. STT/TTS timeout variables are separate and remain at esperanto's 300-second default.