diff --git a/.github/workflows/test-all.yml b/.github/workflows/test-all.yml index a8a78d2d..e70acc30 100644 --- a/.github/workflows/test-all.yml +++ b/.github/workflows/test-all.yml @@ -33,7 +33,8 @@ jobs: libreoffice \ fontconfig \ chromium \ - chromium-driver + chromium-driver \ + imagemagick - name: Install Python dependencies run: | @@ -81,7 +82,8 @@ jobs: libreoffice \ fontconfig \ chromium \ - chromium-driver + chromium-driver \ + imagemagick - name: Install Python dependencies run: | diff --git a/.gitignore b/.gitignore index 0ec246cf..71c9e7ab 100644 --- a/.gitignore +++ b/.gitignore @@ -21,4 +21,9 @@ container.db .cursor .agents skills-lock.json -.codex/ \ No newline at end of file +.codex/ + +# presentation-export runtime (downloaded via scripts/sync-presentation-export.cjs or Docker build) +presentation-export/index.js +presentation-export/py/ +.cache/presentation-export/ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index b9c1bf32..476ad1a0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,62 +1,62 @@ -FROM python:3.11-slim-bookworm - -# Install Node.js and npm -RUN apt-get update && apt-get install -y \ - nginx \ - curl \ - libreoffice \ - fontconfig \ - chromium \ - zstd - - -# Install Node.js 20 using NodeSource repository -RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \ - apt-get install -y nodejs - - -# Create a working directory -WORKDIR /app - -# Set environment variables -ENV APP_DATA_DIRECTORY=/app_data -ENV TEMP_DIRECTORY=/tmp/presenton -ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium - - -# Install ollama -RUN curl -fsSL https://ollama.com/install.sh | sh - -# Install dependencies for FastAPI -RUN pip install alembic aiohttp aiomysql aiosqlite asyncpg fastapi[standard] \ - pathvalidate pdfplumber chromadb sqlmodel \ - anthropic google-genai openai fastmcp dirtyjson -RUN pip install docling --extra-index-url https://download.pytorch.org/whl/cpu - -# Install dependencies for Next.js -WORKDIR /app/servers/nextjs -COPY servers/nextjs/package.json servers/nextjs/package-lock.json ./ -RUN npm install - - -# Copy Next.js app -COPY servers/nextjs/ /app/servers/nextjs/ - -# Build the Next.js app -WORKDIR /app/servers/nextjs -RUN npm run build +# syntax=docker/dockerfile:1.4 +FROM python:3.11-slim-trixie WORKDIR /app -# Copy FastAPI -COPY servers/fastapi/ ./servers/fastapi/ -COPY start.js LICENSE NOTICE ./ +# LiteParse uses Node + @llamaindex/liteparse (same runner as Electron); OCR uses Tesseract. +ENV APP_DATA_DIRECTORY=/app_data \ + TEMP_DIRECTORY=/tmp/presenton \ + PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium \ + UV_SYSTEM_PYTHON=1 \ + UV_COMPILE_BYTECODE=1 \ + UV_LINK_MODE=copy \ + PATH="/root/.local/bin:${PATH}" \ + EXPORT_PACKAGE_ROOT=/app/presentation-export \ + EXPORT_RUNTIME_DIR=/app/presentation-export \ + BUILT_PYTHON_MODULE_PATH=/app/presentation-export/py/convert-linux-x64 \ + PRESENTON_APP_ROOT=/app -# Copy nginx configuration +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates curl unzip \ + nginx libreoffice fontconfig chromium imagemagick zstd \ + tesseract-ocr tesseract-ocr-eng \ + && curl -LsSf https://astral.sh/uv/install.sh | sh \ + && rm -rf /var/lib/apt/lists/* + +RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \ + && apt-get install -y nodejs \ + && rm -rf /var/lib/apt/lists/* + +COPY package.json package-lock.json /app/ +RUN npm --prefix /app install --omit=dev + +RUN mkdir -p /app/document-extraction-liteparse \ + && npm --prefix /app/document-extraction-liteparse init -y \ + && npm --prefix /app/document-extraction-liteparse install @llamaindex/liteparse@1.4.0 --omit=dev +COPY electron/resources/document-extraction/liteparse_runner.mjs /app/document-extraction-liteparse/liteparse_runner.mjs + +COPY scripts/sync-presentation-export.cjs /app/scripts/sync-presentation-export.cjs +RUN node /app/scripts/sync-presentation-export.cjs --force \ + && chmod +x /app/presentation-export/py/convert-linux-x64 + +RUN curl -fsSL https://ollama.com/install.sh | sh + +COPY servers/fastapi /app/servers/fastapi +WORKDIR /app/servers/fastapi +RUN --mount=type=cache,target=/root/.cache/uv \ + uv export --frozen --no-dev --no-emit-project -o /tmp/requirements.txt \ + && uv pip install --system -r /tmp/requirements.txt \ + && uv pip install --system --no-deps . + +WORKDIR /app/servers/nextjs +COPY servers/nextjs/package.json servers/nextjs/package-lock.json ./ +RUN npm install +COPY servers/nextjs/ /app/servers/nextjs/ +RUN npm run build + +WORKDIR /app +COPY start.js LICENSE NOTICE ./ COPY nginx.conf /etc/nginx/nginx.conf -# Expose the port EXPOSE 80 - -# Start the servers -CMD ["node", "/app/start.js"] \ No newline at end of file +CMD ["node", "/app/start.js"] diff --git a/Dockerfile.dev b/Dockerfile.dev index 72732d7e..f002900b 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -1,43 +1,55 @@ -FROM python:3.11-slim-bookworm +# syntax=docker/dockerfile:1.4 +FROM python:3.11-slim-trixie -# Install Node.js and npm -RUN apt-get update && apt-get install -y \ - nginx \ - curl \ - libreoffice \ - fontconfig \ - chromium \ - zstd - -# Install Node.js 20 using NodeSource repository -RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \ - apt-get install -y nodejs - - -# Change working directory WORKDIR /app -RUN ls -a +# LiteParse (Node + @llamaindex/liteparse) for document extraction; OCR via Tesseract. +ENV APP_DATA_DIRECTORY=/app_data \ + TEMP_DIRECTORY=/tmp/presenton \ + PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium \ + UV_SYSTEM_PYTHON=1 \ + UV_COMPILE_BYTECODE=1 \ + UV_LINK_MODE=copy \ + PATH="/root/.local/bin:${PATH}" \ + EXPORT_PACKAGE_ROOT=/app/presentation-export \ + EXPORT_RUNTIME_DIR=/app/presentation-export \ + BUILT_PYTHON_MODULE_PATH=/app/presentation-export/py/convert-linux-x64 \ + PRESENTON_APP_ROOT=/app -# Set environment variables -ENV APP_DATA_DIRECTORY=/app_data -ENV TEMP_DIRECTORY=/tmp/presenton -ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates curl unzip \ + nginx libreoffice fontconfig chromium imagemagick zstd \ + tesseract-ocr tesseract-ocr-eng \ + && curl -LsSf https://astral.sh/uv/install.sh | sh \ + && rm -rf /var/lib/apt/lists/* -# Install ollama -# RUN curl -fsSL http://ollama.com/install.sh | sh +RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \ + && apt-get install -y nodejs \ + && rm -rf /var/lib/apt/lists/* -# Install dependencies for FastAPI -RUN pip install alembic aiohttp aiomysql aiosqlite asyncpg fastapi[standard] \ - pathvalidate pdfplumber chromadb sqlmodel \ - anthropic google-genai openai fastmcp dirtyjson -RUN pip install docling --extra-index-url https://download.pytorch.org/whl/cpu +COPY package.json package-lock.json /app/ +RUN npm --prefix /app install --omit=dev -# Copy nginx configuration +RUN mkdir -p /app/document-extraction-liteparse \ + && npm --prefix /app/document-extraction-liteparse init -y \ + && npm --prefix /app/document-extraction-liteparse install @llamaindex/liteparse@1.4.0 --omit=dev +COPY electron/resources/document-extraction/liteparse_runner.mjs /app/document-extraction-liteparse/liteparse_runner.mjs + +COPY scripts/sync-presentation-export.cjs /app/scripts/sync-presentation-export.cjs +RUN node /app/scripts/sync-presentation-export.cjs --force \ + && chmod +x /app/presentation-export/py/convert-linux-x64 + +# Bind mount `.:/app` hides any .venv under servers/fastapi at runtime — install deps into +# system site-packages (same interpreter `start.js` uses as `python`). +COPY servers/fastapi /app/servers/fastapi +WORKDIR /app/servers/fastapi +RUN --mount=type=cache,target=/root/.cache/uv \ + uv export --frozen --no-dev --no-emit-project -o /tmp/requirements.txt \ + && uv pip install --system -r /tmp/requirements.txt \ + && uv pip install --system --no-deps . + +WORKDIR /app COPY nginx.conf /etc/nginx/nginx.conf -# Expose the port EXPOSE 80 - -# Start the servers CMD ["node", "/app/start.js", "--dev"] diff --git a/README.md b/README.md index 5626132b..78534c81 100644 --- a/README.md +++ b/README.md @@ -214,6 +214,15 @@ These settings apply to both Docker and the Electron app's backend. You may want - TOOL_CALLS=[Enable/Disable Tool Calls on Custom LLM]: If **true**, **LLM** will use Tool Call instead of Json Schema for Structured Output. - DISABLE_THINKING=[Enable/Disable Thinking on Custom LLM]: If **true**, Thinking will be disabled. - WEB_GROUNDING=[Enable/Disable Web Search for OpenAI, Google And Anthropic]: If **true**, LLM will be able to search web for better results. +- MEM0_ENABLED=[true/false]: Enables mem0 OSS presentation memory. Default is **true**. +- MEM0_DIR=[Path]: Directory for mem0 OSS local storage (Qdrant path + history DB). Default is **/app_data/mem0**. +- MEM0_EMBEDDER_PROVIDER=[Provider]: Embedder provider for mem0 OSS. Default is **fastembed**. +- MEM0_EMBEDDER_MODEL=[Model]: Mid-range local embedding model for memory search. Default is **BAAI/bge-small-en-v1.5**. +- MEM0_EMBEDDING_DIMS=[Number]: Embedding dimensions used by mem0 embedder and qdrant collection. Default is **384**. +- LITEPARSE_DPI=[Number]: LiteParse OCR render DPI (higher can increase memory use). Default is **120**. +- LITEPARSE_NUM_WORKERS=[Number]: LiteParse OCR worker count. Default is **1** for stable Docker parsing. + +Mem0 in Docker uses OSS/self-hosted mode (not Mem0 Platform API). Memory is isolated per presentation ID. Prompt context, extracted document text, generated outline context, and subsequent edit interactions are stored and retrieved only for that same presentation (including when revisiting later). You can also set the following environment variables to customize the image generation provider and API keys: diff --git a/docker-compose.yml b/docker-compose.yml index 9698ed29..6def2521 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -36,6 +36,13 @@ services: - DISABLE_ANONYMOUS_TRACKING=${DISABLE_ANONYMOUS_TRACKING} - COMFYUI_URL=${COMFYUI_URL} - COMFYUI_WORKFLOW=${COMFYUI_WORKFLOW} + - MEM0_ENABLED=${MEM0_ENABLED:-true} + - MEM0_DIR=${MEM0_DIR:-/app_data/mem0} + - MEM0_EMBEDDER_PROVIDER=${MEM0_EMBEDDER_PROVIDER:-fastembed} + - MEM0_EMBEDDER_MODEL=${MEM0_EMBEDDER_MODEL:-BAAI/bge-small-en-v1.5} + - MEM0_EMBEDDING_DIMS=${MEM0_EMBEDDING_DIMS:-384} + - LITEPARSE_DPI=${LITEPARSE_DPI:-120} + - LITEPARSE_NUM_WORKERS=${LITEPARSE_NUM_WORKERS:-1} - OPEN_WEBUI_IMAGE_URL=${OPEN_WEBUI_IMAGE_URL} - OPEN_WEBUI_IMAGE_API_KEY=${OPEN_WEBUI_IMAGE_API_KEY} @@ -83,6 +90,14 @@ services: - DISABLE_ANONYMOUS_TRACKING=${DISABLE_ANONYMOUS_TRACKING} - COMFYUI_URL=${COMFYUI_URL} - COMFYUI_WORKFLOW=${COMFYUI_WORKFLOW} + - MEM0_ENABLED=${MEM0_ENABLED:-true} + - MEM0_DIR=${MEM0_DIR:-/app_data/mem0} + - MEM0_EMBEDDER_PROVIDER=${MEM0_EMBEDDER_PROVIDER:-fastembed} + - MEM0_EMBEDDER_MODEL=${MEM0_EMBEDDER_MODEL:-BAAI/bge-small-en-v1.5} + - MEM0_EMBEDDING_DIMS=${MEM0_EMBEDDING_DIMS:-384} + - LITEPARSE_DPI=${LITEPARSE_DPI:-120} + - LITEPARSE_NUM_WORKERS=${LITEPARSE_NUM_WORKERS:-1} + - OPEN_WEBUI_IMAGE_URL=${OPEN_WEBUI_IMAGE_URL} - OPEN_WEBUI_IMAGE_API_KEY=${OPEN_WEBUI_IMAGE_API_KEY} @@ -96,8 +111,12 @@ services: - "1455:1455" volumes: - .:/app + - presenton_root_node_modules:/app/node_modules + - presenton_document_extraction_liteparse:/app/document-extraction-liteparse - ./app_data:/app_data environment: + # Dockerfile.dev does not install ollama; use a host daemon via OLLAMA_URL or omit. + - START_EMBEDDED_OLLAMA=false - MIGRATE_DATABASE_ON_STARTUP=true - CAN_CHANGE_KEYS=${CAN_CHANGE_KEYS} - LLM=${LLM} @@ -122,6 +141,13 @@ services: - DISABLE_ANONYMOUS_TRACKING=${DISABLE_ANONYMOUS_TRACKING} - COMFYUI_URL=${COMFYUI_URL} - COMFYUI_WORKFLOW=${COMFYUI_WORKFLOW} + - MEM0_ENABLED=${MEM0_ENABLED:-true} + - MEM0_DIR=${MEM0_DIR:-/app_data/mem0} + - MEM0_EMBEDDER_PROVIDER=${MEM0_EMBEDDER_PROVIDER:-fastembed} + - MEM0_EMBEDDER_MODEL=${MEM0_EMBEDDER_MODEL:-BAAI/bge-small-en-v1.5} + - MEM0_EMBEDDING_DIMS=${MEM0_EMBEDDING_DIMS:-384} + - LITEPARSE_DPI=${LITEPARSE_DPI:-120} + - LITEPARSE_NUM_WORKERS=${LITEPARSE_NUM_WORKERS:-1} - OPEN_WEBUI_IMAGE_URL=${OPEN_WEBUI_IMAGE_URL} - OPEN_WEBUI_IMAGE_API_KEY=${OPEN_WEBUI_IMAGE_API_KEY} @@ -142,8 +168,11 @@ services: - "1455:1455" volumes: - .:/app + - presenton_root_node_modules:/app/node_modules + - presenton_document_extraction_liteparse:/app/document-extraction-liteparse - ./app_data:/app_data environment: + - START_EMBEDDED_OLLAMA=false - MIGRATE_DATABASE_ON_STARTUP=true - CAN_CHANGE_KEYS=${CAN_CHANGE_KEYS} - LLM=${LLM} @@ -168,5 +197,16 @@ services: - DISABLE_ANONYMOUS_TRACKING=${DISABLE_ANONYMOUS_TRACKING} - COMFYUI_URL=${COMFYUI_URL} - COMFYUI_WORKFLOW=${COMFYUI_WORKFLOW} + - MEM0_ENABLED=${MEM0_ENABLED:-true} + - MEM0_DIR=${MEM0_DIR:-/app_data/mem0} + - MEM0_EMBEDDER_PROVIDER=${MEM0_EMBEDDER_PROVIDER:-fastembed} + - MEM0_EMBEDDER_MODEL=${MEM0_EMBEDDER_MODEL:-BAAI/bge-small-en-v1.5} + - MEM0_EMBEDDING_DIMS=${MEM0_EMBEDDING_DIMS:-384} + - LITEPARSE_DPI=${LITEPARSE_DPI:-120} + - LITEPARSE_NUM_WORKERS=${LITEPARSE_NUM_WORKERS:-1} + +volumes: + presenton_root_node_modules: + presenton_document_extraction_liteparse: - OPEN_WEBUI_IMAGE_URL=${OPEN_WEBUI_IMAGE_URL} - OPEN_WEBUI_IMAGE_API_KEY=${OPEN_WEBUI_IMAGE_API_KEY} diff --git a/electron/servers/fastapi/constants/llm.py b/electron/servers/fastapi/constants/llm.py index 3f663f6c..1172de4f 100644 --- a/electron/servers/fastapi/constants/llm.py +++ b/electron/servers/fastapi/constants/llm.py @@ -4,4 +4,4 @@ OPENAI_URL = "https://api.openai.com/v1" DEFAULT_OPENAI_MODEL = "gpt-4.1" DEFAULT_GOOGLE_MODEL = "models/gemini-2.5-flash" DEFAULT_ANTHROPIC_MODEL = "claude-sonnet-4-20250514" -DEFAULT_CODEX_MODEL = "gpt-5.1-codex-mini" +DEFAULT_CODEX_MODEL = "gpt-5.2" diff --git a/electron/servers/fastapi/migrations.py b/electron/servers/fastapi/migrations.py index 4cb75de0..335c4ef4 100644 --- a/electron/servers/fastapi/migrations.py +++ b/electron/servers/fastapi/migrations.py @@ -11,6 +11,8 @@ from utils.get_env import get_migrate_database_on_startup_env LEGACY_BASELINE_REVISION = "00b3c27a13bc" +# Revision before 95b5127e93cd (template_create_infos); used when DB has theme but not that table. +REVISION_BEFORE_TEMPLATE_CREATE_INFO = "82abdbc476a7" async def migrate_database_on_startup() -> None: @@ -49,6 +51,7 @@ def _run_migrations() -> None: database_url = _to_sync_database_url(database_url) config.set_main_option("sqlalchemy.url", database_url) + _repair_orphan_alembic_revision(config, database_url) _stamp_legacy_database_if_needed(config, database_url) try: @@ -62,6 +65,52 @@ def _run_migrations() -> None: raise +def _repair_orphan_alembic_revision(config: Config, database_url: str) -> None: + """ + If alembic_version points at a revision id that no longer exists in alembic/versions + (removed branch, old image, etc.), re-stamp from the live schema so upgrade can run. + """ + script = ScriptDirectory.from_config(config) + known = {rev.revision for rev in script.walk_revisions()} + heads = script.get_heads() + if len(heads) != 1: + return + head = heads[0] + + engine = create_engine(database_url) + try: + with engine.connect() as connection: + inspector = inspect(connection) + tables = set(inspector.get_table_names()) + if "alembic_version" not in tables: + return + version_num = connection.execute( + text("SELECT version_num FROM alembic_version LIMIT 1") + ).scalar_one_or_none() + if not version_num or version_num in known: + return + print( + f"Alembic revision {version_num!r} is missing from the codebase; " + "inferring applied migrations from schema and re-stamping.", + flush=True, + ) + target = _infer_revision_from_schema(inspector, tables, head) + command.stamp(config, target) + finally: + engine.dispose() + + +def _infer_revision_from_schema(inspector, tables: set[str], head_revision: str) -> str: + """Best-effort: map existing SQLite/Postgres schema to our linear migration chain.""" + if "template_create_infos" in tables: + return head_revision + if "presentations" in tables: + cols = {c["name"] for c in inspector.get_columns("presentations")} + if "theme" in cols: + return REVISION_BEFORE_TEMPLATE_CREATE_INFO + return LEGACY_BASELINE_REVISION + + def _stamp_legacy_database_if_needed(config: Config, database_url: str) -> None: """ If the DB has app tables but no migration reference in alembic_version, diff --git a/electron/servers/fastapi/services/export_task_service.py b/electron/servers/fastapi/services/export_task_service.py index 571dee01..dccba7ab 100644 --- a/electron/servers/fastapi/services/export_task_service.py +++ b/electron/servers/fastapi/services/export_task_service.py @@ -4,7 +4,6 @@ import os import shutil import subprocess import tempfile -import uuid from typing import Mapping from fastapi import HTTPException @@ -33,7 +32,7 @@ class ExportTaskService: self.timeout_seconds = timeout_seconds self.node_binary = os.getenv("LITEPARSE_NODE_BINARY", "node") self.export_dir = self._resolve_export_dir() - self.entrypoint_path = os.path.join(self.export_dir, "index.js") + self.entrypoint_path = self._resolve_entrypoint_path(self.export_dir) self.converter_path = self._resolve_converter_path(self.export_dir) @staticmethod @@ -42,31 +41,40 @@ class ExportTaskService: if configured: return configured + package_root = (os.getenv("EXPORT_PACKAGE_ROOT") or "").strip() + if package_root: + return package_root + cwd = os.path.abspath(".") service_dir = os.path.dirname(__file__) candidates = [ - os.path.abspath(os.path.join(cwd, "..", "..", "resources", "export")), - os.path.abspath(os.path.join(cwd, "..", "export")), - os.path.abspath( - os.path.join(service_dir, "..", "..", "..", "resources", "export") - ), - os.path.abspath(os.path.join(service_dir, "..", "..", "export")), - os.path.abspath( - os.path.join(cwd, "..", "..", "electron", "resources", "export") - ), - os.path.abspath( - os.path.join( - service_dir, "..", "..", "..", "..", "electron", "resources", "export" - ) - ), + os.path.abspath(os.path.join(cwd, "..", "..", "presentation-export")), + os.path.abspath(os.path.join(cwd, "..", "presentation-export")), + os.path.abspath(os.path.join(service_dir, "..", "..", "..", "presentation-export")), + os.path.abspath(os.path.join(service_dir, "..", "..", "..", "..", "presentation-export")), ] for candidate in candidates: - if os.path.isfile(os.path.join(candidate, "index.js")): + if os.path.isfile(os.path.join(candidate, "index.cjs")) or os.path.isfile( + os.path.join(candidate, "index.js") + ): return candidate return candidates[0] + @staticmethod + def _resolve_entrypoint_path(export_dir: str) -> str: + index_cjs = os.path.join(export_dir, "index.cjs") + if os.path.isfile(index_cjs): + return index_cjs + + index_js = os.path.join(export_dir, "index.js") + if os.path.isfile(index_js): + shutil.copyfile(index_js, index_cjs) + return index_cjs + + return index_cjs + @staticmethod def _resolve_converter_path(export_dir: str) -> str: py_dir = os.path.join(export_dir, "py") diff --git a/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/settings/SettingCodex.tsx b/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/settings/SettingCodex.tsx index 756e4317..bdd6df18 100644 --- a/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/settings/SettingCodex.tsx +++ b/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/settings/SettingCodex.tsx @@ -52,10 +52,9 @@ const CHATGPT_MODELS: CodexModel[] = [ { id: "gpt-5.4-mini", name: "GPT-5.4-Mini" }, { id: "gpt-5.3-codex", name: "GPT-5.3-Codex" }, { id: "gpt-5.2", name: "GPT-5.2" }, - { id: "gpt-5.1-codex-mini", name: "GPT-5.1-Codex-Mini" }, ]; -const DEFAULT_CODEX_MODEL = "gpt-5.1-codex-mini"; +const DEFAULT_CODEX_MODEL = "gpt-5.2"; export default function CodexConfig({ codexModel, diff --git a/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/settings/SettingPage.tsx b/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/settings/SettingPage.tsx index 404a1265..eccc2ed6 100644 --- a/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/settings/SettingPage.tsx +++ b/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/settings/SettingPage.tsx @@ -22,6 +22,8 @@ import ImageProvider from "./ImageProvider"; import PrivacySettings from "./PrivacySettings"; import { IMAGE_PROVIDERS, LLM_PROVIDERS } from "@/utils/providerConstants"; import { ImagesApi } from "@/app/(presentation-generator)/services/api/images"; +import { getApiUrl } from "@/utils/api"; +import { toast } from "sonner"; const STOCK_IMAGE_PROVIDERS = new Set(["pexels", "pixabay"]); @@ -105,7 +107,32 @@ const SettingsPage = () => { } }; + const checkCurrentAuthStatus = async () => { + try { + const res = await fetch(getApiUrl("/api/v1/ppt/codex/auth/status")); + if (!res.ok) { + return false; + } + const data = await res.json(); + if (data.status === "authenticated") { + return true; + } else { + return false; + } + } catch { + return false; + } + }; + const handleSaveConfig = async () => { + + if (llmConfig.LLM === 'codex') { + const isAuthenticated = await checkCurrentAuthStatus(); + if (!isAuthenticated) { + toast.error("Please sign in to ChatGPT to continue"); + return; + } + } trackEvent(MixpanelEvent.Settings_SaveConfiguration_Button_Clicked, { pathname }); const validationError = getLLMConfigValidationError(llmConfig); if (validationError) { diff --git a/electron/servers/nextjs/app/(presentation-generator)/custom-template/components/FileUploadSection.tsx b/electron/servers/nextjs/app/(presentation-generator)/custom-template/components/FileUploadSection.tsx index 10fe87a8..b73d69fd 100644 --- a/electron/servers/nextjs/app/(presentation-generator)/custom-template/components/FileUploadSection.tsx +++ b/electron/servers/nextjs/app/(presentation-generator)/custom-template/components/FileUploadSection.tsx @@ -113,10 +113,10 @@ export const FileUploadSection: React.FC = ({ Click to Upload or drag & drop.

