feat: enhance Python environment setup with improved detection and verification methods
This commit is contained in:
@@ -1,3 +1,9 @@
|
|||||||
|
<p align="center">
|
||||||
|
<img src="build/icon.png" alt="Dekupai logo — stylized letter D with orange and red wing elements" width="180" />
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center"><em>Logo: Stylized “D” mark with vibrant orange and red wing motifs — glossy 3D finish.</em></p>
|
||||||
|
|
||||||
# Dekupai 🍊
|
# Dekupai 🍊
|
||||||
### *Premium Glassmorphic AI Background Remover & Decoupage Studio*
|
### *Premium Glassmorphic AI Background Remover & Decoupage Studio*
|
||||||
|
|
||||||
@@ -130,4 +136,6 @@ To package and compile **Dekupai** into standalone production binaries or instal
|
|||||||
|
|
||||||
**Dekupai** is developed under the corporate vision of **Trunçgil Teknoloji** by the software engineering and design expertise of **Ümit Tunç**.
|
**Dekupai** is developed under the corporate vision of **Trunçgil Teknoloji** by the software engineering and design expertise of **Ümit Tunç**.
|
||||||
|
|
||||||
|
**Logo & Brand Identity:** The Dekupai app icon features a stylized capital “D” combined with wing-like elements in orange and red gradients, rendered with a glossy three-dimensional finish — representing speed, precision, and creative elevation.
|
||||||
|
|
||||||
It integrates state-of-the-art AI-powered image segmentation, combining the deep-learning capabilities of **BiRefNet** with high-end glassmorphic client-side controls to deliver professional desktop matting pipelines directly to local workstations.
|
It integrates state-of-the-art AI-powered image segmentation, combining the deep-learning capabilities of **BiRefNet** with high-end glassmorphic client-side controls to deliver professional desktop matting pipelines directly to local workstations.
|
||||||
|
|||||||
+47
-25
@@ -1,12 +1,13 @@
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import argparse
|
||||||
import subprocess
|
import subprocess
|
||||||
import venv
|
import venv
|
||||||
|
|
||||||
# Force line buffering
|
# Force line buffering
|
||||||
sys.stdout.reconfigure(line_buffering=True)
|
sys.stdout.reconfigure(line_buffering=True)
|
||||||
|
|
||||||
def run_command(command, description):
|
def run_command(command, description, required=True):
|
||||||
print(f"[STATUS] {description}...")
|
print(f"[STATUS] {description}...")
|
||||||
try:
|
try:
|
||||||
process = subprocess.Popen(
|
process = subprocess.Popen(
|
||||||
@@ -18,10 +19,8 @@ def run_command(command, description):
|
|||||||
bufsize=1
|
bufsize=1
|
||||||
)
|
)
|
||||||
|
|
||||||
# Read output in real-time
|
|
||||||
for line in process.stdout:
|
for line in process.stdout:
|
||||||
line_str = line.strip()
|
line_str = line.strip()
|
||||||
# If the output has pip download status, keep it readable but don't spam too much
|
|
||||||
if "Downloading" in line_str or "Installing" in line_str:
|
if "Downloading" in line_str or "Installing" in line_str:
|
||||||
print(f"[STATUS] {line_str}")
|
print(f"[STATUS] {line_str}")
|
||||||
elif line_str:
|
elif line_str:
|
||||||
@@ -29,18 +28,32 @@ def run_command(command, description):
|
|||||||
|
|
||||||
process.wait()
|
process.wait()
|
||||||
if process.returncode != 0:
|
if process.returncode != 0:
|
||||||
print(f"[ERROR] Command failed with code {process.returncode}", file=sys.stderr)
|
print(f"[ERROR] {description} failed (exit code {process.returncode})", file=sys.stderr)
|
||||||
return False
|
if required:
|
||||||
|
return False
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[ERROR] Exception occurred: {str(e)}", file=sys.stderr)
|
print(f"[ERROR] Exception during {description}: {str(e)}", file=sys.stderr)
|
||||||
return False
|
return not required
|
||||||
|
|
||||||
|
def verify_install(python_path):
|
||||||
|
print("[STATUS] Verifying PyTorch installation...")
|
||||||
|
verify_cmd = (
|
||||||
|
f'"{python_path}" -c "import torch; import transformers; import PIL; '
|
||||||
|
f'print(f\\"torch={{torch.__version__}}\\")"'
|
||||||
|
)
|
||||||
|
return run_command(verify_cmd, "Verifying installed packages", required=True)
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
parser = argparse.ArgumentParser()
|
||||||
venv_dir = os.path.join(base_dir, ".venv")
|
parser.add_argument('--data-dir', help='Writable directory for .venv (used by packaged app builds)')
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
backend_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
base_dir = os.path.abspath(args.data_dir) if args.data_dir else os.path.dirname(backend_dir)
|
||||||
|
venv_dir = os.path.join(base_dir, ".venv")
|
||||||
|
requirements_path = os.path.join(backend_dir, "requirements.txt")
|
||||||
|
|
||||||
# 1. Create venv if not exists
|
|
||||||
if not os.path.exists(venv_dir):
|
if not os.path.exists(venv_dir):
|
||||||
print("[STATUS] Creating Python virtual environment...")
|
print("[STATUS] Creating Python virtual environment...")
|
||||||
try:
|
try:
|
||||||
@@ -52,33 +65,42 @@ def main():
|
|||||||
else:
|
else:
|
||||||
print("[STATUS] Virtual environment already exists.")
|
print("[STATUS] Virtual environment already exists.")
|
||||||
|
|
||||||
# 2. Path to pip inside the venv
|
|
||||||
if os.name == 'nt':
|
if os.name == 'nt':
|
||||||
pip_path = os.path.join(venv_dir, "Scripts", "pip.exe")
|
|
||||||
python_path = os.path.join(venv_dir, "Scripts", "python.exe")
|
python_path = os.path.join(venv_dir, "Scripts", "python.exe")
|
||||||
else:
|
else:
|
||||||
pip_path = os.path.join(venv_dir, "bin", "pip")
|
|
||||||
python_path = os.path.join(venv_dir, "bin", "python")
|
python_path = os.path.join(venv_dir, "bin", "python")
|
||||||
|
|
||||||
if not os.path.exists(pip_path):
|
if not os.path.exists(python_path):
|
||||||
print("[ERROR] pip executable not found inside virtual environment!", file=sys.stderr)
|
print("[ERROR] Python executable not found inside virtual environment!", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# 3. Upgrade pip
|
run_command(
|
||||||
run_command(f'"{pip_path}" install --upgrade pip', "Upgrading pip to latest version")
|
f'"{python_path}" -m pip install --upgrade pip',
|
||||||
|
"Upgrading pip to latest version",
|
||||||
|
required=False
|
||||||
|
)
|
||||||
|
|
||||||
# 4. Install dependencies
|
torch_index = "https://download.pytorch.org/whl/cpu"
|
||||||
requirements_path = os.path.join(base_dir, "backend", "requirements.txt")
|
torch_cmd = (
|
||||||
|
f'"{python_path}" -m pip install torch torchvision '
|
||||||
|
f'--index-url {torch_index}'
|
||||||
|
)
|
||||||
|
if not run_command(torch_cmd, "Installing PyTorch AI Engine (CPU version)"):
|
||||||
|
print("[ERROR] PyTorch installation failed. Ensure Python 3.10–3.12 is installed and you have internet access.", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
# Use extra index URL for CPU-only torch to download faster (150MB instead of 2.5GB+ GPU version)
|
other_deps_cmd = (
|
||||||
# This is perfect for single images on desktop!
|
f'"{python_path}" -m pip install '
|
||||||
pip_install_cmd = f'"{pip_path}" install -r "{requirements_path}" --extra-index-url https://download.pytorch.org/whl/cpu'
|
f'"transformers<5" timm einops kornia pillow accelerate'
|
||||||
|
)
|
||||||
success = run_command(pip_install_cmd, "Installing PyTorch, Transformers, and PIL dependencies")
|
if not run_command(other_deps_cmd, "Installing HuggingFace Transformers & PIL library"):
|
||||||
if not success:
|
|
||||||
print("[ERROR] Dependency installation failed!", file=sys.stderr)
|
print("[ERROR] Dependency installation failed!", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
if not verify_install(python_path):
|
||||||
|
print("[ERROR] Package verification failed after installation!", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
print("[STATUS] Setup completed successfully!")
|
print("[STATUS] Setup completed successfully!")
|
||||||
print("[STATUS] DONE")
|
print("[STATUS] DONE")
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
@@ -1,6 +1,6 @@
|
|||||||
const { app, BrowserWindow, ipcMain, dialog, shell } = require('electron');
|
const { app, BrowserWindow, ipcMain, dialog, shell } = require('electron');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const { spawn } = require('child_process');
|
const { spawn, spawnSync } = require('child_process');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
|
||||||
let mainWindow;
|
let mainWindow;
|
||||||
@@ -42,14 +42,195 @@ app.on('window-all-closed', function () {
|
|||||||
if (process.platform !== 'darwin') app.quit();
|
if (process.platform !== 'darwin') app.quit();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let cachedSystemPython = null;
|
||||||
|
|
||||||
|
function getResourceRoot() {
|
||||||
|
if (app.isPackaged) {
|
||||||
|
const unpacked = path.join(process.resourcesPath, 'app.asar.unpacked');
|
||||||
|
if (fs.existsSync(unpacked)) return unpacked;
|
||||||
|
return process.resourcesPath;
|
||||||
|
}
|
||||||
|
return __dirname;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDataRoot() {
|
||||||
|
return app.isPackaged ? app.getPath('userData') : __dirname;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBackendPath(...segments) {
|
||||||
|
return path.join(getResourceRoot(), 'backend', ...segments);
|
||||||
|
}
|
||||||
|
|
||||||
|
function readRegistryValue(key, valueName) {
|
||||||
|
try {
|
||||||
|
const result = spawnSync('reg', ['query', key, '/v', valueName], {
|
||||||
|
encoding: 'utf8',
|
||||||
|
windowsHide: true,
|
||||||
|
timeout: 5000,
|
||||||
|
});
|
||||||
|
if (result.status !== 0) return '';
|
||||||
|
const match = result.stdout.match(new RegExp(`${valueName}\\s+REG_(?:EXPAND_)?SZ\\s+(.*)`, 'i'));
|
||||||
|
return match ? match[1].trim() : '';
|
||||||
|
} catch (_) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSpawnEnv() {
|
||||||
|
const env = { ...process.env };
|
||||||
|
if (process.platform !== 'win32') return env;
|
||||||
|
|
||||||
|
const pathEntries = new Set();
|
||||||
|
const addPaths = (value) => {
|
||||||
|
if (!value) return;
|
||||||
|
value.split(';').forEach((entry) => {
|
||||||
|
const trimmed = entry.trim();
|
||||||
|
if (trimmed) pathEntries.add(trimmed);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
addPaths(process.env.PATH);
|
||||||
|
addPaths(readRegistryValue('HKLM\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment', 'Path'));
|
||||||
|
addPaths(readRegistryValue('HKCU\\Environment', 'Path'));
|
||||||
|
|
||||||
|
const localAppData = process.env.LOCALAPPDATA
|
||||||
|
|| (process.env.USERPROFILE ? path.join(process.env.USERPROFILE, 'AppData', 'Local') : null);
|
||||||
|
if (localAppData) {
|
||||||
|
pathEntries.add(path.join(localAppData, 'Programs', 'Python', 'Launcher'));
|
||||||
|
for (const version of ['312', '311', '310']) {
|
||||||
|
pathEntries.add(path.join(localAppData, 'Programs', 'Python', `Python${version}`));
|
||||||
|
pathEntries.add(path.join(localAppData, 'Programs', 'Python', `Python${version}`, 'Scripts'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.env.ProgramFiles) {
|
||||||
|
for (const version of ['312', '311', '310']) {
|
||||||
|
pathEntries.add(path.join(process.env.ProgramFiles, `Python${version}`));
|
||||||
|
pathEntries.add(path.join(process.env.ProgramFiles, `Python${version}`, 'Scripts'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
env.PATH = [...pathEntries].join(';');
|
||||||
|
return env;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getWindowsPythonCandidates() {
|
||||||
|
const candidates = [];
|
||||||
|
const seen = new Set();
|
||||||
|
const addCandidate = (command, args = []) => {
|
||||||
|
const key = `${command}::${args.join(' ')}`;
|
||||||
|
if (seen.has(key)) return;
|
||||||
|
seen.add(key);
|
||||||
|
candidates.push({ command, args });
|
||||||
|
};
|
||||||
|
|
||||||
|
const localAppData = process.env.LOCALAPPDATA
|
||||||
|
|| (process.env.USERPROFILE ? path.join(process.env.USERPROFILE, 'AppData', 'Local') : null);
|
||||||
|
|
||||||
|
if (localAppData) {
|
||||||
|
const pyLauncher = path.join(localAppData, 'Programs', 'Python', 'Launcher', 'py.exe');
|
||||||
|
if (fs.existsSync(pyLauncher)) {
|
||||||
|
for (const flag of ['-3.12', '-3.11', '-3.10', '-3']) {
|
||||||
|
addCandidate(pyLauncher, [flag]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const version of ['312', '311', '310']) {
|
||||||
|
const pythonExe = path.join(localAppData, 'Programs', 'Python', `Python${version}`, 'python.exe');
|
||||||
|
if (fs.existsSync(pythonExe)) addCandidate(pythonExe, []);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.env.ProgramFiles) {
|
||||||
|
for (const version of ['312', '311', '310']) {
|
||||||
|
const pythonExe = path.join(process.env.ProgramFiles, `Python${version}`, 'python.exe');
|
||||||
|
if (fs.existsSync(pythonExe)) addCandidate(pythonExe, []);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addCandidate('py', ['-3.12']);
|
||||||
|
addCandidate('py', ['-3.11']);
|
||||||
|
addCandidate('py', ['-3.10']);
|
||||||
|
addCandidate('py', ['-3']);
|
||||||
|
addCandidate('python', []);
|
||||||
|
|
||||||
|
return candidates;
|
||||||
|
}
|
||||||
|
|
||||||
|
function verifyPython(command, args, env) {
|
||||||
|
try {
|
||||||
|
const result = spawnSync(command, [...args, '--version'], {
|
||||||
|
encoding: 'utf8',
|
||||||
|
windowsHide: true,
|
||||||
|
timeout: 6000,
|
||||||
|
env,
|
||||||
|
});
|
||||||
|
return result.status === 0 && !result.error;
|
||||||
|
} catch (_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveSystemPython() {
|
||||||
|
if (cachedSystemPython) return cachedSystemPython;
|
||||||
|
|
||||||
|
const env = getSpawnEnv();
|
||||||
|
const candidates = process.platform === 'win32'
|
||||||
|
? getWindowsPythonCandidates()
|
||||||
|
: [
|
||||||
|
{ command: 'python3', args: [] },
|
||||||
|
{ command: 'python', args: [] },
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const candidate of candidates) {
|
||||||
|
if (verifyPython(candidate.command, candidate.args, env)) {
|
||||||
|
cachedSystemPython = candidate;
|
||||||
|
console.log(`[Python Resolver] Selected: ${candidate.command} ${candidate.args.join(' ')}`);
|
||||||
|
return cachedSystemPython;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.warn('[Python Resolver] No working Python found; falling back to "python".');
|
||||||
|
cachedSystemPython = { command: 'python', args: [], unverified: true };
|
||||||
|
return cachedSystemPython;
|
||||||
|
}
|
||||||
|
|
||||||
|
function spawnPython(command, args, options = {}) {
|
||||||
|
return spawn(command, args, {
|
||||||
|
shell: false,
|
||||||
|
windowsHide: true,
|
||||||
|
env: getSpawnEnv(),
|
||||||
|
...options,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function attachProcessListeners(child, { onLine, onClose, onError }) {
|
||||||
|
child.stdout.on('data', (data) => {
|
||||||
|
const output = data.toString();
|
||||||
|
for (const line of output.split('\n')) {
|
||||||
|
if (line.trim()) onLine(line.trim());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
child.stderr.on('data', (data) => {
|
||||||
|
const errStr = data.toString();
|
||||||
|
for (const line of errStr.split('\n')) {
|
||||||
|
if (line.trim()) onLine(`[ERROR] ${line.trim()}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
child.on('error', (err) => onError(err));
|
||||||
|
child.on('close', (code) => onClose(code));
|
||||||
|
}
|
||||||
|
|
||||||
// Helper: Get Python Path
|
// Helper: Get Python Path
|
||||||
function getPythonPaths() {
|
function getPythonPaths() {
|
||||||
const isWindows = process.platform === 'win32';
|
const isWindows = process.platform === 'win32';
|
||||||
const venvPython = isWindows
|
const venvPython = isWindows
|
||||||
? path.join(__dirname, '.venv', 'Scripts', 'python.exe')
|
? path.join(getDataRoot(), '.venv', 'Scripts', 'python.exe')
|
||||||
: path.join(__dirname, '.venv', 'bin', 'python');
|
: path.join(getDataRoot(), '.venv', 'bin', 'python');
|
||||||
|
|
||||||
const systemPython = 'python';
|
const systemPython = resolveSystemPython();
|
||||||
const hasVenv = fs.existsSync(venvPython);
|
const hasVenv = fs.existsSync(venvPython);
|
||||||
|
|
||||||
return { venvPython, systemPython, hasVenv };
|
return { venvPython, systemPython, hasVenv };
|
||||||
@@ -64,36 +245,41 @@ ipcMain.handle('check-python-setup', async () => {
|
|||||||
// IPC: Run Setup
|
// IPC: Run Setup
|
||||||
ipcMain.handle('run-python-setup', async (event) => {
|
ipcMain.handle('run-python-setup', async (event) => {
|
||||||
const { systemPython } = getPythonPaths();
|
const { systemPython } = getPythonPaths();
|
||||||
const setupScript = path.join(__dirname, 'backend', 'setup.py');
|
const setupScript = getBackendPath('setup.py');
|
||||||
|
const dataDir = getDataRoot();
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
console.log(`[Main Process] Spawning setup: ${systemPython} ${setupScript}`);
|
const { command, args: pythonArgs } = systemPython;
|
||||||
// Spawn host python to run setup.py
|
const spawnArgs = [...pythonArgs, setupScript, '--data-dir', dataDir];
|
||||||
const process = spawn(systemPython, [setupScript], { shell: true });
|
console.log(`[Main Process] Spawning setup: ${command} ${spawnArgs.join(' ')}`);
|
||||||
|
|
||||||
let stderrData = '';
|
let stderrData = '';
|
||||||
|
let settled = false;
|
||||||
|
const settle = (payload) => {
|
||||||
|
if (settled) return;
|
||||||
|
settled = true;
|
||||||
|
resolve(payload);
|
||||||
|
};
|
||||||
|
|
||||||
process.stdout.on('data', (data) => {
|
const setupProcess = spawnPython(command, spawnArgs, { cwd: dataDir });
|
||||||
const output = data.toString();
|
|
||||||
console.log(`[Setup Output] ${output.trim()}`);
|
|
||||||
const lines = output.split('\n');
|
|
||||||
for (const line of lines) {
|
|
||||||
if (line.trim()) {
|
|
||||||
event.sender.send('setup-progress', line.trim());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
process.stderr.on('data', (data) => {
|
attachProcessListeners(setupProcess, {
|
||||||
const errStr = data.toString();
|
onLine: (line) => {
|
||||||
stderrData += errStr;
|
console.log(`[Setup Output] ${line}`);
|
||||||
console.error(`[Setup Error] ${errStr.trim()}`);
|
event.sender.send('setup-progress', line);
|
||||||
event.sender.send('setup-progress', `[ERROR] ${errStr.trim()}`);
|
if (line.startsWith('[ERROR]')) stderrData += `${line}\n`;
|
||||||
});
|
},
|
||||||
|
onError: (err) => {
|
||||||
process.on('close', (code) => {
|
console.error(`[Setup Error] Failed to start process: ${err.message}`);
|
||||||
console.log(`[Main Process] Setup completed with exit code ${code}`);
|
settle({
|
||||||
resolve({ code, error: stderrData });
|
code: 1,
|
||||||
|
error: `Python baslatilamadi: ${err.message}. Python 3.10-3.12 kurulu oldugundan emin olun.`,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onClose: (code) => {
|
||||||
|
console.log(`[Main Process] Setup completed with exit code ${code}`);
|
||||||
|
settle({ code, error: stderrData });
|
||||||
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -106,41 +292,38 @@ ipcMain.handle('remove-background', async (event, { inputPath, outputPath }) =>
|
|||||||
return { success: false, error: 'Python virtual environment not set up. Please run setup first.' };
|
return { success: false, error: 'Python virtual environment not set up. Please run setup first.' };
|
||||||
}
|
}
|
||||||
|
|
||||||
const processScript = path.join(__dirname, 'backend', 'process.py');
|
const processScript = getBackendPath('process.py');
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
console.log(`[Main Process] Spawning background removal: ${venvPython} ${processScript}`);
|
console.log(`[Main Process] Spawning background removal: ${venvPython} ${processScript}`);
|
||||||
// Spawn venv python to run process.py
|
|
||||||
const args = ['--input', inputPath, '--output', outputPath];
|
const args = ['--input', inputPath, '--output', outputPath];
|
||||||
const process = spawn(venvPython, [processScript, ...args], { shell: false });
|
|
||||||
|
|
||||||
let stderrData = '';
|
let stderrData = '';
|
||||||
|
let settled = false;
|
||||||
|
const settle = (payload) => {
|
||||||
|
if (settled) return;
|
||||||
|
settled = true;
|
||||||
|
resolve(payload);
|
||||||
|
};
|
||||||
|
|
||||||
process.stdout.on('data', (data) => {
|
const bgProcess = spawnPython(venvPython, [processScript, ...args], { cwd: getDataRoot() });
|
||||||
const output = data.toString();
|
|
||||||
console.log(`[Python Output] ${output.trim()}`);
|
attachProcessListeners(bgProcess, {
|
||||||
const lines = output.split('\n');
|
onLine: (line) => {
|
||||||
for (const line of lines) {
|
console.log(`[Python Output] ${line}`);
|
||||||
if (line.trim()) {
|
event.sender.send('process-progress', line);
|
||||||
event.sender.send('process-progress', line.trim());
|
if (line.startsWith('[ERROR]')) stderrData += `${line}\n`;
|
||||||
|
},
|
||||||
|
onError: (err) => {
|
||||||
|
settle({ success: false, error: `Python baslatilamadi: ${err.message}` });
|
||||||
|
},
|
||||||
|
onClose: (code) => {
|
||||||
|
console.log(`[Main Process] Process completed with exit code ${code}`);
|
||||||
|
if (code === 0) {
|
||||||
|
settle({ success: true, outputPath });
|
||||||
|
} else {
|
||||||
|
settle({ success: false, error: stderrData || `Process failed with exit code ${code}` });
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
});
|
|
||||||
|
|
||||||
process.stderr.on('data', (data) => {
|
|
||||||
const errStr = data.toString();
|
|
||||||
stderrData += errStr;
|
|
||||||
console.error(`[Python Error] ${errStr.trim()}`);
|
|
||||||
event.sender.send('process-progress', `[ERROR] ${errStr.trim()}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
process.on('close', (code) => {
|
|
||||||
console.log(`[Main Process] Process completed with exit code ${code}`);
|
|
||||||
if (code === 0) {
|
|
||||||
resolve({ success: true, outputPath });
|
|
||||||
} else {
|
|
||||||
resolve({ success: false, error: stderrData || `Process failed with exit code ${code}` });
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -36,6 +36,9 @@
|
|||||||
"!.venv/**",
|
"!.venv/**",
|
||||||
"!node_modules/**"
|
"!node_modules/**"
|
||||||
],
|
],
|
||||||
|
"asarUnpack": [
|
||||||
|
"backend/**"
|
||||||
|
],
|
||||||
"win": {
|
"win": {
|
||||||
"target": "nsis",
|
"target": "nsis",
|
||||||
"icon": "build/icon.ico"
|
"icon": "build/icon.ico"
|
||||||
|
|||||||
+14
-2
@@ -246,6 +246,15 @@ elements.btnStartSetup.addEventListener('click', async () => {
|
|||||||
setStepDone(elements.stepPip);
|
setStepDone(elements.stepPip);
|
||||||
setStepActive(elements.stepDeps);
|
setStepActive(elements.stepDeps);
|
||||||
elements.setupProgressBar.style.width = '60%';
|
elements.setupProgressBar.style.width = '60%';
|
||||||
|
} else if (progressText.includes("Installing HuggingFace")) {
|
||||||
|
elements.setupStatusText.innerText = getTrans('setupInstallingDeps');
|
||||||
|
setStepDone(elements.stepDeps);
|
||||||
|
setStepActive(elements.stepLibs);
|
||||||
|
elements.setupProgressBar.style.width = '80%';
|
||||||
|
} else if (progressText.includes("Verifying PyTorch")) {
|
||||||
|
elements.setupStatusText.innerText = getTrans('setupInstallingDeps');
|
||||||
|
setStepActive(elements.stepLibs);
|
||||||
|
elements.setupProgressBar.style.width = '90%';
|
||||||
} else if (progressText.includes("Downloading torch")) {
|
} else if (progressText.includes("Downloading torch")) {
|
||||||
elements.setupStatusText.innerText = getTrans('setupDownloadingTorch');
|
elements.setupStatusText.innerText = getTrans('setupDownloadingTorch');
|
||||||
elements.setupProgressBar.style.width = '75%';
|
elements.setupProgressBar.style.width = '75%';
|
||||||
@@ -274,9 +283,12 @@ elements.btnStartSetup.addEventListener('click', async () => {
|
|||||||
}, 1500);
|
}, 1500);
|
||||||
} else {
|
} else {
|
||||||
setStepFailed(elements.stepDeps);
|
setStepFailed(elements.stepDeps);
|
||||||
elements.setupStatusText.innerText = getTrans('setupFailed');
|
const errorDetail = (result.error || '').trim();
|
||||||
|
elements.setupStatusText.innerText = errorDetail
|
||||||
|
? `${getTrans('setupFailed')} ${errorDetail.slice(0, 200)}`
|
||||||
|
: getTrans('setupFailed');
|
||||||
elements.btnStartSetup.disabled = false;
|
elements.btnStartSetup.disabled = false;
|
||||||
showToast(getTrans('toastSetupError'), getTrans('toastSetupErrorMsg'), "error");
|
showToast(getTrans('toastSetupError'), errorDetail || getTrans('toastSetupErrorMsg'), "error");
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
elements.setupStatusText.innerText = `${getTrans('toastSetupError')}: ${err.message}`;
|
elements.setupStatusText.innerText = `${getTrans('toastSetupError')}: ${err.message}`;
|
||||||
|
|||||||
Reference in New Issue
Block a user