NEXT_PUBLIC_* vars are inlined at build time — not available in Docker build context.
publicRuntimeConfig is resolved at runtime from next.config.mjs, which works correctly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
authSlice, clientSlice, custom-template hooks, dashboard/presentation-generation
services and CustomConfig — all /api/v1/ calls now route through apiFetch.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Without this, fetch('/api/v1/...') from the browser hits Apache root,
which routes /api/ to OliVAS (port 8000) instead of DeckForge (port 8001).
apiFetch prepends NEXT_PUBLIC_BASE_PATH so requests go through Next.js
rewrites to the correct backend.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add basePath/assetPrefix to next.config.mjs (was only patched on server)
- Fix AuthGuard to use router.push('/login') instead of window.location.href
(window.location ignores Next.js basePath, sent users to /login not /ppt-tool/login)
- Remove deploy.sh Step 10 deckforge.conf creation — DeckForge rules are now
merged into optical-dev.oliver.solutions.conf alongside OliVAS config
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Next.js basePath redirects /ppt-tool/ → /ppt-tool (308), then Apache
had no rule for the slash-less path. Added RedirectMatch to complete
the loop back to /ppt-tool/.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Avoids 3-10 min CPU-heavy index rebuild on every container start.
ONNX model + icons index baked into image at build time.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Read API/WEB/PG/REDIS ports from .env on startup (Docker Compose
loads .env directly, shell exports are ignored by it)
- check_port: auto-find next free port instead of interactive prompt
- set_env_port: write chosen port back to .env so it persists across runs
- port_taken_by_other: use 'ss | grep' instead of ss sport filter (more reliable)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Google Cloud LB marks backend unhealthy when GET / returns 302.
Serve /var/www/html/index.html (meta-redirect to /ppt-tool/) for root
so health check gets 200 OK and traffic flows through.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
In Compose v5, !reset resets a key to its default (empty) and ignores
the provided values. !override replaces the existing list entirely,
which is the correct behaviour for restricting ports to 127.0.0.1.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Next.js SSR takes 30-60s to compile on first boot — immediate curl check
always fails. Now retries every 5s for up to 24 attempts (2 minutes).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Docker Compose merges port lists from multiple -f files by concatenation.
Without !reset, both 0.0.0.0:8000:8000 (base) and 127.0.0.1:8000:8000
(prod override) are applied simultaneously, causing "port already allocated".
!reset clears the list before applying prod-specific bindings (requires
Docker Compose v2.24+, available in current docker-compose-plugin via apt).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Based on PPTAgent (EMNLP 2025) and DocPres research findings:
1. Brief summarization (summarize_brief.py)
- For content >800 chars: single LLM call extracts {overview, sections[{title,
key_points, data_points}]} before outline generation
- Prevents "lost middle" context loss in long documents
- BriefStructure.to_outline_context() formats sections for outline prompt
- BriefStructure.get_section_text(idx) returns targeted excerpt per slide
2. Section attribution in SlideOutlineModel
- Added source_section_idx: Optional[int] field
- LLM sets this during outline generation to map each slide → brief section
- Used to pass targeted section text to per-slide content generation
instead of full brief (reduces hallucination, improves accuracy)
3. Narrative continuity in slide content generation
- prev_slide_title passed to each content generation call
- Injected in user prompt: "ensure this slide continues naturally from..."
- Batch-safe: titles collected from completed batch before next starts
4. Source section text in content generation
- source_section_text parameter added to get_slide_content_from_type_and_outline
- Injected as "Source Material for This Slide" in user prompt
- Only data points present in the excerpt should be used
5. Richer layout catalog
- PresentationLayoutModel.to_catalog_string() added
- Includes field names + maxLength constraints alongside layout descriptions
- Helps LLM make informed layout choices based on content type
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- docling removed: PDF now parsed by PyMuPDF (fitz), PPTX by python-pptx
- layoutparser removed: already optional with graceful fallback (returns [])
- torch/pytorch index removed: no longer needed by any dependency
- pymupdf added: ~20MB wheel, no ML deps, faster than docling for text extraction
- All existing DOCX parsing kept (python-docx, already working)
- extract_text_from_image_via_vision() unchanged (Gemini API)
Result: api/worker Docker image ~3-4GB lighter, no NVIDIA libs on CPU server
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Without [tool.uv.sources] binding torch to the pytorch-cpu index,
docling/layoutparser pull the full CUDA torch from PyPI, which includes
nvidia-nccl, nvidia-cusparselt, nvidia-cudnn etc (~5-8GB of GPU libs
useless on CPU-only servers). This caused "no space left on device"
during Docker layer extraction on the production server.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously any Docker process on the port was treated as safe.
Now uses docker inspect on our project's containers specifically,
so ports used by other apps on the server trigger the conflict prompt.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Removes root check; uses sudo internally for apt/apache/ufw commands
so git pull and docker commands run as the invoking user (with correct
SSH keys and docker group membership).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
afterFiles ordering was insufficient in Next.js 14 — the catch-all
/api/v1/* rewrite still intercepted SSE requests before route handlers.
Fix: add a `missing` condition on `Accept: text/event-stream` to the
rewrite rule. EventSource always sends this header, so SSE requests now
skip the rewrite entirely and are handled by the existing route handlers
(app/api/v1/ppt/outlines/stream, presentation/stream, jobs/stream).
Normal JSON API requests are unaffected.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The flat-array form of rewrites() runs as beforeFiles, intercepting /api/v1/*
requests before Next.js can match route handlers. This caused the SSE stream
endpoints (outlines/stream, presentation/stream, jobs/stream) to be handled
by the buffering HTTP proxy instead of the custom streaming route handlers,
resulting in ECONNRESET → 500 on the browser.
afterFiles rewrites only apply when no matching app/api/ file exists, so
route handlers now win over the backend proxy for all /api/v1/* paths that
have explicit handlers.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- New POST /api/generate-pptx route (Next.js) uses PptxGenJS to build
PPTX directly from the Phase 8 JSON element model — no headless Chrome
- export_utils.py queries DB for slides + layout codes, POSTs payload to
Next.js, saves binary response to disk (removes python-pptx/Puppeteer)
- Coordinate conversion: px / 96 → inches (1280×720 = 13.333×7.5 in)
- CSS color/font-size parsing (hex, rgb/rgba, px→pt at 0.75pt/px)
- Fallback renderer for slides without a JSON layout schema
- PDF export (Puppeteer) unchanged
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Next.js rewrites() buffer HTTP responses and drop long-lived connections,
making SSE (text/event-stream) impossible. The backend never even received
the request (no log entry in API, ECONNRESET in web proxy logs).
Create dedicated route.ts files for all 3 SSE endpoints:
- /api/v1/ppt/outlines/stream/[id]
- /api/v1/ppt/presentation/stream/[id]
- /api/v1/ppt/jobs/[job_id]/stream
Each route forwards cookies for auth and returns backend's ReadableStream
directly as a Response, preventing any buffering. Sets X-Accel-Buffering: no
to also disable nginx buffering.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- outlines.py: Fix ECONNRESET/socket-hang-up — Depends session closes before
StreamingResponse generator runs; capture presentation data upfront, use
async_session_maker() inside inner() for the final DB commit (same pattern as Phase 4)
- useCustomTemplates.ts: Filter null-template items in summary map (crashed on
presentations without a TemplateModel); use item.layout_count instead of hardcoded 0
- TemplateSelection.tsx: Move custom AI templates section above built-in templates
- presentationGeneration.ts + OutlinePage.tsx: Add selectedTemplateId to Redux so
template selection persists when navigating away and back to /outline; clearOutlines
also resets selectedTemplateId for new presentation flows
- DocumentPreviewPage.tsx: Detect JSON file content (table decomposition output) and
convert to markdown table or pretty-printed code block before passing to MarkdownRenderer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- documents_loader: skip missing files (print warning + continue) instead
of raising HTTPException inside SSE generator — prevents "response
already started" runtime error when temp files are wiped on restart
- V1ContentRender: wrap non-edit mode render in SlideErrorBoundary so
broken custom layout components show a red placeholder instead of
crashing the page (React error #62)
- CustomTemplateCard: wrap LayoutPreview in SlideErrorBoundary for the
same reason — bad compiled TSX from master deck parsing no longer
crashes the outline template picker
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Keep asyncio import for future timeout implementation.
Handle TimeoutError and other exceptions properly.
Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
Prevents socket hang up when LLM is slow or unavailable.
- Timeout: 120 seconds (configurable via OUTLINE_TIMEOUT_SECONDS)
- Returns 504 Gateway Timeout error if exceeded
- Fixes: socket hang up errors in Next.js proxy
Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
- Added PARSING_MODEL env var (default: gemini-2.5-flash-lite)
- Master deck parser now uses fast lite model
- 3-4x faster than gemini-3.1-pro: 22 layouts = 8-12 min vs 44-60 min
- Keep gemini-3.1-pro for presentation generation (quality)
Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
- PostgreSQL SET LOCAL doesn't support parameterized queries
- Use string formatting (safe: user.id is UUID, role is enum)
- Fixes 'syntax error at or near $1' error
Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
- Strip 'models/' prefix from Google API response for consistency
- Model names in .env should be without prefix (e.g., gemini-3.1-pro-preview)
- Fixes startup failure when checking model availability
Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
Security Improvements (P0.0-P0.4):
- P0.0: Migrate to Gemini-only AI stack (simplified, single billing)
- P0.1: Fix CORS to restrict allowed origins from env (was *)
- P0.2: Remove hardcoded dev password, require env var
- P0.3: Add rate limiting (slowapi) - 3-10 req/min on sensitive endpoints
- P0.4: Add request size limits (100MB default via middleware)
New Features:
- Unified LLM service with Google Gemini priority
- OXML geometry extractor for layout parsing
- TSX validator for generated React components
- Client ID support in presentation requests with access control
- Configurable LLM/image timeouts via env vars
Modern Design System (P0.9 - partial):
- Enhanced CSS design tokens (primary, semantic colors, shadows)
- Typography scale (h1-h4, body variants, caption)
- Modern animations (fadeIn, slideIn, scaleIn)
- Updated Button component with better variants and hover effects
- Created unified Card and StatusBadge components
- Applied design system to Dashboard and Settings pages
Backend Improvements:
- Master deck parser simplification
- Slide-to-HTML endpoint cleanup (325 lines removed)
- Better error handling in prompts endpoint
Frontend Improvements:
- Settings UI simplified to show only Google/Gemini
- Dashboard uses CSS variables instead of hardcoded colors
- Improved button transitions and hover states
Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
- Fix image path handling in purge (remove incorrect lstrip)
- Add hard-delete of presentation records from database after file cleanup
- Add debug logging throughout purge process
- Fix outline parsing when LLM returns slides as JSON string
- Fix purge success message to show total files count
Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
- Change NanoBanana Pro model from gemini-3-pro-image-preview to gemini-2.0-flash-exp-image (previous model doesn't exist)
- Purge endpoint now deletes generated images from /app_data/images/ when soft-deleted presentations are purged
- Add purged_images counter to purge response
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
PDF documents with Mermaid diagrams, styled with purple theme.
Includes build script for regenerating PDFs from Markdown sources.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>