From 3a4aacd0dd28bbb6ea62e9d785d77a68424510dd Mon Sep 17 00:00:00 2001 From: sudipnext Date: Sun, 19 Apr 2026 13:15:19 +0545 Subject: [PATCH] feat: enhance user configuration handling and add error messaging for model pulling --- docker-compose.yml | 3 ++ servers/nextjs/app/api/user-config/route.ts | 55 +++------------------ servers/nextjs/types/llm_config.ts | 3 ++ servers/nextjs/utils/providerUtils.ts | 47 ++++++++++++++++-- 4 files changed, 56 insertions(+), 52 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index eb25539d..a2693f48 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -93,6 +93,7 @@ services: 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. @@ -140,6 +141,7 @@ services: 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 @@ -170,3 +172,4 @@ services: volumes: presenton_root_node_modules: + presenton_document_extraction_liteparse: diff --git a/servers/nextjs/app/api/user-config/route.ts b/servers/nextjs/app/api/user-config/route.ts index a66465bc..af604f4b 100644 --- a/servers/nextjs/app/api/user-config/route.ts +++ b/servers/nextjs/app/api/user-config/route.ts @@ -40,62 +40,23 @@ export async function POST(request: Request) { const configData = fs.readFileSync(userConfigPath, "utf-8"); existingConfig = JSON.parse(configData); } + const definedIncomingEntries = Object.entries(userConfig).filter( + ([, value]) => value !== undefined + ); const mergedConfig: LLMConfig = { - LLM: userConfig.LLM || existingConfig.LLM, - OPENAI_API_KEY: userConfig.OPENAI_API_KEY || existingConfig.OPENAI_API_KEY, - OPENAI_MODEL: userConfig.OPENAI_MODEL || existingConfig.OPENAI_MODEL, - GOOGLE_API_KEY: userConfig.GOOGLE_API_KEY || existingConfig.GOOGLE_API_KEY, - GOOGLE_MODEL: userConfig.GOOGLE_MODEL || existingConfig.GOOGLE_MODEL, - ANTHROPIC_API_KEY: - userConfig.ANTHROPIC_API_KEY || existingConfig.ANTHROPIC_API_KEY, - ANTHROPIC_MODEL: - userConfig.ANTHROPIC_MODEL || existingConfig.ANTHROPIC_MODEL, - OLLAMA_URL: userConfig.OLLAMA_URL || existingConfig.OLLAMA_URL, - OLLAMA_MODEL: userConfig.OLLAMA_MODEL || existingConfig.OLLAMA_MODEL, - CUSTOM_LLM_URL: userConfig.CUSTOM_LLM_URL || existingConfig.CUSTOM_LLM_URL, - CUSTOM_LLM_API_KEY: - userConfig.CUSTOM_LLM_API_KEY || existingConfig.CUSTOM_LLM_API_KEY, - CUSTOM_MODEL: userConfig.CUSTOM_MODEL || existingConfig.CUSTOM_MODEL, - DISABLE_IMAGE_GENERATION: - userConfig.DISABLE_IMAGE_GENERATION === undefined - ? existingConfig.DISABLE_IMAGE_GENERATION - : userConfig.DISABLE_IMAGE_GENERATION, - PIXABAY_API_KEY: - userConfig.PIXABAY_API_KEY || existingConfig.PIXABAY_API_KEY, - IMAGE_PROVIDER: userConfig.IMAGE_PROVIDER || existingConfig.IMAGE_PROVIDER, - PEXELS_API_KEY: userConfig.PEXELS_API_KEY || existingConfig.PEXELS_API_KEY, - COMFYUI_URL: userConfig.COMFYUI_URL || existingConfig.COMFYUI_URL, - COMFYUI_WORKFLOW: - userConfig.COMFYUI_WORKFLOW || existingConfig.COMFYUI_WORKFLOW, - DALL_E_3_QUALITY: - userConfig.DALL_E_3_QUALITY || existingConfig.DALL_E_3_QUALITY, - GPT_IMAGE_1_5_QUALITY: - userConfig.GPT_IMAGE_1_5_QUALITY || existingConfig.GPT_IMAGE_1_5_QUALITY, - TOOL_CALLS: - userConfig.TOOL_CALLS === undefined - ? existingConfig.TOOL_CALLS - : userConfig.TOOL_CALLS, - DISABLE_THINKING: - userConfig.DISABLE_THINKING === undefined - ? existingConfig.DISABLE_THINKING - : userConfig.DISABLE_THINKING, - EXTENDED_REASONING: - userConfig.EXTENDED_REASONING === undefined - ? existingConfig.EXTENDED_REASONING - : userConfig.EXTENDED_REASONING, - WEB_GROUNDING: - userConfig.WEB_GROUNDING === undefined - ? existingConfig.WEB_GROUNDING - : userConfig.WEB_GROUNDING, + ...existingConfig, + ...Object.fromEntries(definedIncomingEntries), USE_CUSTOM_URL: userConfig.USE_CUSTOM_URL === undefined ? existingConfig.USE_CUSTOM_URL : userConfig.USE_CUSTOM_URL, - CODEX_MODEL: userConfig.CODEX_MODEL || existingConfig.CODEX_MODEL, CODEX_ACCESS_TOKEN: existingConfig.CODEX_ACCESS_TOKEN, CODEX_REFRESH_TOKEN: existingConfig.CODEX_REFRESH_TOKEN, CODEX_TOKEN_EXPIRES: existingConfig.CODEX_TOKEN_EXPIRES, CODEX_ACCOUNT_ID: existingConfig.CODEX_ACCOUNT_ID, + CODEX_USERNAME: existingConfig.CODEX_USERNAME, + CODEX_EMAIL: existingConfig.CODEX_EMAIL, + CODEX_IS_PRO: existingConfig.CODEX_IS_PRO, DISABLE_ANONYMOUS_TRACKING: Object.prototype.hasOwnProperty.call( userConfig, "DISABLE_ANONYMOUS_TRACKING" diff --git a/servers/nextjs/types/llm_config.ts b/servers/nextjs/types/llm_config.ts index e022cacc..7af2b942 100644 --- a/servers/nextjs/types/llm_config.ts +++ b/servers/nextjs/types/llm_config.ts @@ -49,6 +49,9 @@ export interface LLMConfig { CODEX_REFRESH_TOKEN?: string; CODEX_TOKEN_EXPIRES?: string; CODEX_ACCOUNT_ID?: string; + CODEX_USERNAME?: string; + CODEX_EMAIL?: string; + CODEX_IS_PRO?: boolean; // Only used in UI settings USE_CUSTOM_URL?: boolean; diff --git a/servers/nextjs/utils/providerUtils.ts b/servers/nextjs/utils/providerUtils.ts index c5d817ba..6aca4dc6 100644 --- a/servers/nextjs/utils/providerUtils.ts +++ b/servers/nextjs/utils/providerUtils.ts @@ -13,6 +13,7 @@ export interface DownloadingModel { downloaded: number | null; status: string; done: boolean; + error?: string | null; } export interface OllamaModelsResult { @@ -88,7 +89,7 @@ export const changeProvider = ( export const checkIfSelectedOllamaModelIsPulled = async (ollamaModel: string) => { try { - const response = await fetch(getApiUrl("/api/v1/ppt/ollama/models/available")); + const response = await fetch(getApiUrl('/api/v1/ppt/ollama/models/available')); const models = await response.json(); const pulledModels = models.map((model: any) => model.name); return pulledModels.includes(ollamaModel); @@ -120,6 +121,24 @@ function isAbortError(e: unknown): boolean { return e instanceof Error && e.name === "AbortError"; } +async function getPullErrorMessage( + response: Response, + fallback: string +): Promise { + try { + const body = await response.json(); + if (typeof body?.detail === "string" && body.detail.trim()) { + return body.detail; + } + if (typeof body?.error === "string" && body.error.trim()) { + return body.error; + } + } catch { + // Ignore parse errors and use fallback. + } + return fallback; +} + /** * Pulls Ollama model with progress tracking. * Pass an AbortSignal to stop polling (e.g. user cancels download). @@ -132,6 +151,7 @@ export const pullOllamaModel = async ( return new Promise((resolve, reject) => { let interval: ReturnType | null = null; let settled = false; + let polling = false; const cleanup = () => { if (interval !== null) { @@ -155,11 +175,17 @@ export const pullOllamaModel = async ( } signal?.addEventListener("abort", onAbort); - interval = setInterval(async () => { + const pollOnce = async () => { + if (settled || polling) { + return; + } + if (signal?.aborted) { onAbort(); return; } + + polling = true; try { const response = await fetch( getApiUrl(`/api/v1/ppt/ollama/model/pull?model=${model}`) @@ -173,12 +199,12 @@ export const pullOllamaModel = async ( cleanup(); onProgress?.(data); resolve(data); - } else if (data.status === "error") { + } else if (data.status === "error" || data.error) { if (settled) return; settled = true; cleanup(); onProgress?.(resetDownloadingModel()); - reject(new Error("Error occurred while pulling model")); + reject(new Error(data.error || "Error occurred while pulling model")); } else { onProgress?.(data); } @@ -190,7 +216,11 @@ export const pullOllamaModel = async ( if (response.status === 403) { reject(new Error("Request to Ollama Not Authorized")); } else { - reject(new Error("Error occurred while pulling model")); + const errorMessage = await getPullErrorMessage( + response, + "Error occurred while pulling model" + ); + reject(new Error(errorMessage)); } } } catch (error) { @@ -202,7 +232,14 @@ export const pullOllamaModel = async ( cleanup(); onProgress?.(resetDownloadingModel()); reject(error); + } finally { + polling = false; } + }; + + void pollOnce(); + interval = setInterval(() => { + void pollOnce(); }, 1000); }); }; \ No newline at end of file