feat: enhance user configuration handling and add error messaging for model pulling

This commit is contained in:
sudipnext 2026-04-19 13:15:19 +05:45
parent 3d06644914
commit 3a4aacd0dd
4 changed files with 56 additions and 52 deletions

View file

@ -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:

View file

@ -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"

View file

@ -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;

View file

@ -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<string> {
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<typeof setInterval> | 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);
});
};