- :
-
+ :
+
-
+
-
-

{selectedFile.name}

+
+

{selectedFile.name}

Presentation ( {(selectedFile.size / (1024 * 1024)).toFixed(2)} MB)

diff --git a/electron/servers/nextjs/app/(presentation-generator)/presentation/components/PresentationPage.tsx b/electron/servers/nextjs/app/(presentation-generator)/presentation/components/PresentationPage.tsx index 706f92b5..c5a996a3 100644 --- a/electron/servers/nextjs/app/(presentation-generator)/presentation/components/PresentationPage.tsx +++ b/electron/servers/nextjs/app/(presentation-generator)/presentation/components/PresentationPage.tsx @@ -157,7 +157,7 @@ const PresentationPage: React.FC = ({ background: "rgba(255, 255, 255, 0.10)", boxShadow: "0 0 20.01px 0 rgba(122, 90, 248, 0.16) inset", }} - className="p-6 rounded-[20px] flex flex-col items-center overflow-hidden justify-center border border-[#EDECEC] " + className="p-6 rounded-[20px] font-inter flex flex-col items-center overflow-hidden justify-center border border-[#EDECEC] " >
diff --git a/electron/servers/nextjs/app/(presentation-generator)/upload/components/AdvanceSettings.tsx b/electron/servers/nextjs/app/(presentation-generator)/upload/components/AdvanceSettings.tsx index 59b226c6..9343554d 100644 --- a/electron/servers/nextjs/app/(presentation-generator)/upload/components/AdvanceSettings.tsx +++ b/electron/servers/nextjs/app/(presentation-generator)/upload/components/AdvanceSettings.tsx @@ -1,172 +1,255 @@ -import ToolTip from '@/components/ToolTip' -import { Button } from '@/components/ui/button' -import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog' -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' -import { Switch } from '@/components/ui/switch' -import { Textarea } from '@/components/ui/textarea' -import { SlidersHorizontal } from 'lucide-react' -import React, { useState } from 'react' -import { PresentationConfig, ToneType, VerbosityType } from '../type' - +import ToolTip from '@/components/ToolTip'; +import { Button } from '@/components/ui/button'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import { Switch } from '@/components/ui/switch'; +import { Textarea } from '@/components/ui/textarea'; +import { Pencil, SlidersHorizontal, X } from 'lucide-react'; +import React, { useEffect, useState } from 'react'; +import { PresentationConfig, ToneType, VerbosityType } from '../type'; interface ConfigurationSelectsProps { - config: PresentationConfig; - onConfigChange: (key: keyof PresentationConfig, value: any) => void; + config: PresentationConfig; + onConfigChange: (key: keyof PresentationConfig, value: any) => void; } + +const toggleClassName = + 'h-[22px] w-[36px] border-0 bg-[#D8D8DD] data-[state=checked]:bg-[#7A5AF8] '; + const AdvanceSettings = ({ config, onConfigChange }: ConfigurationSelectsProps) => { + const [openAdvanced, setOpenAdvanced] = useState(false); - const [openAdvanced, setOpenAdvanced] = useState(false); + const [advancedDraft, setAdvancedDraft] = useState({ + tone: config.tone, + verbosity: config.verbosity, + instructions: config.instructions, + includeTableOfContents: config.includeTableOfContents, + includeTitleSlide: config.includeTitleSlide, + webSearch: config.webSearch, + }); - const [advancedDraft, setAdvancedDraft] = useState({ - tone: config.tone, - verbosity: config.verbosity, - instructions: config.instructions, - includeTableOfContents: config.includeTableOfContents, - includeTitleSlide: config.includeTitleSlide, - webSearch: config.webSearch, + const syncDraftFromConfig = () => { + setAdvancedDraft({ + tone: config.tone, + verbosity: config.verbosity, + instructions: config.instructions, + includeTableOfContents: config.includeTableOfContents, + includeTitleSlide: config.includeTitleSlide, + webSearch: config.webSearch, }); + }; - const handleOpenAdvancedChange = (open: boolean) => { - if (open) { - setAdvancedDraft({ - tone: config.tone, - verbosity: config.verbosity, - instructions: config.instructions, - includeTableOfContents: config.includeTableOfContents, - includeTitleSlide: config.includeTitleSlide, - webSearch: config.webSearch, - }); - } - setOpenAdvanced(open); + const handleOpenAdvanced = () => { + syncDraftFromConfig(); + setOpenAdvanced(true); + }; + + const handleCloseAdvanced = () => { + setOpenAdvanced(false); + }; + + const handleSaveAdvanced = () => { + onConfigChange('tone', advancedDraft.tone); + onConfigChange('verbosity', advancedDraft.verbosity); + onConfigChange('instructions', advancedDraft.instructions); + onConfigChange('includeTableOfContents', advancedDraft.includeTableOfContents); + onConfigChange('includeTitleSlide', advancedDraft.includeTitleSlide); + onConfigChange('webSearch', advancedDraft.webSearch); + setOpenAdvanced(false); + }; + + useEffect(() => { + if (!openAdvanced) { + return; + } + + const previousOverflow = document.body.style.overflow; + + const onKeyDown = (event: KeyboardEvent) => { + if (event.key === 'Escape') { + handleCloseAdvanced(); + } }; - const handleSaveAdvanced = () => { - onConfigChange("tone", advancedDraft.tone); - onConfigChange("verbosity", advancedDraft.verbosity); - onConfigChange("instructions", advancedDraft.instructions); - onConfigChange("includeTableOfContents", advancedDraft.includeTableOfContents); - onConfigChange("includeTitleSlide", advancedDraft.includeTitleSlide); - onConfigChange("webSearch", advancedDraft.webSearch); - setOpenAdvanced(false); + document.body.style.overflow = 'hidden'; + window.addEventListener('keydown', onKeyDown); + + return () => { + document.body.style.overflow = previousOverflow; + window.removeEventListener('keydown', onKeyDown); }; - return ( -
- - + +
+ + {openAdvanced && ( +
+
event.stopPropagation()} + > + + +
+
+
+

+ Advanced Settings +

+

Adjust Presentation Behavior

+
+ + - - - - - Advanced settings - + Save + +
-
- {/* Tone */} -
- -

Controls the writing style (e.g., casual, professional, funny).

- -
+
+
+ +
+ +