diff --git a/electron/.gitignore b/electron/.gitignore index bd040a77..588efb1a 100644 --- a/electron/.gitignore +++ b/electron/.gitignore @@ -21,6 +21,13 @@ app_dist resources/fastapi resources/nextjs dist +eng.traineddata +servers/fastapi/build/ +servers/fastapi/dist/ servers/fastapi/fastembed_cache/ electron/.cache/ -electron/.cache/export-runtime/ \ No newline at end of file +electron/.cache/export-runtime/ +*.pkg +*.toc +*.zip +*.pyc \ No newline at end of file diff --git a/electron/app/ipc/setup_install_handlers.ts b/electron/app/ipc/setup_install_handlers.ts index 29ddb454..3ffa345b 100644 --- a/electron/app/ipc/setup_install_handlers.ts +++ b/electron/app/ipc/setup_install_handlers.ts @@ -20,6 +20,7 @@ import { import { getSetupStatus } from "../utils/setup-dependencies"; import { getImageMagickDownloadUrl, + getImageMagickManualInstallCommands, isImageMagickInstalled, } from "../utils/imagemagick-check"; @@ -72,6 +73,33 @@ function commandExists(command: string, versionArgs: string[] = ["--version"]): return result.status === 0; } +function resolveBrewCommand(): string | null { + if (commandExists("brew")) { + return "brew"; + } + + const candidates = ["/opt/homebrew/bin/brew", "/usr/local/bin/brew"]; + for (const candidate of candidates) { + if (fs.existsSync(candidate)) { + return candidate; + } + } + return null; +} + +function resolveLinuxEscalationCommand(): string | null { + if (commandExists("pkexec", ["--version"])) return "pkexec"; + if (commandExists("sudo", ["-V"])) return "sudo"; + return null; +} + +function logManualImageMagickCommands(wc: WebContents) { + for (const line of getImageMagickManualInstallCommands()) { + const level = line.endsWith(":") ? "info" : "cmd"; + sendImageMagickLog(wc, level, line); + } +} + function runInstallCommand( wc: WebContents, command: string, @@ -210,7 +238,18 @@ export function setupSetupInstallHandlers() { if (process.platform === "linux") { if (commandExists("apt-get")) { - await runInstallCommand(wc, "pkexec", [ + const escalator = resolveLinuxEscalationCommand(); + if (!escalator) { + throw new Error( + "Neither pkexec nor sudo is available to run apt-get install." + ); + } + + await runInstallCommand(wc, escalator, [ + "apt-get", + "update", + ]); + await runInstallCommand(wc, escalator, [ "apt-get", "install", "-y", @@ -218,17 +257,30 @@ export function setupSetupInstallHandlers() { ]); } else { throw new Error( - "apt-get is unavailable. Install ImageMagick manually from the official download page." + "apt-get is unavailable. Install ImageMagick manually using your package manager." ); } } else if (process.platform === "darwin") { - if (commandExists("brew")) { - await runInstallCommand(wc, "brew", ["install", "imagemagick"]); - } else { + let brewCommand = resolveBrewCommand(); + if (!brewCommand) { + sendImageMagickLog( + wc, + "info", + "Homebrew not found. Installing Homebrew first..." + ); + const installHomebrewCommand = + 'NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"'; + await runInstallCommand(wc, "/bin/bash", ["-c", installHomebrewCommand]); + brewCommand = resolveBrewCommand(); + } + + if (!brewCommand) { throw new Error( - "Homebrew is not installed. Install ImageMagick manually from the official download page." + "Homebrew installation completed, but brew was not found on PATH." ); } + + await runInstallCommand(wc, brewCommand, ["install", "imagemagick"]); } else if (process.platform === "win32") { if (commandExists("choco", ["-v"])) { await runInstallCommand(wc, "choco", [ @@ -238,7 +290,7 @@ export function setupSetupInstallHandlers() { ]); } else { throw new Error( - "Chocolatey is not installed. Install ImageMagick manually from the official download page." + "Chocolatey is not installed. Falling back to direct installer download." ); } } else { @@ -253,14 +305,21 @@ export function setupSetupInstallHandlers() { const message = error instanceof Error ? error.message : "ImageMagick install failed"; sendImageMagickLog(wc, "error", message); + logManualImageMagickCommands(wc); const downloadUrl = getImageMagickDownloadUrl(); sendImageMagickLog( wc, "info", - `Falling back to manual install page: ${downloadUrl}` + `Opening manual install link: ${downloadUrl}` ); await shell.openExternal(downloadUrl); - return { ok: true }; + sendImageMagickProgress( + wc, + "error", + undefined, + "Finish manual installation, then click Retry." + ); + return { ok: false, error: message }; } } ); diff --git a/electron/app/utils/imagemagick-check.ts b/electron/app/utils/imagemagick-check.ts index 6495c7f8..38d01be5 100644 --- a/electron/app/utils/imagemagick-check.ts +++ b/electron/app/utils/imagemagick-check.ts @@ -18,10 +18,34 @@ export function isImageMagickInstalled(): boolean { export function getImageMagickDownloadUrl(): string { if (process.platform === "win32") { - return "https://imagemagick.org/script/download.php#windows"; + return "https://imagemagick.org/archive/binaries/ImageMagick-7.1.2-18-Q16-HDRI-x64-dll.exe"; } if (process.platform === "darwin") { - return "https://imagemagick.org/script/download.php#macosx"; + return "https://brew.sh/"; } return "https://imagemagick.org/script/download.php#linux"; } + +export function getImageMagickManualInstallCommands(): string[] { + if (process.platform === "win32") { + return [ + "Download and run the installer:", + getImageMagickDownloadUrl(), + ]; + } + + if (process.platform === "darwin") { + return [ + "Install Homebrew:", + '/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"', + "Install ImageMagick:", + "brew install imagemagick", + ]; + } + + return [ + "Install ImageMagick:", + "sudo apt-get update", + "sudo apt-get install -y imagemagick", + ]; +} diff --git a/electron/resources/document-extraction/liteparse_runner.mjs b/electron/resources/document-extraction/liteparse_runner.mjs index 6793610a..d7b68195 100644 --- a/electron/resources/document-extraction/liteparse_runner.mjs +++ b/electron/resources/document-extraction/liteparse_runner.mjs @@ -70,7 +70,7 @@ const ocrEnabled = parseBool(readArg("--ocr-enabled"), true); const dpi = toNumber(readArg("--dpi"), 150, 72, 600); const numWorkers = toNumber( readArg("--num-workers"), - Math.max(os.cpus().length - 4, 1), + Math.max(os.cpus().length - 2, 1), 1, 64 ); diff --git a/electron/resources/ui/setup-installer/index.html b/electron/resources/ui/setup-installer/index.html index 5ff1397b..2d871226 100644 --- a/electron/resources/ui/setup-installer/index.html +++ b/electron/resources/ui/setup-installer/index.html @@ -288,7 +288,7 @@ ? 'Presenton uses LibreOffice to generate custom templates from PPTX files.' : step === 'chrome' ? 'Presenton uses Chromium for export and slide rendering. Download it now (~150 MB).' - : 'Presenton uses ImageMagick for OCR/document conversion support. We will try automatic installation first, then open the download page if package manager tools are unavailable.'; + : 'Presenton uses ImageMagick for OCR/document conversion support. Linux uses apt, macOS installs Homebrew first (if needed) and then runs brew install imagemagick, and Windows uses Chocolatey with a direct installer fallback.'; document.getElementById('btn-install').onclick = () => startInstall(step); document.getElementById('btn-skip').onclick = () => handleSkip(); showState('prompt'); @@ -315,8 +315,17 @@ }); } else { document.getElementById('dl-heading').textContent = 'Installing ImageMagick'; - document.getElementById('dl-phase').textContent = 'Automatic install (apt/brew/choco) with fallback to manual download'; - window.setupInstaller.installImageMagick().then(() => { + document.getElementById('dl-phase').textContent = 'Linux: apt-get | macOS: Homebrew + brew install | Windows: choco or direct installer'; + window.setupInstaller.installImageMagick().then((installResult) => { + if (!installResult || !installResult.ok) { + if (currentStep !== 'imagemagick') return; + document.getElementById('err-msg').textContent = installResult?.error || 'ImageMagick installation needs manual completion. Follow the shown commands and then click Retry.'; + showState('error'); + document.getElementById('btn-retry').onclick = () => startInstall('imagemagick'); + document.getElementById('btn-skip-error').onclick = () => nextOrDone(); + return; + } + window.setupInstaller.checkImageMagick().then(res => { if (!res.ok && currentStep === 'imagemagick') { document.getElementById('err-msg').textContent = res.error || 'ImageMagick is not installed yet.';