Electron desktop app for generating PowerPoint decks from chat, files, and URLs with support for GitHub Copilot, OpenAI, Azure OpenAI, and Claude.
This app aims to create a local, NotebookLM-style workflow for PPTX generation. It ingests source materials, grounds the content in them, and produces presentation-ready slides complete with layout, text, images, icons, and charts. Unlike NotebookLM, which generates AI images into slides, this app produces fully editable slides grounded in user-provided sources.
graph LR
classDef context fill:#E6E6FA,stroke:#9370DB,color:#000;
classDef llmLogic fill:#FFFACD,stroke:#DAA520,color:#000;
classDef agentCore fill:#89CFF0,stroke:#4682B4,stroke-width:2px,color:#000,font-weight:bold;
classDef layout fill:#98FF98,stroke:#3CB371,color:#000;
classDef render fill:#FFDAB9,stroke:#CD5C5C,color:#000;
classDef final fill:#FFC0CB,stroke:#DB7093,stroke-width:2px,color:#000;
subgraph Inputs ["Source Ingestion"]
direction TB
A[Raw Documents]:::context
B[NotebookLM - Notes and Images]:::context
end
C[RAPTOR Engine - Tree RAG]:::llmLogic
D{{Agent Workflow - Slide Planner}}:::agentCore
E[Kiwi Solver - Layout Engine]:::layout
F[PPTX Renderer - python-pptx or COM]:::render
G[Final PPTX]:::final
A --> C
C --> D
B -. optional .-> D
D -->|Slide JSON| E
E -->|Layout Geometry| F
F --> G
linkStyle 2 stroke:#9370DB,stroke-dasharray:5 5;
linkStyle 3 stroke:#4682B4,stroke-width:2px;
The app uses constraint-based layout computation (via the Kiwi solver π¦, an implementation of the Cassowary algorithm; Matplotlib uses kiwisolver internally in a limited subset of layout calculations). and π¦ RAPTOR-style hierarchical retrieval and summarization to structure the final output. RAPTOR fits this app well because PPTX content is naturally hierarchical: a top-down story breaks into sections, slides, and supporting details, so tree-based retrieval keeps the narrative coherent while preserving local context.
- Generation Speed: 100 slides in under 2 minutes
- Long-Context Support: 779-page PDF condensed into a 10β100 slide PowerPoint presentation.
Requirements:
- Node.js with
pnpm uvand Python 3.13+- credentials for at least one supported model provider
- Microsoft PowerPoint on Windows (Optional) β required for local preview images only; layout measurement uses Pillow font metrics. See the details
Install dependencies:
pnpm installSet up the Python environment once:
uv syncBefore running the app, decide which provider you want to use in Settings:
- GitHub Copilot with GitHub-hosted models
- GitHub Copilot with your own Azure OpenAI or Foundry deployment
- OpenAI
- Azure OpenAI
- Claude
Recommended option for most users: GitHub Copilot with GitHub-hosted models. It has the simplest setup in this app and is the most tested path.
Run the development server:
pnpm devBuild:
pnpm distIf .venv already exists and you only want to package:
pnpm dist:skip-venvGitHub PAT permissions:
- Classic PAT β no specific scope needed; the account must have an active Copilot subscription.
- Fine-grained PAT β Under "Permissions," click Add permissions and select Copilot Requests.
Choose the provider in Settings first, then enter only the matching fields:
GitHub Copilot+GitHub-hosted models:GITHUB_TOKEN,MODEL_NAMEGitHub Copilot+Self-serving Azure OpenAI / Foundry:GITHUB_TOKEN,COPILOT_MODEL_SOURCE,MODEL_NAME, Azure connection detailsAzure OpenAI:MODEL_NAME, Azure connection detailsOpenAI:MODEL_NAME,OPENAI_API_KEYClaude:MODEL_NAME,ANTHROPIC_API_KEY
REASONING_EFFORT is optional for all providers.
SHOW_TOOL_CALLING_MESSAGES is optional and controls whether chat shows tool execution entries such as tweak_slide, patch_layout_infrastructure, and rerun_pptx.
Notes:
- For GitHub Copilot hosted models, use a token with Copilot entitlement.
- For Copilot with self-serving Azure, set
LLM_PROVIDER=copilotandCOPILOT_MODEL_SOURCE=azure-openai, then provide your Azure endpoint and authentication details in Settings. - For Azure, use the full base URL including
/openai/v1. MODEL_NAMEcan be a GitHub-hosted model name, an Azure deployment name, or another provider-specific model identifier.
Datasource ingestion and PPTX generation use a local uv-managed Python environment so MarkItDown and python-pptx stay isolated from the system Python installation.
This creates .venv and installs the dependencies declared in pyproject.toml. The Electron app automatically prefers that interpreter for both content ingestion and PPTX generation.
For packaged builds, .venv is bundled into the app's resources directory. pnpm dist:skip-venv only skips recreating the environment; it still requires an existing .venv in the repo root.
https://python-pptx.readthedocs.io/
PPTX generation runs through the bundled Python runner at scripts/pptx-python-runner.py, which loads the storyboard, theme, and precomputed layout artifacts, then invokes the deterministic renderer in scripts/slide_renderer.py with runtime variables including OUTPUT_PATH, PPTX_TITLE, PPTX_THEME_JSON, PPTX_COLOR_TREATMENT, and PPTX_TEXT_BOX_STYLE.
Long documents are indexed into a RAPTOR tree (Recursive Abstractive Processing for Tree-Organized Retrieval) for targeted per-chunk context injection during PPTX generation.
| Component | Path | Role |
|---|---|---|
embed_service.py |
scripts/raptor/ |
Local embedding via multilingual-e5-small ONNX (384-dim, 100+ languages, INT8 quantized ~118 MB) |
raptor_builder.py |
scripts/raptor/ |
Splits markdown β embeds sections β agglomerative clustering β hierarchical tree; uses a content-length-adaptive section threshold (_heading_section_threshold(): ~1 section per 3 KB, clamped 2β8) to decide when heading-based splitting is sufficient before falling back to semantic paragraph splitting |
raptor_retriever.py |
scripts/raptor/ |
Per-chunk cosine retrieval across all tree levels β top-K relevant sections |
raptor-handler.ts |
electron/ipc/data/ |
TypeScript IPC wrapper calling Python scripts via execFile |
download_model.py |
scripts/raptor/ |
Downloads model from HuggingFace (automated in pnpm dist) |
Pipeline: At ingestion, data-consumer.ts calls buildRaptorTree() which writes a .structured-summary.json with the RAPTOR tree and embeddings. At generation time, chat-handler.ts calls retrieveContext() with slide-derived queries, injecting only the most relevant sections (~12 KB) instead of the full document (~123 KB) into each chunk prompt.
Model files are stored at resources/models/embed/ (git-ignored) and bundled into packaged builds via electron-builder.yml. Run pnpm setup:models to download manually, or let pnpm dist handle it automatically.
Layout modules live in scripts/layout/ and compute content-adaptive slide coordinates before the Python renderer writes the deck.
pptx-handler.ts
β
ββ 1. computeLayoutSpecs() Call hybrid_layout.py as subprocess
β β
β hybrid_layout.py Orchestrator (CLI entry point)
β ββ layout_blueprint.py Load declarative zone definitions
β ββ font_text_measure.py Measure text heights via Pillow font metrics (cross-platform)
β ββ constraint_solver.py Solve zone positions with kiwisolver
β ββ layout_specs.py Emit LayoutSpec / RectSpec dataclasses
β β
β LayoutSpec JSON (stdout)
β
ββ 2. renderPresentationToFile()
β β PPTX_LAYOUT_SPECS_JSON env var
β pptx-python-runner.py Deserialize specs β PRECOMPUTED_LAYOUT_SPECS
β ββ slide_renderer.py Deterministic renderer consumes specs for positioning
β
ββ 3. Post-generation
layout_validator.py Validate overlap, bounds, text overflow
| Module | Role |
|---|---|
hybrid_layout.py |
Orchestrator + JSON serialization + CLI entry point; uses Pillow measurement with heuristic fallback |
layout_blueprint.py |
Declarative zone definitions for 14 layout types |
font_text_measure.py |
Text height measurement via Pillow font metrics (cross-platform, ~90β95% accuracy) |
constraint_solver.py |
Kiwisolver (Cassowary) constraint solver β LayoutSpec |
layout_specs.py |
LayoutSpec / RectSpec dataclasses and flow_layout_spec() cascade helper |
layout_validator.py |
Post-generation validation (overlap, bounds, text overflow) |
Pre-computed specs are injected into pptx-python-runner.py through PPTX_LAYOUT_SPECS_JSON, then deserialized into PRECOMPUTED_LAYOUT_SPECS for the deterministic renderer. Requires kiwisolver.
Hybrid layout artifacts are stored in the active workspace under previews/:
layout-input.jsonβ the storyboard-derivedSlideContent[]payload written immediately when the slide scenario is set and refreshed again before layout computationlayout-specs.jsonβ the computedLayoutSpec[]output written byhybrid_layout.py
- Pillow font-metrics β cross-platform: simulates word-wrap via TrueType glyph metrics; accurate enough for layout planning (~90β95% accuracy)
- Heuristic fallback β uses the shared text-height estimator only if Pillow is unavailable
- Auto-size β post-generation escape hatch: sets
TEXT_TO_FIT_SHAPEon shapes and lets PowerPoint shrink text at open time when overflow repair still cannot reclaim enough space
App data is stored in the Electron userData directory:
| File | Path (Windows) | Description |
|---|---|---|
settings.json |
%APPDATA%\pptx-slide-agent\settings.json |
API keys, model settings, and other preferences |
workspace.json |
%APPDATA%\pptx-slide-agent\workspace.json |
Last-used workspace directory |
On macOS the equivalent path is ~/Library/Application Support/pptx-slide-agent/.
Work can be saved and loaded as .pptapp project files (JSON). A project snapshot includes:
- Slide outline / story content
- Chat message history
- Full palette configuration (theme slots and tokens)
The center preview panel renders local slide PNGs generated from the preview PPTX. Use Load Preview to read cached images from the workspace, Rerender to export fresh images from PowerPoint via COM, and Open in PowerPoint to launch the latest preview deck in the desktop app.
Important behavior:
- Artifact-level PPTX fixes can regenerate the preview
.pptxwithout automatically refreshing the PNG previews currently shown in the center pane. - After a chat repair changes layout, style, icons, images, or render settings, click
Rerenderto refresh the preview images from the updated PPTX. - If you want to inspect the repair steps in chat, enable
Show Tool Calling Messagesin Settings.
Prompt workflow files live here:
Workflow loading is provider-neutral. The provider-specific runtime wiring lives in electron/ipc/llm.
The app can generate infographic images and slide decks from Google NotebookLM notebooks via the unofficial notebooklm-py library.
Requirements:
notebooklm-pyinstalled in the project.venv(declared inpyproject.toml)- one-time
notebooklm-pylibrary login completed in the project environment
Browser sign-in at https://notebooklm.google/ is not sufficient by itself. The app checks the notebooklm-py library's stored session, so use the in-app Open NotebookLM Login action or run python -m notebooklm login from the project .venv, complete the browser sign-in, then press ENTER in that terminal to save the session.
In the slide panel, toggle NotebookLM Infographic to select a notebook and generate an infographic PNG saved to the workspace images/ folder.
