From f8156df6f5d73993ea4c12917b0d0693503ee1bb Mon Sep 17 00:00:00 2001 From: voidborne-d Date: Fri, 20 Mar 2026 06:08:54 +0000 Subject: [PATCH 01/11] fix: configure SQLAlchemy connection pool and dispose engines on shutdown - Add configurable pool settings via environment variables: DB_POOL_SIZE, DB_MAX_OVERFLOW, DB_POOL_TIMEOUT, DB_POOL_RECYCLE, DB_POOL_PRE_PING (defaults: 5, 10, 30s, 1800s, true) - Enable pool_pre_ping by default to detect and recycle stale connections - Add dispose_engines() called during FastAPI lifespan shutdown to release all connections back to the database - Skip pool configuration for SQLite (uses file-lock, not connection pools) - Apply changes to both servers/ and electron/ FastAPI instances Fixes #453 (stale connections exhausting pool) Fixes #454 (missing pool configuration) --- electron/servers/fastapi/api/lifespan.py | 4 ++- electron/servers/fastapi/services/database.py | 21 ++++++++++-- electron/servers/fastapi/utils/db_utils.py | 34 +++++++++++++++++++ servers/fastapi/api/lifespan.py | 4 ++- servers/fastapi/services/database.py | 21 ++++++++++-- servers/fastapi/utils/db_utils.py | 34 +++++++++++++++++++ 6 files changed, 112 insertions(+), 6 deletions(-) diff --git a/electron/servers/fastapi/api/lifespan.py b/electron/servers/fastapi/api/lifespan.py index 6fe4e6c4..1ce3e26f 100644 --- a/electron/servers/fastapi/api/lifespan.py +++ b/electron/servers/fastapi/api/lifespan.py @@ -4,7 +4,7 @@ import os from fastapi import FastAPI from migrations import migrate_database_on_startup -from services.database import create_db_and_tables +from services.database import create_db_and_tables, dispose_engines from utils.get_env import get_app_data_directory_env from utils.model_availability import ( check_llm_and_image_provider_api_or_model_availability, @@ -24,3 +24,5 @@ async def app_lifespan(_: FastAPI): await create_db_and_tables() await check_llm_and_image_provider_api_or_model_availability() yield + # Shutdown: release all database connections to prevent stale/leaked pools. + await dispose_engines() diff --git a/electron/servers/fastapi/services/database.py b/electron/servers/fastapi/services/database.py index 6149447a..b1fa842a 100644 --- a/electron/servers/fastapi/services/database.py +++ b/electron/servers/fastapi/services/database.py @@ -19,13 +19,19 @@ from models.sql.slide import SlideModel from models.sql.presentation_layout_code import PresentationLayoutCodeModel from models.sql.template import TemplateModel from models.sql.webhook_subscription import WebhookSubscription -from utils.db_utils import get_database_url_and_connect_args +from utils.db_utils import get_database_url_and_connect_args, get_pool_kwargs from utils.get_env import get_app_data_directory_env database_url, connect_args = get_database_url_and_connect_args() -sql_engine: AsyncEngine = create_async_engine(database_url, connect_args=connect_args) +# Apply connection-pool settings for server-class databases (PostgreSQL, MySQL). +# SQLite uses a file-lock model and ignores pool configuration, so we skip it. +_pool_kwargs = get_pool_kwargs() if "sqlite" not in database_url else {} + +sql_engine: AsyncEngine = create_async_engine( + database_url, connect_args=connect_args, **_pool_kwargs +) async_session_maker = async_sessionmaker(sql_engine, expire_on_commit=False) @@ -76,3 +82,14 @@ async def create_db_and_tables(): tables=[OllamaPullStatus.__table__], ) ) + + +async def dispose_engines(): + """Dispose all engine connection pools. + + Call this during application shutdown (e.g. in a FastAPI ``shutdown`` + event or lifespan context) to release every connection back to the + database and prevent stale / leaked connections. + """ + await sql_engine.dispose() + await container_db_engine.dispose() diff --git a/electron/servers/fastapi/utils/db_utils.py b/electron/servers/fastapi/utils/db_utils.py index 60b521fb..8976eb8b 100644 --- a/electron/servers/fastapi/utils/db_utils.py +++ b/electron/servers/fastapi/utils/db_utils.py @@ -4,6 +4,40 @@ from urllib.parse import urlsplit, urlunsplit, parse_qsl import ssl +def _int_env(name: str, default: int) -> int: + """Read an integer from an environment variable, falling back to *default*.""" + raw = os.getenv(name) + if raw is None: + return default + try: + return int(raw) + except ValueError: + return default + + +def get_pool_kwargs() -> dict: + """Build SQLAlchemy engine pool keyword arguments from environment variables. + + Supported variables (all optional): + DB_POOL_SIZE – max persistent connections (default 5) + DB_MAX_OVERFLOW – extra connections above pool_size (default 10) + DB_POOL_TIMEOUT – seconds to wait for a connection (default 30) + DB_POOL_RECYCLE – seconds before a connection is recycled (default 1800) + DB_POOL_PRE_PING – enable connection liveness check (default true) + + For SQLite the pool settings are not applicable and an empty dict is + returned, since SQLite uses ``StaticPool`` / ``NullPool`` by default. + """ + return { + "pool_size": _int_env("DB_POOL_SIZE", 5), + "max_overflow": _int_env("DB_MAX_OVERFLOW", 10), + "pool_timeout": _int_env("DB_POOL_TIMEOUT", 30), + "pool_recycle": _int_env("DB_POOL_RECYCLE", 1800), + "pool_pre_ping": os.getenv("DB_POOL_PRE_PING", "true").lower() + not in ("false", "0", "no"), + } + + def _ensure_sqlite_parent_dir(database_url: str) -> None: if not database_url.startswith("sqlite://"): return diff --git a/servers/fastapi/api/lifespan.py b/servers/fastapi/api/lifespan.py index 6fe4e6c4..1ce3e26f 100644 --- a/servers/fastapi/api/lifespan.py +++ b/servers/fastapi/api/lifespan.py @@ -4,7 +4,7 @@ import os from fastapi import FastAPI from migrations import migrate_database_on_startup -from services.database import create_db_and_tables +from services.database import create_db_and_tables, dispose_engines from utils.get_env import get_app_data_directory_env from utils.model_availability import ( check_llm_and_image_provider_api_or_model_availability, @@ -24,3 +24,5 @@ async def app_lifespan(_: FastAPI): await create_db_and_tables() await check_llm_and_image_provider_api_or_model_availability() yield + # Shutdown: release all database connections to prevent stale/leaked pools. + await dispose_engines() diff --git a/servers/fastapi/services/database.py b/servers/fastapi/services/database.py index 5557451a..39d8fd13 100644 --- a/servers/fastapi/services/database.py +++ b/servers/fastapi/services/database.py @@ -20,12 +20,18 @@ from models.sql.slide import SlideModel from models.sql.presentation_layout_code import PresentationLayoutCodeModel from models.sql.template import TemplateModel from models.sql.webhook_subscription import WebhookSubscription -from utils.db_utils import get_database_url_and_connect_args +from utils.db_utils import get_database_url_and_connect_args, get_pool_kwargs database_url, connect_args = get_database_url_and_connect_args() -sql_engine: AsyncEngine = create_async_engine(database_url, connect_args=connect_args) +# Apply connection-pool settings for server-class databases (PostgreSQL, MySQL). +# SQLite uses a file-lock model and ignores pool configuration, so we skip it. +_pool_kwargs = get_pool_kwargs() if "sqlite" not in database_url else {} + +sql_engine: AsyncEngine = create_async_engine( + database_url, connect_args=connect_args, **_pool_kwargs +) async_session_maker = async_sessionmaker(sql_engine, expire_on_commit=False) @@ -81,3 +87,14 @@ async def create_db_and_tables(): tables=[OllamaPullStatus.__table__], ) ) + + +async def dispose_engines(): + """Dispose all engine connection pools. + + Call this during application shutdown (e.g. in a FastAPI ``shutdown`` + event or lifespan context) to release every connection back to the + database and prevent stale / leaked connections. + """ + await sql_engine.dispose() + await container_db_engine.dispose() diff --git a/servers/fastapi/utils/db_utils.py b/servers/fastapi/utils/db_utils.py index 368740f5..1789a1a7 100644 --- a/servers/fastapi/utils/db_utils.py +++ b/servers/fastapi/utils/db_utils.py @@ -4,6 +4,40 @@ from urllib.parse import urlsplit, urlunsplit, parse_qsl import ssl +def _int_env(name: str, default: int) -> int: + """Read an integer from an environment variable, falling back to *default*.""" + raw = os.getenv(name) + if raw is None: + return default + try: + return int(raw) + except ValueError: + return default + + +def get_pool_kwargs() -> dict: + """Build SQLAlchemy engine pool keyword arguments from environment variables. + + Supported variables (all optional): + DB_POOL_SIZE – max persistent connections (default 5) + DB_MAX_OVERFLOW – extra connections above pool_size (default 10) + DB_POOL_TIMEOUT – seconds to wait for a connection (default 30) + DB_POOL_RECYCLE – seconds before a connection is recycled (default 1800) + DB_POOL_PRE_PING – enable connection liveness check (default true) + + For SQLite the pool settings are not applicable and an empty dict is + returned, since SQLite uses ``StaticPool`` / ``NullPool`` by default. + """ + return { + "pool_size": _int_env("DB_POOL_SIZE", 5), + "max_overflow": _int_env("DB_MAX_OVERFLOW", 10), + "pool_timeout": _int_env("DB_POOL_TIMEOUT", 30), + "pool_recycle": _int_env("DB_POOL_RECYCLE", 1800), + "pool_pre_ping": os.getenv("DB_POOL_PRE_PING", "true").lower() + not in ("false", "0", "no"), + } + + def get_database_url_and_connect_args() -> tuple[str, dict]: database_url = get_database_url_env() or "sqlite:///" + os.path.join( get_app_data_directory_env() or "/tmp/presenton", "fastapi.db" From f2703ec0034f0334ed4d3c0072b7e5f2b0232868 Mon Sep 17 00:00:00 2001 From: Christopher Quenneville Date: Sun, 5 Apr 2026 12:27:45 -0500 Subject: [PATCH 02/11] feat: add Open WebUI as image generation provider Add native support for Open WebUI's image generation API as a new image provider option. Open WebUI exposes an OpenAI-like /v1/images/generations endpoint but with key differences that require special handling: - Response is a bare JSON array instead of {"data": [...]} - Image URLs are relative paths (e.g. /api/v1/files/.../content) - File downloads require the same Bearer auth token The implementation uses raw HTTP calls via aiohttp rather than the OpenAI SDK to handle these differences. No model parameter is sent since Open WebUI manages the image model in its own admin settings. Backend changes: - New OPEN_WEBUI enum value in ImageProvider - generate_image_open_webui() method in ImageGenerationService - Environment getters/setters for OPEN_WEBUI_IMAGE_URL and OPEN_WEBUI_IMAGE_API_KEY - UserConfig model and config loading/saving pipeline updated Frontend changes: - New "Open WebUI" option in image provider dropdown - Settings UI with URL and optional API key fields - Validation, field mappings, and config persistence Docker: - OPEN_WEBUI_IMAGE_URL and OPEN_WEBUI_IMAGE_API_KEY added to all docker-compose service definitions Co-Authored-By: Claude Opus 4.6 (1M context) --- docker-compose.yml | 10 ++- servers/fastapi/enums/image_provider.py | 1 + servers/fastapi/models/user_config.py | 4 + .../services/image_generation_service.py | 87 +++++++++++++++++++ servers/fastapi/utils/get_env.py | 9 ++ servers/fastapi/utils/image_provider.py | 4 + servers/fastapi/utils/set_env.py | 9 ++ servers/fastapi/utils/user_config.py | 10 +++ .../(dashboard)/settings/ImageProvider.tsx | 52 +++++++++++ servers/nextjs/app/api/user-config/route.ts | 4 + .../components/ImageSelectionConfig.tsx | 50 +++++++++++ servers/nextjs/types/llm_config.ts | 4 + servers/nextjs/utils/providerConstants.ts | 9 ++ servers/nextjs/utils/providerUtils.ts | 2 + servers/nextjs/utils/storeHelpers.ts | 5 ++ 15 files changed, 259 insertions(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 05c189f8..9698ed29 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -36,6 +36,8 @@ services: - DISABLE_ANONYMOUS_TRACKING=${DISABLE_ANONYMOUS_TRACKING} - COMFYUI_URL=${COMFYUI_URL} - COMFYUI_WORKFLOW=${COMFYUI_WORKFLOW} + - OPEN_WEBUI_IMAGE_URL=${OPEN_WEBUI_IMAGE_URL} + - OPEN_WEBUI_IMAGE_API_KEY=${OPEN_WEBUI_IMAGE_API_KEY} production-gpu: # image: ghcr.io/presenton/presenton:latest @@ -81,7 +83,9 @@ services: - DISABLE_ANONYMOUS_TRACKING=${DISABLE_ANONYMOUS_TRACKING} - COMFYUI_URL=${COMFYUI_URL} - COMFYUI_WORKFLOW=${COMFYUI_WORKFLOW} - + - OPEN_WEBUI_IMAGE_URL=${OPEN_WEBUI_IMAGE_URL} + - OPEN_WEBUI_IMAGE_API_KEY=${OPEN_WEBUI_IMAGE_API_KEY} + development: build: context: . @@ -118,6 +122,8 @@ services: - DISABLE_ANONYMOUS_TRACKING=${DISABLE_ANONYMOUS_TRACKING} - COMFYUI_URL=${COMFYUI_URL} - COMFYUI_WORKFLOW=${COMFYUI_WORKFLOW} + - OPEN_WEBUI_IMAGE_URL=${OPEN_WEBUI_IMAGE_URL} + - OPEN_WEBUI_IMAGE_API_KEY=${OPEN_WEBUI_IMAGE_API_KEY} development-gpu: build: @@ -162,3 +168,5 @@ services: - DISABLE_ANONYMOUS_TRACKING=${DISABLE_ANONYMOUS_TRACKING} - COMFYUI_URL=${COMFYUI_URL} - COMFYUI_WORKFLOW=${COMFYUI_WORKFLOW} + - OPEN_WEBUI_IMAGE_URL=${OPEN_WEBUI_IMAGE_URL} + - OPEN_WEBUI_IMAGE_API_KEY=${OPEN_WEBUI_IMAGE_API_KEY} diff --git a/servers/fastapi/enums/image_provider.py b/servers/fastapi/enums/image_provider.py index 9d773ad5..76312b73 100644 --- a/servers/fastapi/enums/image_provider.py +++ b/servers/fastapi/enums/image_provider.py @@ -9,3 +9,4 @@ class ImageProvider(Enum): DALLE3 = "dall-e-3" GPT_IMAGE_1_5 = "gpt-image-1.5" COMFYUI = "comfyui" + OPEN_WEBUI = "open_webui" diff --git a/servers/fastapi/models/user_config.py b/servers/fastapi/models/user_config.py index c26a6cb0..8a1b4249 100644 --- a/servers/fastapi/models/user_config.py +++ b/servers/fastapi/models/user_config.py @@ -36,6 +36,10 @@ class UserConfig(BaseModel): COMFYUI_URL: Optional[str] = None COMFYUI_WORKFLOW: Optional[str] = None + # Open WebUI Image Provider + OPEN_WEBUI_IMAGE_URL: Optional[str] = None + OPEN_WEBUI_IMAGE_API_KEY: Optional[str] = None + # Dalle 3 Quality DALL_E_3_QUALITY: Optional[str] = None # Gpt Image 1.5 Quality diff --git a/servers/fastapi/services/image_generation_service.py b/servers/fastapi/services/image_generation_service.py index f9ec1202..93255e36 100644 --- a/servers/fastapi/services/image_generation_service.py +++ b/servers/fastapi/services/image_generation_service.py @@ -12,6 +12,8 @@ from utils.get_env import ( get_dall_e_3_quality_env, get_gpt_image_1_5_quality_env, get_pexels_api_key_env, + get_open_webui_image_url_env, + get_open_webui_image_api_key_env, ) from utils.get_env import get_pixabay_api_key_env from utils.get_env import get_comfyui_url_env @@ -25,6 +27,7 @@ from utils.image_provider import ( is_nanobanana_pro_selected, is_dalle3_selected, is_comfyui_selected, + is_open_webui_selected, ) import uuid @@ -53,6 +56,8 @@ class ImageGenerationService: return self.generate_image_openai_gpt_image_1_5 elif is_comfyui_selected(): return self.generate_image_comfyui + elif is_open_webui_selected(): + return self.generate_image_open_webui return None def is_stock_provider_selected(self): @@ -141,6 +146,88 @@ class ImageGenerationService: get_gpt_image_1_5_quality_env() or "medium", ) + async def generate_image_open_webui( + self, prompt: str, output_directory: str + ) -> str: + base_url = get_open_webui_image_url_env() + if not base_url: + raise ValueError("OPEN_WEBUI_IMAGE_URL environment variable is not set") + + base_url = base_url.rstrip("/") + api_key = get_open_webui_image_api_key_env() or "" + + from urllib.parse import urlparse + + parsed = urlparse(base_url) + origin = f"{parsed.scheme}://{parsed.netloc}" + + headers = {"Content-Type": "application/json"} + if api_key: + headers["Authorization"] = f"Bearer {api_key}" + + payload = { + "prompt": prompt, + "n": 1, + "size": "1024x1024", + } + + async with aiohttp.ClientSession(trust_env=True) as session: + resp = await session.post( + f"{base_url}/images/generations", + json=payload, + headers=headers, + timeout=aiohttp.ClientTimeout(total=300), + ) + + if resp.status != 200: + error_text = await resp.text() + raise Exception( + f"Open WebUI image generation returned {resp.status}: {error_text}" + ) + + body = await resp.json() + + # Open WebUI returns a bare [...] array instead of {"data": [...]}. + if isinstance(body, list): + items = body + elif isinstance(body, dict) and "data" in body: + items = body["data"] + else: + raise Exception(f"Unexpected response format: {type(body)}") + + if not items: + raise Exception("Open WebUI returned empty results") + + item = items[0] + image_path = os.path.join(output_directory, f"{uuid.uuid4()}.png") + + if item.get("b64_json"): + with open(image_path, "wb") as f: + f.write(base64.b64decode(item["b64_json"])) + elif item.get("url"): + image_url = item["url"] + # Open WebUI returns relative URLs like /api/v1/files/.../content + if image_url.startswith("/"): + image_url = origin + image_url + dl_headers = {} + if api_key: + dl_headers["Authorization"] = f"Bearer {api_key}" + dl_resp = await session.get( + image_url, + headers=dl_headers, + timeout=aiohttp.ClientTimeout(total=120), + ) + if dl_resp.status != 200: + raise Exception( + f"Failed to download image: {dl_resp.status}" + ) + with open(image_path, "wb") as f: + f.write(await dl_resp.read()) + else: + raise Exception("Open WebUI returned no image data") + + return image_path + async def _generate_image_google( self, prompt: str, output_directory: str, model: str ) -> str: diff --git a/servers/fastapi/utils/get_env.py b/servers/fastapi/utils/get_env.py index 74cf2e1f..be8d31b4 100644 --- a/servers/fastapi/utils/get_env.py +++ b/servers/fastapi/utils/get_env.py @@ -142,3 +142,12 @@ def get_codex_model_env(): def get_migrate_database_on_startup_env(): return os.getenv("MIGRATE_DATABASE_ON_STARTUP") + + +# Open WebUI Image Provider +def get_open_webui_image_url_env(): + return os.getenv("OPEN_WEBUI_IMAGE_URL") + + +def get_open_webui_image_api_key_env(): + return os.getenv("OPEN_WEBUI_IMAGE_API_KEY") diff --git a/servers/fastapi/utils/image_provider.py b/servers/fastapi/utils/image_provider.py index 15469709..cb3525e0 100644 --- a/servers/fastapi/utils/image_provider.py +++ b/servers/fastapi/utils/image_provider.py @@ -38,6 +38,10 @@ def is_comfyui_selected() -> bool: return ImageProvider.COMFYUI == get_selected_image_provider() +def is_open_webui_selected() -> bool: + return ImageProvider.OPEN_WEBUI == get_selected_image_provider() + + def get_selected_image_provider() -> ImageProvider | None: """ Get the selected image provider from environment variables. diff --git a/servers/fastapi/utils/set_env.py b/servers/fastapi/utils/set_env.py index 6f26b34f..52c1278d 100644 --- a/servers/fastapi/utils/set_env.py +++ b/servers/fastapi/utils/set_env.py @@ -124,3 +124,12 @@ def set_codex_account_id_env(value: str): def set_codex_model_env(value: str): os.environ["CODEX_MODEL"] = value + + +# Open WebUI Image Provider +def set_open_webui_image_url_env(value: str): + os.environ["OPEN_WEBUI_IMAGE_URL"] = value + + +def set_open_webui_image_api_key_env(value: str): + os.environ["OPEN_WEBUI_IMAGE_API_KEY"] = value diff --git a/servers/fastapi/utils/user_config.py b/servers/fastapi/utils/user_config.py index f83d3047..a050163d 100644 --- a/servers/fastapi/utils/user_config.py +++ b/servers/fastapi/utils/user_config.py @@ -33,6 +33,8 @@ from utils.get_env import ( get_codex_token_expires_env, get_codex_account_id_env, get_codex_model_env, + get_open_webui_image_url_env, + get_open_webui_image_api_key_env, ) from utils.parsers import parse_bool_or_none from utils.set_env import ( @@ -65,6 +67,8 @@ from utils.set_env import ( set_codex_token_expires_env, set_codex_account_id_env, set_codex_model_env, + set_open_webui_image_url_env, + set_open_webui_image_api_key_env, ) @@ -133,6 +137,8 @@ def get_user_config(): CODEX_REFRESH_TOKEN=existing_config.CODEX_REFRESH_TOKEN or get_codex_refresh_token_env(), CODEX_TOKEN_EXPIRES=existing_config.CODEX_TOKEN_EXPIRES or get_codex_token_expires_env(), CODEX_ACCOUNT_ID=existing_config.CODEX_ACCOUNT_ID or get_codex_account_id_env(), + OPEN_WEBUI_IMAGE_URL=existing_config.OPEN_WEBUI_IMAGE_URL or get_open_webui_image_url_env(), + OPEN_WEBUI_IMAGE_API_KEY=existing_config.OPEN_WEBUI_IMAGE_API_KEY or get_open_webui_image_api_key_env(), ) @@ -196,6 +202,10 @@ def update_env_with_user_config(): set_codex_token_expires_env(user_config.CODEX_TOKEN_EXPIRES) if user_config.CODEX_ACCOUNT_ID: set_codex_account_id_env(user_config.CODEX_ACCOUNT_ID) + if user_config.OPEN_WEBUI_IMAGE_URL: + set_open_webui_image_url_env(user_config.OPEN_WEBUI_IMAGE_URL) + if user_config.OPEN_WEBUI_IMAGE_API_KEY: + set_open_webui_image_api_key_env(user_config.OPEN_WEBUI_IMAGE_API_KEY) def save_codex_tokens_to_user_config() -> None: diff --git a/servers/nextjs/app/(presentation-generator)/(dashboard)/settings/ImageProvider.tsx b/servers/nextjs/app/(presentation-generator)/(dashboard)/settings/ImageProvider.tsx index 31bc11ea..59e8a21c 100644 --- a/servers/nextjs/app/(presentation-generator)/(dashboard)/settings/ImageProvider.tsx +++ b/servers/nextjs/app/(presentation-generator)/(dashboard)/settings/ImageProvider.tsx @@ -262,6 +262,33 @@ const ImageProvider = ({ llmConfig, setLlmConfig }: { llmConfig: LLMConfig, setL ); } + // Show Open WebUI configuration + if (provider.value === "open_webui") { + return ( +
+
+ +
+ { + input_field_changed( + e.target.value, + "OPEN_WEBUI_IMAGE_URL" + ); + }} + /> +
+
+
+ ); + } + // Show API key input for other providers return (
@@ -300,6 +327,31 @@ const ImageProvider = ({ llmConfig, setLlmConfig }: { llmConfig: LLMConfig, setL {!isImageGenerationDisabled &&
{renderQualitySelector(llmConfig, input_field_changed)} + {llmConfig.IMAGE_PROVIDER === "open_webui" && ( +
+ +
+ { + input_field_changed(e.target.value, "OPEN_WEBUI_IMAGE_API_KEY"); + }} + /> + +
+
+ )} {llmConfig.IMAGE_PROVIDER === "comfyui" &&
-

Allow the model to consult the web for fresher facts.

+

Allow the model to consult the web for fresher facts. Turn on and click Save—this toggle alone controls web search for this deck.

{/* Instructions */} diff --git a/electron/servers/nextjs/app/(presentation-generator)/upload/components/ConfigurationSelects.tsx b/electron/servers/nextjs/app/(presentation-generator)/upload/components/ConfigurationSelects.tsx index e2040136..b56eb286 100644 --- a/electron/servers/nextjs/app/(presentation-generator)/upload/components/ConfigurationSelects.tsx +++ b/electron/servers/nextjs/app/(presentation-generator)/upload/components/ConfigurationSelects.tsx @@ -375,7 +375,7 @@ export function ConfigurationSelects({ onCheckedChange={(checked) => setAdvancedDraft((prev) => ({ ...prev, webSearch: checked }))} />
-

Allow the model to consult the web for fresher facts.

+

Allow the model to consult the web for fresher facts. Turn on and click Save—this toggle alone controls web search for this deck.

{/* Instructions */} From 22c43569556f0846a29abbf00322a513753e5030 Mon Sep 17 00:00:00 2001 From: sudipnext Date: Thu, 16 Apr 2026 14:48:05 +0545 Subject: [PATCH 06/11] fix: update Codex model versions and default settings - Changed DEFAULT_CODEX_MODEL to "gpt-5.1-codex-mini" in multiple files. - Updated CHATGPT_MODELS to reflect the new model naming conventions and removed deprecated entries. --- electron/servers/fastapi/constants/llm.py | 2 +- .../(dashboard)/settings/SettingCodex.tsx | 14 +++++++------- electron/servers/nextjs/components/CodexConfig.tsx | 14 +++++++------- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/electron/servers/fastapi/constants/llm.py b/electron/servers/fastapi/constants/llm.py index 21eacb73..3f663f6c 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.2-codex" +DEFAULT_CODEX_MODEL = "gpt-5.1-codex-mini" 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 8b4e8b9a..756e4317 100644 --- a/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/settings/SettingCodex.tsx +++ b/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/settings/SettingCodex.tsx @@ -46,16 +46,16 @@ interface CodexModel { } const CHATGPT_MODELS: CodexModel[] = [ - { id: "gpt-5.1", name: "GPT-5.1" }, - { id: "gpt-5.1-codex-max", name: "GPT-5.1 Codex Max" }, - { id: "gpt-5.2", name: "GPT-5.2" }, - { id: "gpt-5.2-codex", name: "GPT-5.2 Codex" }, - { id: "gpt-5.3-codex", name: "GPT-5.3 Codex" }, - { id: "gpt-5.4-mini", name: "GPT-5.4 Mini" }, { id: "gpt-5.4", name: "GPT-5.4" }, + { id: "gpt-5.2-codex", name: "GPT-5.2-Codex" }, + { id: "gpt-5.1-codex-max", name: "GPT-5.1-Codex-Max" }, + { 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.4-mini"; +const DEFAULT_CODEX_MODEL = "gpt-5.1-codex-mini"; export default function CodexConfig({ codexModel, diff --git a/electron/servers/nextjs/components/CodexConfig.tsx b/electron/servers/nextjs/components/CodexConfig.tsx index 3f6453d2..da56a161 100644 --- a/electron/servers/nextjs/components/CodexConfig.tsx +++ b/electron/servers/nextjs/components/CodexConfig.tsx @@ -33,16 +33,16 @@ interface CodexModel { } export const CHATGPT_MODELS: CodexModel[] = [ - { id: "gpt-5.1", name: "GPT-5.1" }, - { id: "gpt-5.1-codex-max", name: "GPT-5.1 Codex Max" }, - { id: "gpt-5.2", name: "GPT-5.2" }, - { id: "gpt-5.2-codex", name: "GPT-5.2 Codex" }, - { id: "gpt-5.3-codex", name: "GPT-5.3 Codex" }, - { id: "gpt-5.4 mini", name: "GPT-5.4 Mini" }, { id: "gpt-5.4", name: "GPT-5.4" }, + { id: "gpt-5.2-codex", name: "GPT-5.2-Codex" }, + { id: "gpt-5.1-codex-max", name: "GPT-5.1-Codex-Max" }, + { 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" }, ]; -export const DEFAULT_CODEX_MODEL = "gpt-5.4-mini"; +export const DEFAULT_CODEX_MODEL = "gpt-5.1-codex-mini"; export default function CodexConfig({ codexModel, From 040c6198895e5b0b27963ef5f6703b3aeeb72473 Mon Sep 17 00:00:00 2001 From: sudipnext Date: Thu, 16 Apr 2026 20:11:25 +0545 Subject: [PATCH 07/11] fix: refine web search handling in LLM client and presentation outline generation - Updated LLMClient to include CODEX in web search checks. - Simplified web search logic in generate_ppt_outline to improve clarity and efficiency. - Ensured consistent usage of web search settings across methods. --- electron/servers/fastapi/services/llm_client.py | 10 +++++++--- .../llm_calls/generate_presentation_outlines.py | 8 ++++---- servers/fastapi/services/llm_client.py | 1 + .../llm_calls/generate_presentation_outlines.py | 17 ++++++++++++----- 4 files changed, 24 insertions(+), 12 deletions(-) diff --git a/electron/servers/fastapi/services/llm_client.py b/electron/servers/fastapi/services/llm_client.py index 6f5c7bac..63e39bb9 100644 --- a/electron/servers/fastapi/services/llm_client.py +++ b/electron/servers/fastapi/services/llm_client.py @@ -109,20 +109,24 @@ class LLMClient: """ if not web_search: return False - if self.llm_provider in (LLMProvider.OLLAMA, LLMProvider.CUSTOM): + if self.llm_provider in ( + LLMProvider.OLLAMA, + LLMProvider.CUSTOM, + LLMProvider.CODEX, + ): return False return True def outline_uses_prefetched_web_facts(self, web_search: bool) -> bool: """Chat Completions + json_schema rarely invoke custom function tools. - For OpenAI and Codex we prefetch via the Responses API (``web_search_preview``) + For OpenAI we can prefetch via the Responses API (``web_search_preview``) and attach the result as context so Advanced settings **Web search** still grounds outlines without relying on ``SearchWebTool`` in the same call. """ if not self.web_search_enabled_for_request(web_search): return False - return self.llm_provider in (LLMProvider.OPENAI, LLMProvider.CODEX) + return self.llm_provider == LLMProvider.OPENAI async def prefetch_outline_web_facts( self, diff --git a/electron/servers/fastapi/utils/llm_calls/generate_presentation_outlines.py b/electron/servers/fastapi/utils/llm_calls/generate_presentation_outlines.py index 3d0d1d49..d9acb5b1 100644 --- a/electron/servers/fastapi/utils/llm_calls/generate_presentation_outlines.py +++ b/electron/servers/fastapi/utils/llm_calls/generate_presentation_outlines.py @@ -196,6 +196,7 @@ async def generate_ppt_outline( ) client = LLMClient() + web_search_enabled = client.web_search_enabled_for_request(web_search) merged_context = additional_context if client.outline_uses_prefetched_web_facts(web_search): @@ -207,9 +208,8 @@ async def generate_ppt_outline( else f"## Web research (current sources)\n{facts}" ) - use_search_tool = ( - client.web_search_enabled_for_request(web_search) - and not client.outline_uses_prefetched_web_facts(web_search) + use_search_tool = web_search_enabled and not client.outline_uses_prefetched_web_facts( + web_search ) try: @@ -225,7 +225,7 @@ async def generate_ppt_outline( instructions, include_title_slide, include_table_of_contents, - web_search=bool(web_search), + web_search=web_search_enabled, ), response_model.model_json_schema(), strict=True, diff --git a/servers/fastapi/services/llm_client.py b/servers/fastapi/services/llm_client.py index 355b544b..53460edf 100644 --- a/servers/fastapi/services/llm_client.py +++ b/servers/fastapi/services/llm_client.py @@ -91,6 +91,7 @@ class LLMClient: if ( self.llm_provider == LLMProvider.OLLAMA or self.llm_provider == LLMProvider.CUSTOM + or self.llm_provider == LLMProvider.CODEX ): return False return parse_bool_or_none(get_web_grounding_env()) or False diff --git a/servers/fastapi/utils/llm_calls/generate_presentation_outlines.py b/servers/fastapi/utils/llm_calls/generate_presentation_outlines.py index cb044d4c..4a6c3d2c 100644 --- a/servers/fastapi/utils/llm_calls/generate_presentation_outlines.py +++ b/servers/fastapi/utils/llm_calls/generate_presentation_outlines.py @@ -1,6 +1,7 @@ from datetime import datetime from typing import Optional +from enums.llm_provider import LLMProvider from models.llm_message import LLMSystemMessage, LLMUserMessage from models.llm_tools import SearchWebTool from services.llm_client import LLMClient @@ -97,6 +98,16 @@ async def generate_ppt_outline( response_model = get_presentation_outline_model_with_n_slides(n_slides) client = LLMClient() + providers_with_search_tool = { + LLMProvider.OPENAI, + LLMProvider.ANTHROPIC, + LLMProvider.GOOGLE, + } + use_search_tool = ( + web_search + and client.enable_web_grounding() + and client.llm_provider in providers_with_search_tool + ) try: async for chunk in client.stream_structured( @@ -113,11 +124,7 @@ async def generate_ppt_outline( ), response_model.model_json_schema(), strict=True, - tools=( - [SearchWebTool] - if (client.enable_web_grounding() and web_search) - else None - ), + tools=([SearchWebTool] if use_search_tool else None), ): yield chunk except Exception as e: From b2cb7343561a455f6631e50ea04d585853ce7c1f Mon Sep 17 00:00:00 2001 From: sudipnext Date: Thu, 16 Apr 2026 20:12:18 +0545 Subject: [PATCH 08/11] chore: bump version to 0.7.3-beta in package.json --- electron/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/electron/package.json b/electron/package.json index ffef3c6e..7a474098 100644 --- a/electron/package.json +++ b/electron/package.json @@ -1,7 +1,7 @@ { "name": "presenton", "productName": "Presenton Open Source", - "version": "0.7.2-beta", + "version": "0.7.3-beta", "exportVersion": "v0.2.0", "main": "app_dist/main.js", "description": "Open-Source AI Presentation Generator", From 68d5d844ee6d6847c7d2d3237d1457e7c691b937 Mon Sep 17 00:00:00 2001 From: sudipnext Date: Thu, 16 Apr 2026 20:13:28 +0545 Subject: [PATCH 09/11] chore: update exportVersion to v0.2.2 in package.json --- electron/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/electron/package.json b/electron/package.json index 7a474098..47c3c16a 100644 --- a/electron/package.json +++ b/electron/package.json @@ -2,7 +2,7 @@ "name": "presenton", "productName": "Presenton Open Source", "version": "0.7.3-beta", - "exportVersion": "v0.2.0", + "exportVersion": "v0.2.2", "main": "app_dist/main.js", "description": "Open-Source AI Presentation Generator", "homepage": "https://presenton.ai", From 72d3a71e410c9f0a8d796263eb210c68e8a6f69d Mon Sep 17 00:00:00 2001 From: sudipnext Date: Thu, 16 Apr 2026 20:51:10 +0545 Subject: [PATCH 10/11] chore: update version to 0.7.3-beta in package-lock.json and fix ImageMagick download URL for Windows --- electron/app/utils/imagemagick-check.ts | 2 +- electron/package-lock.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/electron/app/utils/imagemagick-check.ts b/electron/app/utils/imagemagick-check.ts index 34985a74..bb3e3e60 100644 --- a/electron/app/utils/imagemagick-check.ts +++ b/electron/app/utils/imagemagick-check.ts @@ -173,7 +173,7 @@ export function getImageMagickBinaryPath(): string { export function getImageMagickDownloadUrl(): string { if (process.platform === "win32") { - return "https://imagemagick.org/archive/binaries/ImageMagick-7.1.2-18-Q16-HDRI-x64-dll.exe"; + return "https://github.com/ImageMagick/ImageMagick/releases/download/7.1.2-18/ImageMagick-7.1.2-18-Q16-HDRI-x64-dll.exe"; } if (process.platform === "darwin") { return "https://brew.sh/"; diff --git a/electron/package-lock.json b/electron/package-lock.json index 75616258..86b36cd1 100644 --- a/electron/package-lock.json +++ b/electron/package-lock.json @@ -1,12 +1,12 @@ { "name": "presenton", - "version": "0.7.2-beta", + "version": "0.7.3-beta", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "presenton", - "version": "0.7.2-beta", + "version": "0.7.3-beta", "hasInstallScript": true, "dependencies": { "@llamaindex/liteparse": "^1.4.0", From 4226682e0bcd9d1f3991649279908308864f4c6f Mon Sep 17 00:00:00 2001 From: sudipnext Date: Thu, 16 Apr 2026 21:33:54 +0545 Subject: [PATCH 11/11] chore: update version to 0.7.3-beta and enhance content generation features - Updated version to 0.7.3-beta with improvements in slide content generation and web search reliability. - Fixed console window flashing during export tasks on Windows. - Various minor fixes and stability improvements across the app. --- electron/version.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/electron/version.json b/electron/version.json index aedbe8c9..ffadbbe7 100644 --- a/electron/version.json +++ b/electron/version.json @@ -1,9 +1,9 @@ { - "version": "0.7.2-beta", - "message": "What's New\n\n:repeat: Smarter Streaming & Retries\n- Outline and slide generation now retries automatically on failure - fewer interrupted generations, smoother experience end to end\n\n:frame_photo: ComfyUI Fix\n- Image generation via ComfyUI is back to working correctly - mid-generation failures resolved\n\n:art: UI & Template Polish\n- Continued refinements to UI components - tighter layouts, cleaner interactions\n- Template improvements - more consistent rendering across providers\n\n:bar_chart: Better Analytics & Error Tracking\n- Sentry integrated for crash and error monitoring - helps us catch and fix issues faster\n\n:wrench: Fixes\n- Download URL and version message corrected in version.json\n- Various stability and content fixes across the board", + "version": "0.7.3-beta", + "message": "Presenton Desktop electron-v0.7.3-beta\n\nSmarter content generation, reliable web search, no more shady Windows popups, and a round of minor fixes under the hood. Clean update. 🙌\n\nWhat's New\n\n🧠 Smarter Slide Content Generation\n• Overflow mitigation loop added — slides no longer clip or overflow when content runs long\n• Improved system prompt for slide content generation — cleaner, better-fitting output every time\n\n🔍 Web Search Fixed\n• Web search is back to working reliably during presentation generation\n\n🪟 Windows Fix\n• Export tasks no longer flash a console window on Windows — cleaner, more polished experience\n\n🔧 Minor Fixes\n• Various small fixes and stability improvements across the app\n\n---\nView full diff: electron-v0.7.2-beta → electron-v0.7.3-beta\nhttps://github.com/presenton/presenton/compare/electron-v0.7.2-beta...electron-v0.7.3-beta\n\nInstallation\nDownload Link: https://presenton.ai/download\nLove the app? Star us on GitHub → github.com/presenton/presenton", "downloads": { - "linux": "https://github.com/presenton/presenton/releases/download/electron-v0.7.2-beta/Presenton-0.7.2-beta.deb", - "mac": "https://github.com/presenton/presenton/releases/download/electron-v0.7.2-beta/Presenton-0.7.2-beta.dmg", - "windows": "https://github.com/presenton/presenton/releases/download/electron-v0.7.2-beta/Presenton-0.7.2-beta.exe" + "linux": "https://github.com/presenton/presenton/releases/download/electron-v0.7.3-beta/Presenton-0.7.3-beta.deb", + "mac": "https://github.com/presenton/presenton/releases/download/electron-v0.7.3-beta/Presenton-0.7.3-beta.dmg", + "windows": "https://github.com/presenton/presenton/releases/download/electron-v0.7.3-beta/Presenton-0.7.3-beta.exe" } -} \ No newline at end of file +}