NEXT_PUBLIC_* vars are baked at Next.js build time but were not
available during Docker build (web service had no env_file/build args).
Hardcode IDs as fallback in msalConfig.ts, also wire AZURE_AD_*
through Dockerfile ARGs and docker-compose build args.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- clientSlice: fetchMasterDecks + fetchClientPresentations use apiFetch (adds /ppt-tool basePath)
- useCustomTemplates: parsedLayoutToCompiled generates schemaJSON from elements instead of null
(null schemaJSON caused 422 on /prepare because backend SlideLayoutModel.json_schema: dict is required)
- presentation.py: update_presentation uses SlideInput (plain Pydantic model with extra='ignore')
instead of SlideModel (table=True SQLModel) to avoid strict validation causing 422 on /update
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Detects changed files since last pull, rebuilds only affected services.
Use ./update.sh for code changes, ./deploy.sh for dep/Dockerfile changes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- AZURE_AD_REDIRECT_URI set to https://optical-dev.oliver.solutions/ppt-tool/
- Root page intercepts ?code= from Azure AD and forwards to backend callback
- Post-OAuth redirect uses NEXT_PUBLIC_BASE_PATH env var (/ppt-tool/dashboard)
- Cookie secure flag driven by COOKIE_SECURE env var (true in prod)
- Dev login now works alongside Azure AD when DEV_AUTH_PASSWORD is set
- Login page shows both Microsoft SSO and dev form when both modes enabled
- docker-compose.prod.yml: add COOKIE_SECURE=true and NEXT_PUBLIC_BASE_PATH
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
window.location.href bypasses Next.js basePath, sending to /dashboard instead of /ppt-tool/dashboard.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>