diff --git a/electron/app/main.ts b/electron/app/main.ts index f91f517a..2eec46db 100644 --- a/electron/app/main.ts +++ b/electron/app/main.ts @@ -129,6 +129,7 @@ async function startServers(fastApiPort: number, nextjsPort: number) { DALL_E_3_QUALITY: process.env.DALL_E_3_QUALITY, GPT_IMAGE_1_5_QUALITY: process.env.GPT_IMAGE_1_5_QUALITY, APP_DATA_DIRECTORY: appDataDir, + FASTAPI_PUBLIC_URL: process.env.NEXT_PUBLIC_FAST_API, TEMP_DIRECTORY: tempDir, USER_CONFIG_PATH: userConfigPath, MIGRATE_DATABASE_ON_STARTUP: "True", diff --git a/electron/app/types/index.d.ts b/electron/app/types/index.d.ts index cee175bc..c56fbee3 100644 --- a/electron/app/types/index.d.ts +++ b/electron/app/types/index.d.ts @@ -28,11 +28,12 @@ interface FastApiEnv { DALL_E_3_QUALITY?: string, GPT_IMAGE_1_5_QUALITY?: string, APP_DATA_DIRECTORY?: string, + FASTAPI_PUBLIC_URL?: string, TEMP_DIRECTORY?: string, USER_CONFIG_PATH?: string, MIGRATE_DATABASE_ON_STARTUP?: string, - /** Absolute path to the resolved LibreOffice executable discovered at startup. */ - SOFFICE_PATH?: string, + /** Absolute path to the resolved LibreOffice executable discovered at startup. */ + SOFFICE_PATH?: string, /** Absolute path to the ImageMagick binary resolved at startup by imagemagick-check.ts. */ IMAGEMAGICK_BINARY?: string, /** Absolute path to the bundled LiteParse runner script. */ @@ -90,4 +91,4 @@ interface UserConfig { interface IPCStatus { success: boolean, message?: string, -} +} diff --git a/electron/app/utils/index.ts b/electron/app/utils/index.ts index d8faf13c..e48646d6 100644 --- a/electron/app/utils/index.ts +++ b/electron/app/utils/index.ts @@ -34,6 +34,7 @@ export function setupEnv(fastApiPort: number, nextjsPort: number) { const { app } = require('electron'); process.env.APP_VERSION = app.getVersion(); process.env.NEXT_PUBLIC_FAST_API = `${localhost}:${fastApiPort}`; + process.env.FASTAPI_PUBLIC_URL = process.env.NEXT_PUBLIC_FAST_API; process.env.TEMP_DIRECTORY = tempDir; process.env.NEXT_PUBLIC_USER_CONFIG_PATH = userConfigPath; process.env.NEXT_PUBLIC_URL = `${localhost}:${nextjsPort}`; diff --git a/electron/servers/fastapi/server.py b/electron/servers/fastapi/server.py index 3c4abca5..a66465d6 100644 --- a/electron/servers/fastapi/server.py +++ b/electron/servers/fastapi/server.py @@ -15,8 +15,8 @@ if __name__ == "__main__": reload = args.reload == "true" host = "127.0.0.1" - # Provide a predictable public URL for services that need absolute asset links. - os.environ.setdefault("FASTAPI_PUBLIC_URL", f"http://{host}:{args.port}") + # Always bind absolute asset generation to the active runtime port. + os.environ["FASTAPI_PUBLIC_URL"] = f"http://{host}:{args.port}" uvicorn.run( "api.main:app", 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 47e3020e..13bf3a14 100644 --- a/electron/servers/fastapi/utils/llm_calls/generate_presentation_outlines.py +++ b/electron/servers/fastapi/utils/llm_calls/generate_presentation_outlines.py @@ -9,12 +9,6 @@ from utils.get_dynamic_models import get_presentation_outline_model_with_n_slide from utils.llm_client_error_handler import handle_llm_client_exceptions from utils.llm_provider import get_model -""" -Previously there was a dedicated search-query generation prompt constant. -Removed in favor of embedding short, actionable web-search steps into the -system prompt when web grounding is requested. -""" - def get_system_prompt( tone: Optional[str] = None, @@ -49,7 +43,7 @@ def get_system_prompt( slide_outline_structure = ( "Each slide content:\n" " - Must have a ## title.\n" - " - Must have content either in multiple bullet points or table or both.\n" + # " - Must have content either in multiple bullet points or table or both.\n" " - Must be in Markdown format.\n" " - Don't use **bold** and __italic__ text." " - First slide title must be the same as the presentation title." @@ -62,7 +56,7 @@ def get_system_prompt( "Each slide content should contain the content for that slide.\n" f"{verbosity_instruction}\n" "Minimize repetitive content and make sure to use different words and phrases for different slides.\n" - "Include numerical data or tables if required or asked by the user.\n" + "Include numerical data, tables or code if required or asked by the user.\n" "If 'auto-detect' is used, figure it out from the content/context.\n" f"{title_slide_instruction}\n" f"{toc_block}" diff --git a/electron/servers/nextjs/app/hooks/compileLayout.ts b/electron/servers/nextjs/app/hooks/compileLayout.ts index cf946190..7bb10219 100644 --- a/electron/servers/nextjs/app/hooks/compileLayout.ts +++ b/electron/servers/nextjs/app/hooks/compileLayout.ts @@ -5,6 +5,7 @@ import * as z from "zod"; import * as Recharts from "recharts"; import * as Babel from "@babel/standalone"; import * as d3 from "d3"; +import { resolveBackendAssetUrl } from "@/utils/api"; // import * as d3Cloud from "d3-cloud"; export interface CompiledLayout { @@ -17,6 +18,37 @@ export interface CompiledLayout { schemaJSON: any; } +function isLikelyBackendAssetPath(value: string): boolean { + if (!value) return false; + if (value.startsWith("file://")) return true; + if (value.startsWith("/app_data/") || value.startsWith("/static/")) return true; + if (value.startsWith("app_data/") || value.startsWith("static/")) return true; + return value.includes("/app_data/") || value.includes("/static/"); +} + +function normalizeLayoutAssetUrls(value: T): T { + if (typeof value === "string") { + const trimmedValue = value.trim(); + if (!isLikelyBackendAssetPath(trimmedValue)) { + return value; + } + return resolveBackendAssetUrl(trimmedValue) as T; + } + + if (Array.isArray(value)) { + return value.map((item) => normalizeLayoutAssetUrls(item)) as T; + } + + if (value && typeof value === "object") { + const normalizedEntries = Object.entries(value as Record).map( + ([key, item]) => [key, normalizeLayoutAssetUrls(item)] + ); + return Object.fromEntries(normalizedEntries) as T; + } + + return value; +} + /** * Compiles a layout code string into a usable React component */ @@ -101,11 +133,17 @@ export function compileCustomLayout(layoutCode: string): CompiledLayout | null { return null; } + const wrappedComponent: React.ComponentType<{ data: any }> = ({ data, ...props }) => { + const normalizedData = React.useMemo(() => normalizeLayoutAssetUrls(data), [data]); + return React.createElement(result.component, { ...(props as any), data: normalizedData }); + }; + wrappedComponent.displayName = `CompiledTemplateLayout(${result.layoutName || result.layoutId || "Custom"})`; + // Parse schema to get sample data let sampleData: Record = {}; if (result.Schema) { try { - sampleData = result.Schema.parse({}); + sampleData = normalizeLayoutAssetUrls(result.Schema.parse({})); } catch (e) { console.warn("Could not parse schema defaults:", e); } @@ -113,7 +151,7 @@ export function compileCustomLayout(layoutCode: string): CompiledLayout | null { const schemaJSON = z.toJSONSchema(result.Schema); return { - component: result.component, + component: wrappedComponent, layoutId: result.layoutId, layoutName: result.layoutName, layoutDescription: result.layoutDescription, diff --git a/electron/servers/nextjs/utils/api.ts b/electron/servers/nextjs/utils/api.ts index 79e99b31..9593a620 100644 --- a/electron/servers/nextjs/utils/api.ts +++ b/electron/servers/nextjs/utils/api.ts @@ -1,10 +1,5 @@ // Utility to get the FastAPI base URL export function getFastAPIUrl(): string { - const queryFastApiUrl = getFastApiUrlFromQuery(); - if (queryFastApiUrl) { - return queryFastApiUrl; - } - // Prefer Electron-preload env when available if (typeof window !== "undefined" && (window as any).env?.NEXT_PUBLIC_FAST_API) { return (window as any).env.NEXT_PUBLIC_FAST_API; @@ -15,6 +10,11 @@ export function getFastAPIUrl(): string { return process.env.NEXT_PUBLIC_FAST_API; } + const queryFastApiUrl = getFastApiUrlFromQuery(); + if (queryFastApiUrl) { + return queryFastApiUrl; + } + // Safe Electron fallback to local FastAPI return "http://127.0.0.1:8000"; }