From 13c532cb018bbb9b5890b145abdb4842b046fa29 Mon Sep 17 00:00:00 2001 From: Ahtisahm Shahid Date: Mon, 8 Jun 2026 19:55:33 +0500 Subject: [PATCH 01/10] fix: remove app update popup from desktop app Removed the update checker integration from the Electron app by dropping the import and all calls to startUpdateChecker/stopUpdateChecker in main.ts so the update banner no longer appears. --- electron/app/main.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/electron/app/main.ts b/electron/app/main.ts index efe7da7af..e11c344df 100644 --- a/electron/app/main.ts +++ b/electron/app/main.ts @@ -15,7 +15,6 @@ import { getSofficePath, isLibreOfficeInstalled } from "./utils/libreoffice-chec import { getPuppeteerExecutablePath, isChromeInstalled } from "./utils/puppeteer-check"; import { getLiteParseRunnerPath } from "./utils/liteparse-check"; import { getImageMagickBinaryPath, isImageMagickInstalled } from "./utils/imagemagick-check"; -import { startUpdateChecker, stopUpdateChecker } from "./utils/update-checker"; import { initMainSentry } from "./sentry/main"; @@ -226,7 +225,6 @@ async function stopServers() { async function forceQuitApp(exitCode = 0) { if (isStopping) return; isStopping = true; - stopUpdateChecker(); try { await stopServers(); } finally { @@ -326,11 +324,6 @@ app.whenReady().then(async () => { await startServers(fastApiPort, nextjsPort); win?.loadURL(`${localhost}:${nextjsPort}`); - // Begin polling the version server for available updates - if (win) { - process.stderr.write("[Presenton] Starting update checker...\n"); - startUpdateChecker(win); - } }); app.on("window-all-closed", async () => { From 76daa07565b0e5aff05c540a28066576498903f5 Mon Sep 17 00:00:00 2001 From: Ahtisahm Shahid Date: Mon, 8 Jun 2026 22:21:15 +0500 Subject: [PATCH 02/10] fix: return user-friendly message for OpenAI budget_exceeded errors When the OpenAI API key runs out of budget it returns a 400 with type=budget_exceeded. The error handler was forwarding the raw OpenAI error string to the UI. Now detect that specific error type and return a clean 402 with an actionable message instead. Applied to both web app and Electron desktop app FastAPI servers. --- .../servers/fastapi/utils/llm_client_error_handler.py | 11 +++++++++++ servers/fastapi/utils/llm_client_error_handler.py | 11 +++++++++++ 2 files changed, 22 insertions(+) diff --git a/electron/servers/fastapi/utils/llm_client_error_handler.py b/electron/servers/fastapi/utils/llm_client_error_handler.py index 7e4c915b1..748870285 100644 --- a/electron/servers/fastapi/utils/llm_client_error_handler.py +++ b/electron/servers/fastapi/utils/llm_client_error_handler.py @@ -8,6 +8,17 @@ def handle_llm_client_exceptions(e: Exception) -> HTTPException: traceback.print_exc() if isinstance(e, OpenAIAPIError): + error_body = getattr(e, "body", None) or {} + error_type = ( + error_body.get("error", {}).get("type", "") + if isinstance(error_body, dict) + else "" + ) + if error_type == "budget_exceeded" or "ExceededBudget" in str(getattr(e, "message", "")): + return HTTPException( + status_code=402, + detail="Your API key has exceeded its budget limit. Please add credits to your OpenAI account to continue.", + ) return HTTPException(status_code=500, detail=f"OpenAI API error: {e.message}") if isinstance(e, GoogleAPIError): return HTTPException(status_code=500, detail=f"Google API error: {e.message}") diff --git a/servers/fastapi/utils/llm_client_error_handler.py b/servers/fastapi/utils/llm_client_error_handler.py index 7e4c915b1..748870285 100644 --- a/servers/fastapi/utils/llm_client_error_handler.py +++ b/servers/fastapi/utils/llm_client_error_handler.py @@ -8,6 +8,17 @@ def handle_llm_client_exceptions(e: Exception) -> HTTPException: traceback.print_exc() if isinstance(e, OpenAIAPIError): + error_body = getattr(e, "body", None) or {} + error_type = ( + error_body.get("error", {}).get("type", "") + if isinstance(error_body, dict) + else "" + ) + if error_type == "budget_exceeded" or "ExceededBudget" in str(getattr(e, "message", "")): + return HTTPException( + status_code=402, + detail="Your API key has exceeded its budget limit. Please add credits to your OpenAI account to continue.", + ) return HTTPException(status_code=500, detail=f"OpenAI API error: {e.message}") if isinstance(e, GoogleAPIError): return HTTPException(status_code=500, detail=f"Google API error: {e.message}") From 867c4bcbdd89a91ee7bfa11ac811b535067d3948 Mon Sep 17 00:00:00 2001 From: Ahtisahm Shahid Date: Tue, 9 Jun 2026 11:58:09 +0500 Subject: [PATCH 03/10] fix: refine budget exceeded error message wording --- electron/servers/fastapi/utils/llm_client_error_handler.py | 4 ++-- servers/fastapi/utils/llm_client_error_handler.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/electron/servers/fastapi/utils/llm_client_error_handler.py b/electron/servers/fastapi/utils/llm_client_error_handler.py index 748870285..787ed1f60 100644 --- a/electron/servers/fastapi/utils/llm_client_error_handler.py +++ b/electron/servers/fastapi/utils/llm_client_error_handler.py @@ -17,9 +17,9 @@ def handle_llm_client_exceptions(e: Exception) -> HTTPException: if error_type == "budget_exceeded" or "ExceededBudget" in str(getattr(e, "message", "")): return HTTPException( status_code=402, - detail="Your API key has exceeded its budget limit. Please add credits to your OpenAI account to continue.", + detail="Your API key has exceeded its budget limit. Please add credits to your account to continue.", ) - return HTTPException(status_code=500, detail=f"OpenAI API error: {e.message}") + return HTTPException(status_code=500, detail=f"API error: {e.message}") if isinstance(e, GoogleAPIError): return HTTPException(status_code=500, detail=f"Google API error: {e.message}") if isinstance(e, AnthropicAPIError): diff --git a/servers/fastapi/utils/llm_client_error_handler.py b/servers/fastapi/utils/llm_client_error_handler.py index 748870285..787ed1f60 100644 --- a/servers/fastapi/utils/llm_client_error_handler.py +++ b/servers/fastapi/utils/llm_client_error_handler.py @@ -17,9 +17,9 @@ def handle_llm_client_exceptions(e: Exception) -> HTTPException: if error_type == "budget_exceeded" or "ExceededBudget" in str(getattr(e, "message", "")): return HTTPException( status_code=402, - detail="Your API key has exceeded its budget limit. Please add credits to your OpenAI account to continue.", + detail="Your API key has exceeded its budget limit. Please add credits to your account to continue.", ) - return HTTPException(status_code=500, detail=f"OpenAI API error: {e.message}") + return HTTPException(status_code=500, detail=f"API error: {e.message}") if isinstance(e, GoogleAPIError): return HTTPException(status_code=500, detail=f"Google API error: {e.message}") if isinstance(e, AnthropicAPIError): From d002a7760eb2d711b97708a2cbbb945852ac5345 Mon Sep 17 00:00:00 2001 From: Ahtisahm Shahid Date: Tue, 9 Jun 2026 13:22:50 +0500 Subject: [PATCH 04/10] chore: remove dead update-checker code and related assets The update checker was disabled in a prior commit. Remove the now-unused update-checker.ts module, generate_update.js script, version.json, and the generate:version npm script from build:electron. --- electron/app/utils/update-checker.ts | 286 --------------------------- electron/generate_update.js | 23 --- electron/package.json | 3 +- electron/version.json | 9 - 4 files changed, 1 insertion(+), 320 deletions(-) delete mode 100644 electron/app/utils/update-checker.ts delete mode 100644 electron/generate_update.js delete mode 100644 electron/version.json diff --git a/electron/app/utils/update-checker.ts b/electron/app/utils/update-checker.ts deleted file mode 100644 index 68d005b9b..000000000 --- a/electron/app/utils/update-checker.ts +++ /dev/null @@ -1,286 +0,0 @@ -import { net } from "electron"; -import { app, BrowserWindow } from "electron"; -import { isDev } from "./constants"; - -/** - * Version check URL — GitHub raw version.json (no API required). - * Override with UPDATE_SERVER_URL for local testing. - */ -const VERSION_JSON_URL = - process.env.UPDATE_SERVER_URL || - "https://raw.githubusercontent.com/presenton/presenton/refs/heads/main/electron/version.json"; - -const CURRENT_VERSION = app.getVersion(); -const WEBSITE_DOWNLOAD_URL = "https://presenton.ai/download"; - -/** Maximum number of fetch attempts (polls). */ -const MAX_ATTEMPTS = 3; - -/** Wait 2 minutes after load before first poll (10s in dev for testing). */ -const INITIAL_DELAY_MS = isDev ? 10 * 1_000 : 2 * 60 * 1_000; - -/** 1 minute between poll attempts (5s in dev for testing). */ -const POLL_INTERVAL_MS = isDev ? 5 * 1_000 : 1 * 60 * 1_000; - -/** Short delay before injecting banner to allow React/Next.js to mount. */ -const INJECT_DELAY_MS = isDev ? 500 : 1_000; - -function log(msg: string): void { - const line = `[UpdateChecker] ${msg}\n`; - process.stderr.write(line); - console.log(`[UpdateChecker] ${msg}`); -} - -interface VersionResponse { - version: string; - message?: string; - downloads?: { - linux: string; - mac: string; - windows: string; - }; -} - -/** - * Simple semver comparison that strips pre-release labels for numeric comparison. - * Returns true if `remote` is strictly newer than `current`. - */ -function isNewerVersion(current: string, remote: string): boolean { - const toNumbers = (v: string) => - v - .replace(/[^0-9.]/g, "") - .split(".") - .map(Number); - - const curr = toNumbers(current); - const rem = toNumbers(remote); - const len = Math.max(curr.length, rem.length); - - for (let i = 0; i < len; i++) { - const c = curr[i] ?? 0; - const r = rem[i] ?? 0; - if (r > c) return true; - if (r < c) return false; - } - return false; -} - -async function fetchVersionInfo(): Promise { - try { - log(`Fetching ${VERSION_JSON_URL}...`); - const response = await net.fetch(VERSION_JSON_URL, { - method: "GET", - headers: { "User-Agent": `Presenton/${CURRENT_VERSION}` }, - }); - if (!response.ok) { - log(`Fetch failed: HTTP ${response.status}`); - return null; - } - const data = (await response.json()) as VersionResponse; - log(`Fetched version: ${data.version}`); - return data; - } catch (err) { - log(`Fetch error: ${err}`); - return null; - } -} - -/** Pending update to re-inject on navigation (production: React/Next.js may replace DOM). */ -let pendingUpdate: { version: string; downloadUrl: string; message?: string } | null = null; - -/** - * Schedules banner injection after INJECT_DELAY_MS so React/Next.js can mount first. - * In production (.deb), the DOM may not be ready when did-finish-load fires. - */ -function scheduleBannerInjection( - win: BrowserWindow, - version: string, - downloadUrl: string, - message?: string -): void { - pendingUpdate = { version, downloadUrl, message }; - setTimeout(() => { - if (win.isDestroyed() || !pendingUpdate) return; - log(`Injecting banner now`); - injectUpdateBanner(win, pendingUpdate.version, pendingUpdate.downloadUrl, pendingUpdate.message); - }, INJECT_DELAY_MS); -} - -/** Escape HTML to prevent XSS; preserve newlines for display. */ -function escapeHtml(text: string): string { - return text - .replace(/&/g, "&") - .replace(//g, ">") - .replace(/"/g, """) - .replace(/\n/g, "
"); -} - -/** - * Injects an update banner at the bottom, aligned with the app UI. - * Includes a "View details" overlay for changelog/message. - */ -function injectUpdateBanner( - win: BrowserWindow, - latest: string, - downloadUrl: string, - message?: string -): void { - const hasMessage = Boolean(message && message.trim()); - const safeMessage = hasMessage ? escapeHtml(message!.trim()) : ""; - const safeMessageJson = JSON.stringify(safeMessage); - const viewDetailsBtnHtml = hasMessage - ? '' - : ""; - - const script = /* js */ ` - (function () { - if (document.getElementById('__presenton_update_banner__')) return; - - const msgHtml = ${safeMessageJson}; - - const banner = document.createElement('div'); - banner.id = '__presenton_update_banner__'; - banner.style.cssText = [ - 'position:fixed', - 'bottom:16px', - 'left:50%', - 'transform:translateX(-50%)', - 'max-width:min(560px,calc(100vw - 32px))', - 'width:100%', - 'background:rgba(255,255,255,0.95)', - 'backdrop-filter:blur(12px)', - '-webkit-backdrop-filter:blur(12px)', - 'color:#191919', - 'display:flex', - 'align-items:center', - 'justify-content:space-between', - 'padding:12px 16px', - 'font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif', - 'font-size:13px', - 'z-index:2147483646', - 'border:1px solid rgba(148,163,184,0.3)', - 'border-radius:12px', - 'box-shadow:0 4px 24px rgba(0,0,0,0.08)', - 'gap:12px', - ].join(';'); - - banner.innerHTML = \` - - ✨ - - Presenton ${latest} is available - — you have ${CURRENT_VERSION} - - -
- ${viewDetailsBtnHtml} - Download update - -
- \`; - - document.body.appendChild(banner); - - if (msgHtml) { - const overlay = document.createElement('div'); - overlay.id = '__presenton_update_overlay__'; - overlay.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.4);display:none;align-items:center;justify-content:center;z-index:2147483647;padding:24px;'; - overlay.onclick = function(e) { if (e.target === overlay) overlay.style.display = 'none'; }; - overlay.innerHTML = \` -
-
-

What's new in ${latest}

- -
-
-
- \`; - document.body.appendChild(overlay); - document.getElementById('__presenton_overlay_content__').innerHTML = msgHtml; - document.getElementById('__presenton_view_details_btn__').onclick = function() { - document.getElementById('__presenton_update_overlay__').style.display = 'flex'; - }; - } - })(); - `; - - win.webContents.executeJavaScript(script).catch((err) => { - log(`Banner injection failed: ${err}`); - }); -} - -/** - * Polls for version info up to MAX_ATTEMPTS times with 1 min between attempts. - * Stops as soon as a successful response is received or all attempts are exhausted. - */ -async function checkForUpdatesWithRetry(win: BrowserWindow): Promise { - log(`Starting check (current: ${CURRENT_VERSION})`); - for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) { - if (win.isDestroyed()) { - log("Window destroyed, aborting"); - return; - } - - log(`Attempt ${attempt}/${MAX_ATTEMPTS}`); - const data = await fetchVersionInfo(); - - if (data) { - const newer = isNewerVersion(CURRENT_VERSION, data.version); - log(`Remote ${data.version} vs current ${CURRENT_VERSION} -> newer? ${newer}`); - if (newer) { - const downloadUrl = WEBSITE_DOWNLOAD_URL; - log(`Injecting banner for ${data.version} (after ${INJECT_DELAY_MS}ms delay)`); - scheduleBannerInjection(win, data.version, downloadUrl, data.message); - } else { - log("No update needed, skipping banner"); - } - return; - } - - // Wait 1 minute before the next poll (skip delay after the last attempt) - if (attempt < MAX_ATTEMPTS) { - log(`Next poll in ${POLL_INTERVAL_MS / 1_000}s...`); - await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS)); - } - } - log("All attempts failed, no update info"); -} - -/** - * Starts the update checker. - * Waits 2 minutes after load, then polls 3 times with 1 min interval. - * Re-injects banner on every navigation (handles Next.js client routing). - */ -export function startUpdateChecker(win: BrowserWindow): void { - log("Registered, waiting for did-finish-load"); - let hasRunCheck = false; - - const onLoad = () => { - if (pendingUpdate) { - log("did-finish-load (navigation), re-injecting banner"); - scheduleBannerInjection(win, pendingUpdate.version, pendingUpdate.downloadUrl, pendingUpdate.message); - } else if (!hasRunCheck) { - hasRunCheck = true; - log(`did-finish-load fired, first poll in ${INITIAL_DELAY_MS / 1_000}s`); - setTimeout(() => { - if (win.isDestroyed()) return; - checkForUpdatesWithRetry(win); - }, INITIAL_DELAY_MS); - } - }; - - if (!win.webContents.isLoading()) { - log(`Page already loaded, first poll in ${INITIAL_DELAY_MS / 1_000}s`); - hasRunCheck = true; - setTimeout(() => { - if (win.isDestroyed()) return; - checkForUpdatesWithRetry(win); - }, INITIAL_DELAY_MS); - } - win.webContents.on("did-finish-load", onLoad); -} - -export function stopUpdateChecker(): void { - pendingUpdate = null; -} diff --git a/electron/generate_update.js b/electron/generate_update.js deleted file mode 100644 index 958c57ae6..000000000 --- a/electron/generate_update.js +++ /dev/null @@ -1,23 +0,0 @@ -const fs = require("fs"); - -const pkg = JSON.parse(fs.readFileSync("package.json")); -let existing = {}; -try { - existing = JSON.parse(fs.readFileSync("version.json", "utf8")); -} catch (_) {} - -const version = pkg.version; - -const update = { - version, - message: process.env.UPDATE_MESSAGE || existing.message || "", - downloads: { - linux: `https://github.com/presenton/presenton/releases/download/electron-v${version}/Presenton-${version}.deb`, - mac: `https://github.com/presenton/presenton/releases/download/electron-v${version}/Presenton-${version}.dmg`, - windows: `https://github.com/presenton/presenton/releases/download/electron-v${version}/Presenton-${version}.exe` - } -}; - -fs.writeFileSync("version.json", JSON.stringify(update, null, 2)); - -console.log("version.json generated"); \ No newline at end of file diff --git a/electron/package.json b/electron/package.json index 47c3c16a9..ff5a983a8 100644 --- a/electron/package.json +++ b/electron/package.json @@ -41,8 +41,7 @@ "fetch:export-runtime:latest": "EXPORT_RUNTIME_VERSION=latest node sync_export_runtime.js --force", "build:nextjs": "rm -rf resources/nextjs && rm -rf servers/nextjs/.next-build && cd servers/nextjs && cross-env BUILD_TARGET=electron npm run build && cp -r .next-build ../../resources/nextjs && cp -r app/presentation-templates ../../resources/nextjs/presentation-templates", "build:fastapi": "rm -rf resources/fastapi && npm run build:vectorstore && node scripts/prepare_fastapi_migrations.js && cd servers/fastapi && uv run python -m PyInstaller --distpath ../../resources server.spec", - "generate:version": "node generate_update.js", - "build:electron": "npm run generate:version && npm run build:export-runtime && rm -rf app_dist && tsc && node build.js", + "build:electron": "npm run build:export-runtime && rm -rf app_dist && tsc && node build.js", "build:all": "npm run clean:build && npm run setup:env && npm run build:ts && npm run install:pyinstaller && npm run build:nextjs && npm run build:fastapi && npm run build:electron", "clean:build": "rm -rf resources/nextjs && rm -rf resources/fastapi && rm -rf app_dist" }, diff --git a/electron/version.json b/electron/version.json deleted file mode 100644 index 243c7d538..000000000 --- a/electron/version.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "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.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 From 8f85535b08a879572f75615a4f30e8f02359711a Mon Sep 17 00:00:00 2001 From: Ahtisahm Shahid Date: Tue, 9 Jun 2026 13:24:45 +0500 Subject: [PATCH 05/10] ci: run tests on PRs targeting release/* branches --- .github/workflows/test-all.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-all.yml b/.github/workflows/test-all.yml index a8a78d2d6..bbd012595 100644 --- a/.github/workflows/test-all.yml +++ b/.github/workflows/test-all.yml @@ -4,7 +4,7 @@ on: push: branches: [ main ] pull_request: - branches: [ main ] + branches: [ main, release/* ] workflow_dispatch: jobs: From bd5e53fa9901ee466406c3aea2797829b0c65a05 Mon Sep 17 00:00:00 2001 From: Ahtisahm Shahid Date: Tue, 9 Jun 2026 15:22:24 +0500 Subject: [PATCH 06/10] ci: trigger PR tests From 92cd31d68210a4b2c56501360f6fa6bce74948ee Mon Sep 17 00:00:00 2001 From: Ahtisahm Shahid Date: Tue, 9 Jun 2026 15:45:09 +0500 Subject: [PATCH 07/10] fix: pass through existing HTTPExceptions in handle_llm_client_exceptions --- electron/servers/fastapi/utils/llm_client_error_handler.py | 2 ++ servers/fastapi/utils/llm_client_error_handler.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/electron/servers/fastapi/utils/llm_client_error_handler.py b/electron/servers/fastapi/utils/llm_client_error_handler.py index 787ed1f60..8ef2b4d3d 100644 --- a/electron/servers/fastapi/utils/llm_client_error_handler.py +++ b/electron/servers/fastapi/utils/llm_client_error_handler.py @@ -7,6 +7,8 @@ def handle_llm_client_exceptions(e: Exception) -> HTTPException: traceback.print_exc() + if isinstance(e, HTTPException): + return e if isinstance(e, OpenAIAPIError): error_body = getattr(e, "body", None) or {} error_type = ( diff --git a/servers/fastapi/utils/llm_client_error_handler.py b/servers/fastapi/utils/llm_client_error_handler.py index 787ed1f60..8ef2b4d3d 100644 --- a/servers/fastapi/utils/llm_client_error_handler.py +++ b/servers/fastapi/utils/llm_client_error_handler.py @@ -7,6 +7,8 @@ def handle_llm_client_exceptions(e: Exception) -> HTTPException: traceback.print_exc() + if isinstance(e, HTTPException): + return e if isinstance(e, OpenAIAPIError): error_body = getattr(e, "body", None) or {} error_type = ( From 0299c7c12c2b07bd6d31ff1ee3889c780e5a1cf3 Mon Sep 17 00:00:00 2001 From: Ahtisahm Shahid Date: Tue, 9 Jun 2026 15:55:46 +0500 Subject: [PATCH 08/10] fix: guard against non-dict error object in OpenAI error body --- electron/servers/fastapi/utils/llm_client_error_handler.py | 7 ++----- servers/fastapi/utils/llm_client_error_handler.py | 7 ++----- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/electron/servers/fastapi/utils/llm_client_error_handler.py b/electron/servers/fastapi/utils/llm_client_error_handler.py index 8ef2b4d3d..ac7b1302a 100644 --- a/electron/servers/fastapi/utils/llm_client_error_handler.py +++ b/electron/servers/fastapi/utils/llm_client_error_handler.py @@ -11,11 +11,8 @@ def handle_llm_client_exceptions(e: Exception) -> HTTPException: return e if isinstance(e, OpenAIAPIError): error_body = getattr(e, "body", None) or {} - error_type = ( - error_body.get("error", {}).get("type", "") - if isinstance(error_body, dict) - else "" - ) + error_obj = error_body.get("error", {}) if isinstance(error_body, dict) else {} + error_type = error_obj.get("type", "") if isinstance(error_obj, dict) else "" if error_type == "budget_exceeded" or "ExceededBudget" in str(getattr(e, "message", "")): return HTTPException( status_code=402, diff --git a/servers/fastapi/utils/llm_client_error_handler.py b/servers/fastapi/utils/llm_client_error_handler.py index 8ef2b4d3d..ac7b1302a 100644 --- a/servers/fastapi/utils/llm_client_error_handler.py +++ b/servers/fastapi/utils/llm_client_error_handler.py @@ -11,11 +11,8 @@ def handle_llm_client_exceptions(e: Exception) -> HTTPException: return e if isinstance(e, OpenAIAPIError): error_body = getattr(e, "body", None) or {} - error_type = ( - error_body.get("error", {}).get("type", "") - if isinstance(error_body, dict) - else "" - ) + error_obj = error_body.get("error", {}) if isinstance(error_body, dict) else {} + error_type = error_obj.get("type", "") if isinstance(error_obj, dict) else "" if error_type == "budget_exceeded" or "ExceededBudget" in str(getattr(e, "message", "")): return HTTPException( status_code=402, From 49b5d24dc5491cc55d32baebb34d07fdac666a2f Mon Sep 17 00:00:00 2001 From: Ahtisahm Shahid Date: Tue, 9 Jun 2026 15:59:42 +0500 Subject: [PATCH 09/10] fix: skip traceback logging for HTTPException passthroughs --- electron/servers/fastapi/utils/llm_client_error_handler.py | 2 +- servers/fastapi/utils/llm_client_error_handler.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/electron/servers/fastapi/utils/llm_client_error_handler.py b/electron/servers/fastapi/utils/llm_client_error_handler.py index ac7b1302a..147625986 100644 --- a/electron/servers/fastapi/utils/llm_client_error_handler.py +++ b/electron/servers/fastapi/utils/llm_client_error_handler.py @@ -6,9 +6,9 @@ def handle_llm_client_exceptions(e: Exception) -> HTTPException: - traceback.print_exc() if isinstance(e, HTTPException): return e + traceback.print_exc() if isinstance(e, OpenAIAPIError): error_body = getattr(e, "body", None) or {} error_obj = error_body.get("error", {}) if isinstance(error_body, dict) else {} diff --git a/servers/fastapi/utils/llm_client_error_handler.py b/servers/fastapi/utils/llm_client_error_handler.py index ac7b1302a..147625986 100644 --- a/servers/fastapi/utils/llm_client_error_handler.py +++ b/servers/fastapi/utils/llm_client_error_handler.py @@ -6,9 +6,9 @@ def handle_llm_client_exceptions(e: Exception) -> HTTPException: - traceback.print_exc() if isinstance(e, HTTPException): return e + traceback.print_exc() if isinstance(e, OpenAIAPIError): error_body = getattr(e, "body", None) or {} error_obj = error_body.get("error", {}) if isinstance(error_body, dict) else {} From 78b42dd1aae32b4f568f7060729e22609718a2a2 Mon Sep 17 00:00:00 2001 From: Ahtisahm Shahid Date: Wed, 10 Jun 2026 22:45:06 +0500 Subject: [PATCH 10/10] fix: isolate eye toggle state per section in onboarding settings Text and image API key visibility toggles shared a single showApiKey state, causing both fields to reveal/hide together. Added a separate showImageApiKey state for all image provider API key inputs. --- .../nextjs/components/OnBoarding/PresentonMode.tsx | 13 +++++++------ .../nextjs/components/OnBoarding/PresentonMode.tsx | 13 +++++++------ 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/electron/servers/nextjs/components/OnBoarding/PresentonMode.tsx b/electron/servers/nextjs/components/OnBoarding/PresentonMode.tsx index ef945f1dc..e7d25d629 100644 --- a/electron/servers/nextjs/components/OnBoarding/PresentonMode.tsx +++ b/electron/servers/nextjs/components/OnBoarding/PresentonMode.tsx @@ -25,6 +25,7 @@ const PresentonMode = ({ currentStep, setStep }: { currentStep: number, setStep: const userConfigState = useSelector((state: RootState) => state.userConfig); const [showApiKey, setShowApiKey] = useState(false); + const [showImageApiKey, setShowImageApiKey] = useState(false); const [availableModels, setAvailableModels] = useState([]); const [openModelSelect, setOpenModelSelect] = useState(false); const [modelsLoading, setModelsLoading] = useState(false); @@ -897,7 +898,7 @@ const PresentonMode = ({ currentStep, setStep }: { currentStep: number, setStep:
@@ -990,7 +991,7 @@ const PresentonMode = ({ currentStep, setStep }: { currentStep: number, setStep:
diff --git a/servers/nextjs/components/OnBoarding/PresentonMode.tsx b/servers/nextjs/components/OnBoarding/PresentonMode.tsx index ef945f1dc..e7d25d629 100644 --- a/servers/nextjs/components/OnBoarding/PresentonMode.tsx +++ b/servers/nextjs/components/OnBoarding/PresentonMode.tsx @@ -25,6 +25,7 @@ const PresentonMode = ({ currentStep, setStep }: { currentStep: number, setStep: const userConfigState = useSelector((state: RootState) => state.userConfig); const [showApiKey, setShowApiKey] = useState(false); + const [showImageApiKey, setShowImageApiKey] = useState(false); const [availableModels, setAvailableModels] = useState([]); const [openModelSelect, setOpenModelSelect] = useState(false); const [modelsLoading, setModelsLoading] = useState(false); @@ -897,7 +898,7 @@ const PresentonMode = ({ currentStep, setStep }: { currentStep: number, setStep:
@@ -990,7 +991,7 @@ const PresentonMode = ({ currentStep, setStep }: { currentStep: number, setStep: