-
Notifications
You must be signed in to change notification settings - Fork 79
Expand file tree
/
Copy pathmain.js
More file actions
260 lines (219 loc) · 7.16 KB
/
main.js
File metadata and controls
260 lines (219 loc) · 7.16 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
const { app, BrowserWindow, ipcMain, dialog, shell } = require('electron');
const path = require('path');
const { spawn } = require('child_process');
const fs = require('fs');
const axios = require('axios');
let mainWindow;
let pythonProcess;
const PYTHON_SERVER_PORT = 5000;
const PYTHON_SERVER_URL = `http://127.0.0.1:${PYTHON_SERVER_PORT}`;
function createWindow() {
mainWindow = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
nodeIntegration: true,
contextIsolation: false
},
icon: path.join(__dirname, 'assets', 'icon.png')
});
mainWindow.loadFile('index.html');
// Open external links in browser instead of Electron window
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
shell.openExternal(url);
return { action: 'deny' };
});
// Handle navigation to external URLs
mainWindow.webContents.on('will-navigate', (event, url) => {
// Allow navigation to local files and the Python server
if (url.startsWith('file://') || url.startsWith(PYTHON_SERVER_URL)) {
return;
}
// Open external URLs in browser
event.preventDefault();
shell.openExternal(url);
});
// Open DevTools in development mode
if (process.argv.includes('--dev')) {
mainWindow.webContents.openDevTools();
}
mainWindow.on('closed', function () {
mainWindow = null;
});
}
function startPythonServer() {
return new Promise((resolve, reject) => {
console.log('Starting Python OCR server...');
const pythonScript = path.join(__dirname, 'backend', 'ocr_server.py');
// Check if Python script exists
if (!fs.existsSync(pythonScript)) {
reject(new Error(`Python script not found: ${pythonScript}`));
return;
}
// Use venv Python if available, otherwise fall back to system Python
let pythonExecutable;
if (process.platform === 'win32') {
const venvPython = path.join(__dirname, 'venv', 'Scripts', 'python.exe');
pythonExecutable = fs.existsSync(venvPython) ? venvPython : 'python';
} else {
const venvPython = path.join(__dirname, 'venv', 'bin', 'python3');
pythonExecutable = fs.existsSync(venvPython) ? venvPython : 'python3';
}
console.log(`Using Python: ${pythonExecutable}`);
pythonProcess = spawn(pythonExecutable, [pythonScript]);
let resolved = false;
const markAsReady = () => {
if (!resolved) {
resolved = true;
console.log('Python server is ready!');
resolve();
}
};
pythonProcess.stdout.on('data', (data) => {
console.log(`Python: ${data.toString()}`);
// Check if server is ready
if (data.toString().includes('Running on')) {
markAsReady();
}
});
pythonProcess.stderr.on('data', (data) => {
// Flask logs to stderr by default, even for INFO messages
console.log(`Python: ${data.toString()}`);
// Flask logs to stderr, so also check here for server ready message
if (data.toString().includes('Running on')) {
markAsReady();
}
});
pythonProcess.on('close', (code) => {
console.log(`Python process exited with code ${code}`);
});
// Wait for server to start with retry logic (timeout after 30 seconds)
const startTime = Date.now();
const maxWaitTime = 30000; // 30 seconds
const checkInterval = 1000; // Check every 1 second
const checkWithRetry = async () => {
if (resolved) return;
const elapsed = Date.now() - startTime;
if (elapsed >= maxWaitTime) {
if (!resolved) {
resolved = true;
reject(new Error('Python server failed to start within timeout'));
}
return;
}
try {
await checkServerHealth();
markAsReady();
} catch (error) {
// Retry after interval
setTimeout(checkWithRetry, checkInterval);
}
};
// Start checking after initial delay
setTimeout(checkWithRetry, 2000);
});
}
async function checkServerHealth() {
try {
const response = await axios.get(`${PYTHON_SERVER_URL}/health`);
return response.data.status === 'ok';
} catch (error) {
throw error;
}
}
function stopPythonServer() {
if (pythonProcess) {
console.log('Stopping Python server...');
pythonProcess.kill();
pythonProcess = null;
}
}
// IPC Handlers
ipcMain.handle('check-server-status', async () => {
try {
const response = await axios.get(`${PYTHON_SERVER_URL}/health`);
return { success: true, data: response.data };
} catch (error) {
return { success: false, error: error.message };
}
});
ipcMain.handle('load-model', async () => {
try {
const response = await axios.post(`${PYTHON_SERVER_URL}/load_model`);
return { success: true, data: response.data };
} catch (error) {
return { success: false, error: error.message };
}
});
ipcMain.handle('get-model-info', async () => {
try {
const response = await axios.get(`${PYTHON_SERVER_URL}/model_info`);
return { success: true, data: response.data };
} catch (error) {
return { success: false, error: error.message };
}
});
ipcMain.handle('select-image', async () => {
const result = await dialog.showOpenDialog(mainWindow, {
properties: ['openFile'],
filters: [
{ name: 'Images', extensions: ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'] }
]
});
if (!result.canceled && result.filePaths.length > 0) {
return { success: true, filePath: result.filePaths[0] };
}
return { success: false };
});
ipcMain.handle('perform-ocr', async (event, { imagePath, promptType, baseSize, imageSize, cropMode }) => {
try {
const FormData = require('form-data');
const formData = new FormData();
// Read image file and append to form data
const imageBuffer = fs.readFileSync(imagePath);
formData.append('image', imageBuffer, {
filename: path.basename(imagePath),
contentType: 'image/jpeg'
});
formData.append('prompt_type', promptType || 'document');
formData.append('base_size', baseSize || 1024);
formData.append('image_size', imageSize || 640);
formData.append('crop_mode', cropMode ? 'true' : 'false');
const response = await axios.post(`${PYTHON_SERVER_URL}/ocr`, formData, {
headers: formData.getHeaders(),
maxContentLength: Infinity,
maxBodyLength: Infinity
});
return { success: true, data: response.data };
} catch (error) {
console.error('OCR Error:', error);
return {
success: false,
error: error.response?.data?.message || error.message
};
}
});
// App lifecycle
app.whenReady().then(async () => {
try {
await startPythonServer();
createWindow();
} catch (error) {
console.error('Failed to start Python server:', error);
dialog.showErrorBox(
'Startup Error',
`Failed to start Python server: ${error.message}\n\nPlease make sure Python 3 is installed and the required dependencies are installed (see README.md)`
);
app.quit();
}
app.on('activate', function () {
if (BrowserWindow.getAllWindows().length === 0) createWindow();
});
});
app.on('window-all-closed', function () {
stopPythonServer();
if (process.platform !== 'darwin') app.quit();
});
app.on('before-quit', () => {
stopPythonServer();
});