libre-webui 0.21.1 → 0.21.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/backend/dist/routes/documents.d.ts.map +1 -1
  2. package/backend/dist/routes/documents.js +2 -0
  3. package/backend/dist/routes/documents.js.map +1 -1
  4. package/backend/dist/services/ollamaService.d.ts +11 -1
  5. package/backend/dist/services/ollamaService.d.ts.map +1 -1
  6. package/backend/dist/services/ollamaService.js +101 -3
  7. package/backend/dist/services/ollamaService.js.map +1 -1
  8. package/backend/dist/services/titleGenerationService.d.ts.map +1 -1
  9. package/backend/dist/services/titleGenerationService.js +14 -3
  10. package/backend/dist/services/titleGenerationService.js.map +1 -1
  11. package/backend/package.json +1 -1
  12. package/frontend/dist/artifact-runtime/.build-fingerprint +1 -1
  13. package/frontend/dist/assets/index-BBpNbfKW.js +16 -0
  14. package/frontend/dist/assets/index-BLATyFmD.css +1 -0
  15. package/frontend/dist/index.html +41 -16
  16. package/frontend/dist/js/ArtifactContainer-Dy2Q0iIL.js +1 -0
  17. package/frontend/dist/js/{ArtifactDemoPage-DQBTKgzC.js → ArtifactDemoPage-K79dWshB.js} +1 -1
  18. package/frontend/dist/js/{ArtifactSandboxFrame-CpblpEf4.js → ArtifactSandboxFrame-DhqLNUtY.js} +2 -2
  19. package/frontend/dist/js/ArtifactSlideOutPanel-Cl8JG7bz.js +1 -0
  20. package/frontend/dist/js/{Button-X9TT9gTD.js → Button-CTXHujOd.js} +1 -1
  21. package/frontend/dist/js/ChatPage-BBayfMLS.js +16 -0
  22. package/frontend/dist/js/{FirstTimeSetup-C3ldEPmK.js → FirstTimeSetup-kHhRzGJ8.js} +1 -1
  23. package/frontend/dist/js/{GalleryPage-DrrOhli8.js → GalleryPage-By8VI39Q.js} +1 -1
  24. package/frontend/dist/js/HomePage-C1L8WAXL.js +1 -0
  25. package/frontend/dist/js/LibreClawPage-Dyua8IUu.js +1 -0
  26. package/frontend/dist/js/{ModelManagerModals-D-NxeVvV.js → ModelManagerModals-CuMRGUd-.js} +1 -1
  27. package/frontend/dist/js/ModelSelector-CqHc-Ln4.js +1 -0
  28. package/frontend/dist/js/ModelsPage-ODeXhDDk.js +2 -0
  29. package/frontend/dist/js/{NotesPage-D10aWpVj.js → NotesPage-CApzDjOq.js} +1 -1
  30. package/frontend/dist/js/{PersonasPage-BvCPzwx2.js → PersonasPage-D_6WBCH5.js} +2 -2
  31. package/frontend/dist/js/{PluginUsagePage-B9YgizW8.js → PluginUsagePage-kutS6SOd.js} +1 -1
  32. package/frontend/dist/js/{RichMessageContent-2L-mHp9Q.js → RichMessageContent-g6WT75Te.js} +1 -1
  33. package/frontend/dist/js/SettingsModal-B7z51BCx.js +1 -0
  34. package/frontend/dist/js/{SettingsToggle-bZ5W4CCf.js → SettingsToggle-DLHlCDBl.js} +1 -1
  35. package/frontend/dist/js/{SystemPage-CKnhy0Fr.js → SystemPage-BMWEet9e.js} +1 -1
  36. package/frontend/dist/js/{UserManagementPage-B3gA_Zx8.js → UserManagementPage-D08lM51c.js} +1 -1
  37. package/frontend/dist/js/WorkPage-DdVLefsK.js +6 -0
  38. package/frontend/dist/js/{api-DjQINB7B.js → api-CLEmGcEh.js} +1 -1
  39. package/frontend/dist/js/{appStore-CIV0DhiE.js → appStore-CGdhvp9P.js} +2 -2
  40. package/frontend/dist/js/ar-BTmB2WVm.js +2 -0
  41. package/frontend/dist/js/{authStore-pmXfK_qt.js → authStore-Dh3beEOq.js} +3 -3
  42. package/frontend/dist/js/{bn-C7CHC42H.js → bn-CQnH7YVT.js} +2 -2
  43. package/frontend/dist/js/chatStore-CDeYWL_q.js +3 -0
  44. package/frontend/dist/js/{config-DF-g5uwS.js → config-DkV1ka7I.js} +1 -1
  45. package/frontend/dist/js/{cs-zGEtwQn_.js → cs-oTTZkQXr.js} +2 -2
  46. package/frontend/dist/js/{da-B-0sHNTu.js → da-Br0gB6J6.js} +2 -2
  47. package/frontend/dist/js/{de-UZNE6t2h.js → de-DE2iztTF.js} +2 -2
  48. package/frontend/dist/js/{es-CPLnFYIw.js → es-DgthfVtl.js} +2 -2
  49. package/frontend/dist/js/{estree-livw6QLA.js → estree-D5KP-qbw.js} +1 -1
  50. package/frontend/dist/js/{fr-Br1o0WVz.js → fr-BjxjFwPh.js} +2 -2
  51. package/frontend/dist/js/{hi-BcEvNEpb.js → hi-D1q7E-9N.js} +2 -2
  52. package/frontend/dist/js/{html-CWlKlrlk.js → html-Cm9c2akd.js} +1 -1
  53. package/frontend/dist/js/{id-C30L2DoT.js → id-CDJZqLTF.js} +2 -2
  54. package/frontend/dist/js/{is-BAEhfi5Z.js → is-BU7Um_Tq.js} +2 -2
  55. package/frontend/dist/js/{it-HWnfQXZY.js → it-CdAxr8P_.js} +2 -2
  56. package/frontend/dist/js/ja-BLbQwz9u.js +2 -0
  57. package/frontend/dist/js/{ko-agDQ__KP.js → ko-CY5lpkAe.js} +2 -2
  58. package/frontend/dist/js/{markdown-BM75gisb.js → markdown-B2u7CnKN.js} +1 -1
  59. package/frontend/dist/js/{messageCodeStyles-DxPb6SKm.js → messageCodeStyles-DalG2j6U.js} +2 -2
  60. package/frontend/dist/js/{ms-aNRa2QX7.js → ms-tQ8AYXEQ.js} +2 -2
  61. package/frontend/dist/js/{nl-BtkiDUn-.js → nl-CRNi2oWT.js} +2 -2
  62. package/frontend/dist/js/{pl-SEO0lBnX.js → pl-CWtFRS_W.js} +2 -2
  63. package/frontend/dist/js/{pt-BYtjmvq1.js → pt-C7SPoLF8.js} +2 -2
  64. package/frontend/dist/js/{ru-B-ynj2hU.js → ru-Dz4AX8xc.js} +2 -2
  65. package/frontend/dist/js/{standalone-h0KNQ5yy.js → standalone-Dwm1daef.js} +1 -1
  66. package/frontend/dist/js/{sv-CliKR15U.js → sv-ETy5-boa.js} +2 -2
  67. package/frontend/dist/js/{th-DJQyDr9J.js → th-DpuMqvxU.js} +2 -2
  68. package/frontend/dist/js/{tr-BBGKZ1la.js → tr-C-1AYAqg.js} +2 -2
  69. package/frontend/dist/js/{typescript-CPI0lY4n.js → typescript-CwieSptM.js} +1 -1
  70. package/frontend/dist/js/{ui-ovAoLjDQ.js → ui-BnDZpjfc.js} +2 -2
  71. package/frontend/dist/js/{uk-C1GcFWxE.js → uk-CyS680gr.js} +2 -2
  72. package/frontend/dist/js/useTranslation-CXY69qNk.js +1 -0
  73. package/frontend/dist/js/utils-BM0_0iFj.js +1 -0
  74. package/frontend/dist/js/{vi-CIxgICOQ.js → vi-BG8HuiTz.js} +2 -2
  75. package/frontend/dist/js/{workApi-CCHIEahA.js → workApi-BOS7crs9.js} +2 -2
  76. package/frontend/dist/js/{zh-Cm7vsQ9s.js → zh-NCs2mkHj.js} +2 -2
  77. package/package.json +3 -20
  78. package/electron/main.js +0 -560
  79. package/frontend/dist/assets/index-BN-WAt2j.js +0 -2
  80. package/frontend/dist/assets/index-D7m1zWWg.css +0 -1
  81. package/frontend/dist/js/ArtifactContainer-BD20LheX.js +0 -1
  82. package/frontend/dist/js/ArtifactSlideOutPanel-BVgr8Cv6.js +0 -1
  83. package/frontend/dist/js/ChatPage-D2EWlzO4.js +0 -16
  84. package/frontend/dist/js/HomePage-D21Haon_.js +0 -1
  85. package/frontend/dist/js/LibreClawPage-CEf9ws8z.js +0 -1
  86. package/frontend/dist/js/ModelSelector-whWBlN-S.js +0 -1
  87. package/frontend/dist/js/ModelsPage-CL2t9qNT.js +0 -2
  88. package/frontend/dist/js/SettingsModal-BK05P_rU.js +0 -1
  89. package/frontend/dist/js/WorkPage-Bj3UVrmb.js +0 -5
  90. package/frontend/dist/js/ar-Bm27TCMQ.js +0 -2
  91. package/frontend/dist/js/chatStore-D76rdaV6.js +0 -2
  92. package/frontend/dist/js/i18n-DJTg6xWw.js +0 -2
  93. package/frontend/dist/js/ja-Dvux1mon.js +0 -2
  94. package/frontend/dist/js/useTranslation-CdeLZqSW.js +0 -1
  95. package/frontend/dist/js/utils-BhpNrGnR.js +0 -1
package/electron/main.js DELETED
@@ -1,560 +0,0 @@
1
- /*
2
- * Libre WebUI - Electron Main Process
3
- * Copyright (C) 2025 Kroonen AI, Inc.
4
- *
5
- * Licensed under the Apache License, Version 2.0 (the "License");
6
- * you may not use this file except in compliance with the License.
7
- * You may obtain a copy of the License at:
8
- *
9
- * http://www.apache.org/licenses/LICENSE-2.0
10
- */
11
-
12
- const {
13
- app,
14
- BrowserWindow,
15
- shell,
16
- Menu,
17
- dialog,
18
- ipcMain,
19
- nativeTheme,
20
- nativeImage,
21
- } = require('electron');
22
- const path = require('path');
23
- const fs = require('fs');
24
- const http = require('http');
25
- const https = require('https');
26
- const { spawn } = require('child_process');
27
-
28
- // Get icon path for Linux (icons are in extraResources for production)
29
- const getIconPath = () => {
30
- const isDev = process.env.NODE_ENV === 'development' || !app.isPackaged;
31
- if (isDev) {
32
- return path.join(__dirname, 'assets', 'icons', '256x256.png');
33
- }
34
- return path.join(process.resourcesPath, 'icons', '256x256.png');
35
- };
36
-
37
- // Set app icon for Linux About dialog
38
- if (process.platform === 'linux') {
39
- app.whenReady().then(() => {
40
- const iconPath = getIconPath();
41
- app.setAboutPanelOptions({
42
- applicationName: 'Libre WebUI',
43
- applicationVersion: app.getVersion(),
44
- copyright: 'Copyright © 2025 Kroonen AI, Inc.',
45
- iconPath: iconPath,
46
- });
47
- });
48
- }
49
-
50
- // Prevent multiple instances (fixes fork bomb issue)
51
- const gotTheLock = app.requestSingleInstanceLock();
52
- if (!gotTheLock) {
53
- app.quit();
54
- process.exit(0);
55
- }
56
-
57
- // Keep references to prevent garbage collection
58
- let mainWindow = null;
59
- let splashWindow = null;
60
- let backendProcess = null;
61
-
62
- const isDev = process.env.NODE_ENV === 'development' || !app.isPackaged;
63
- const BACKEND_PORT = process.env.PORT || 3001;
64
- const FRONTEND_PORT = 5173;
65
-
66
- // Paths
67
- const getResourcePath = (...segments) => {
68
- if (isDev) {
69
- return path.join(__dirname, '..', ...segments);
70
- }
71
- return path.join(process.resourcesPath, ...segments);
72
- };
73
-
74
- // Create splash screen while loading
75
- function createSplashWindow() {
76
- splashWindow = new BrowserWindow({
77
- width: 400,
78
- height: 300,
79
- frame: false,
80
- transparent: true,
81
- alwaysOnTop: true,
82
- skipTaskbar: true,
83
- resizable: false,
84
- webPreferences: {
85
- nodeIntegration: false,
86
- contextIsolation: true,
87
- },
88
- });
89
-
90
- splashWindow.loadFile(path.join(__dirname, 'splash.html'));
91
- splashWindow.center();
92
- }
93
-
94
- // Create the main application window
95
- function createMainWindow() {
96
- // Get icon for Linux
97
- let windowIcon;
98
- if (process.platform === 'linux') {
99
- const iconPath = getIconPath();
100
- windowIcon = nativeImage.createFromPath(iconPath);
101
- }
102
-
103
- mainWindow = new BrowserWindow({
104
- width: 1400,
105
- height: 900,
106
- minWidth: 800,
107
- minHeight: 600,
108
- show: false,
109
- icon: windowIcon,
110
- titleBarStyle: 'hiddenInset',
111
- trafficLightPosition: { x: 12, y: 12 },
112
- backgroundColor: nativeTheme.shouldUseDarkColors ? '#1a1a1a' : '#ffffff',
113
- webPreferences: {
114
- nodeIntegration: false,
115
- contextIsolation: true,
116
- webSecurity: true,
117
- preload: path.join(__dirname, 'preload.js'),
118
- },
119
- });
120
-
121
- // Handle external links
122
- mainWindow.webContents.setWindowOpenHandler(({ url }) => {
123
- try {
124
- const target = new URL(url);
125
- if (target.protocol === 'http:' || target.protocol === 'https:') {
126
- shell.openExternal(target.toString());
127
- }
128
- } catch {
129
- // Refuse malformed and non-web schemes.
130
- }
131
- return { action: 'deny' };
132
- });
133
-
134
- // Show window when ready
135
- mainWindow.once('ready-to-show', () => {
136
- if (splashWindow) {
137
- splashWindow.close();
138
- splashWindow = null;
139
- }
140
- mainWindow.show();
141
- mainWindow.focus();
142
- });
143
-
144
- mainWindow.on('closed', () => {
145
- mainWindow = null;
146
- });
147
-
148
- return mainWindow;
149
- }
150
-
151
- // Where the last landing choice is remembered: {mode: 'local'} or
152
- // {mode: 'remote', url}. Absent means the landing page decides.
153
- const getLandingChoicePath = () =>
154
- path.join(app.getPath('userData'), 'landing.json');
155
-
156
- function readLandingChoice() {
157
- try {
158
- const choice = JSON.parse(fs.readFileSync(getLandingChoicePath(), 'utf8'));
159
- if (choice && choice.mode === 'local') return choice;
160
- if (
161
- choice &&
162
- choice.mode === 'remote' &&
163
- typeof choice.url === 'string' &&
164
- /^https?:\/\//.test(choice.url)
165
- ) {
166
- return choice;
167
- }
168
- } catch {
169
- // No saved choice, or an unreadable one: the landing page decides.
170
- }
171
- return null;
172
- }
173
-
174
- function saveLandingChoice(choice) {
175
- try {
176
- fs.mkdirSync(app.getPath('userData'), { recursive: true });
177
- fs.writeFileSync(getLandingChoicePath(), JSON.stringify(choice));
178
- } catch (error) {
179
- console.error('Could not remember the landing choice:', error);
180
- }
181
- }
182
-
183
- // A server qualifies when its health endpoint answers. Both the Docker image
184
- // and a bare `npm run dev:backend` expose /health at the root.
185
- function probeServer(baseUrl) {
186
- return new Promise(resolve => {
187
- let target;
188
- try {
189
- target = new URL('/health', baseUrl);
190
- } catch {
191
- resolve(false);
192
- return;
193
- }
194
- const client = target.protocol === 'https:' ? https : http;
195
- const request = client.get(target.toString(), response => {
196
- response.resume();
197
- resolve(response.statusCode === 200);
198
- });
199
- request.on('error', () => resolve(false));
200
- request.setTimeout(5000, () => {
201
- request.destroy();
202
- resolve(false);
203
- });
204
- });
205
- }
206
-
207
- // Only the bundled landing page may steer the window; content loaded from a
208
- // server must not be able to invoke these channels.
209
- const isLandingSender = event => {
210
- try {
211
- const senderUrl = new URL(event.senderFrame.url);
212
- return (
213
- senderUrl.protocol === 'file:' &&
214
- senderUrl.pathname.endsWith('/landing.html')
215
- );
216
- } catch {
217
- return false;
218
- }
219
- };
220
-
221
- function loadLocalApp(window) {
222
- if (isDev) {
223
- window.loadURL(`http://localhost:${FRONTEND_PORT}`);
224
- return;
225
- }
226
- const frontendPath = getResourcePath('frontend', 'dist', 'index.html');
227
- window.loadFile(frontendPath).catch(err => {
228
- console.error('Failed to load frontend:', err);
229
- dialog.showErrorBox('Load Error', `Could not load app: ${err.message}`);
230
- });
231
- }
232
-
233
- function showLanding(window) {
234
- window.loadFile(path.join(__dirname, 'landing.html'));
235
- }
236
-
237
- // A tolerant version lookup for the setup screen; the system-info endpoint is
238
- // public and answers before any login.
239
- function fetchLocalVersion() {
240
- return new Promise(resolve => {
241
- const request = http.get(
242
- `http://localhost:${BACKEND_PORT}/api/auth/system-info`,
243
- response => {
244
- let body = '';
245
- response.on('data', chunk => {
246
- body += chunk;
247
- });
248
- response.on('end', () => {
249
- try {
250
- resolve(JSON.parse(body).data.version || null);
251
- } catch {
252
- resolve(null);
253
- }
254
- });
255
- }
256
- );
257
- request.on('error', () => resolve(null));
258
- request.setTimeout(2000, () => {
259
- request.destroy();
260
- resolve(null);
261
- });
262
- });
263
- }
264
-
265
- function registerLandingHandlers() {
266
- ipcMain.on('landing-launch-local', event => {
267
- if (!isLandingSender(event) || !mainWindow) return;
268
- saveLandingChoice({ mode: 'local' });
269
- loadLocalApp(mainWindow);
270
- });
271
-
272
- ipcMain.handle('landing-probe-local', async event => {
273
- if (!isLandingSender(event)) return { healthy: false, version: null };
274
- const healthy = await probeServer(`http://localhost:${BACKEND_PORT}`);
275
- const version = healthy ? await fetchLocalVersion() : null;
276
- return { healthy, version };
277
- });
278
-
279
- // The preload has always exposed getVersion; answer it.
280
- ipcMain.handle('get-version', () => app.getVersion());
281
-
282
- // The landing page's guidance links only; arbitrary pages stay out.
283
- const LANDING_LINKS = new Set([
284
- 'https://docs.librewebui.org/DOCKER/',
285
- 'https://docs.docker.com/get-docker/',
286
- 'https://ollama.com/download',
287
- 'https://librewebui.org',
288
- 'https://docs.librewebui.org',
289
- 'https://kroonen.ai',
290
- ]);
291
- ipcMain.on('landing-open-external', (event, url) => {
292
- if (!isLandingSender(event)) return;
293
- if (LANDING_LINKS.has(url)) shell.openExternal(url);
294
- });
295
-
296
- ipcMain.handle('landing-connect', async (event, rawUrl) => {
297
- if (!isLandingSender(event) || !mainWindow) {
298
- return { ok: false, error: 'Not available right now.' };
299
- }
300
- let url;
301
- try {
302
- url = new URL(String(rawUrl));
303
- } catch {
304
- return { ok: false, error: 'That is not a valid address.' };
305
- }
306
- if (url.protocol !== 'http:' && url.protocol !== 'https:') {
307
- return { ok: false, error: 'The address must start with http or https.' };
308
- }
309
- const base = url.toString().replace(/\/+$/, '');
310
- if (!(await probeServer(base))) {
311
- return {
312
- ok: false,
313
- error: 'No Libre WebUI server answered at that address.',
314
- };
315
- }
316
- saveLandingChoice({ mode: 'remote', url: base });
317
- mainWindow.loadURL(base);
318
- return { ok: true };
319
- });
320
- }
321
-
322
- registerLandingHandlers();
323
-
324
- // Check if backend is running
325
- async function checkBackend() {
326
- return new Promise(resolve => {
327
- const req = http.get(`http://localhost:${BACKEND_PORT}/api/health`, res => {
328
- resolve(res.statusCode === 200);
329
- });
330
- req.on('error', () => resolve(false));
331
- req.setTimeout(2000, () => {
332
- req.destroy();
333
- resolve(false);
334
- });
335
- });
336
- }
337
-
338
- // Start backend in a new Terminal window (macOS)
339
- function startBackendInTerminal() {
340
- const projectRoot = isDev
341
- ? path.join(__dirname, '..')
342
- : path.join(process.resourcesPath, '..');
343
-
344
- // Use AppleScript to open Terminal and run the backend
345
- const script = `
346
- tell application "Terminal"
347
- activate
348
- do script "cd '${projectRoot}' && npm run dev:backend"
349
- end tell
350
- `;
351
-
352
- backendProcess = spawn('osascript', ['-e', script], {
353
- detached: true,
354
- stdio: 'ignore',
355
- });
356
-
357
- backendProcess.unref();
358
- console.log('Started backend in Terminal');
359
- }
360
-
361
- // Stop backend process
362
- function stopBackend() {
363
- if (backendProcess) {
364
- backendProcess.kill();
365
- backendProcess = null;
366
- }
367
- }
368
-
369
- // Create application menu
370
- function createMenu() {
371
- const template = [
372
- {
373
- label: app.name,
374
- submenu: [
375
- { role: 'about' },
376
- { type: 'separator' },
377
- {
378
- label: 'Preferences...',
379
- accelerator: 'CmdOrCtrl+,',
380
- click: () => {
381
- if (mainWindow) {
382
- mainWindow.webContents.executeJavaScript(
383
- 'window.dispatchEvent(new CustomEvent("open-settings"))'
384
- );
385
- }
386
- },
387
- },
388
- {
389
- label: 'Switch Server…',
390
- click: () => {
391
- fs.rmSync(getLandingChoicePath(), { force: true });
392
- if (mainWindow) showLanding(mainWindow);
393
- },
394
- },
395
- { type: 'separator' },
396
- { role: 'services' },
397
- { type: 'separator' },
398
- { role: 'hide' },
399
- { role: 'hideOthers' },
400
- { role: 'unhide' },
401
- { type: 'separator' },
402
- { role: 'quit' },
403
- ],
404
- },
405
- {
406
- label: 'Edit',
407
- submenu: [
408
- { role: 'undo' },
409
- { role: 'redo' },
410
- { type: 'separator' },
411
- { role: 'cut' },
412
- { role: 'copy' },
413
- { role: 'paste' },
414
- { role: 'selectAll' },
415
- ],
416
- },
417
- {
418
- label: 'View',
419
- submenu: [
420
- { role: 'reload' },
421
- { role: 'forceReload' },
422
- { role: 'toggleDevTools' },
423
- { type: 'separator' },
424
- { role: 'resetZoom' },
425
- { role: 'zoomIn' },
426
- { role: 'zoomOut' },
427
- { type: 'separator' },
428
- { role: 'togglefullscreen' },
429
- ],
430
- },
431
- {
432
- label: 'Window',
433
- submenu: [
434
- { role: 'minimize' },
435
- { role: 'zoom' },
436
- { type: 'separator' },
437
- { role: 'front' },
438
- { type: 'separator' },
439
- { role: 'window' },
440
- ],
441
- },
442
- {
443
- label: 'Help',
444
- submenu: [
445
- {
446
- label: 'Documentation',
447
- click: () => shell.openExternal('https://librewebui.org'),
448
- },
449
- {
450
- label: 'GitHub',
451
- click: () =>
452
- shell.openExternal('https://github.com/libre-webui/libre-webui'),
453
- },
454
- { type: 'separator' },
455
- {
456
- label: 'Report Issue',
457
- click: () =>
458
- shell.openExternal(
459
- 'https://github.com/libre-webui/libre-webui/issues'
460
- ),
461
- },
462
- ],
463
- },
464
- ];
465
-
466
- const menu = Menu.buildFromTemplate(template);
467
- Menu.setApplicationMenu(menu);
468
- }
469
-
470
- // Main application startup
471
- async function main() {
472
- createSplashWindow();
473
- createMenu();
474
-
475
- try {
476
- // Check if backend is available
477
- const backendAvailable = await checkBackend();
478
- if (backendAvailable) {
479
- console.log('Backend server detected on port', BACKEND_PORT);
480
- } else {
481
- console.log(
482
- 'Backend not detected - please start it manually with: npm run dev:backend'
483
- );
484
- // Disabled auto-start in Terminal for now
485
- // startBackendInTerminal();
486
- }
487
-
488
- // Create main window
489
- const window = createMainWindow();
490
-
491
- // The landing page decides on first launch; afterwards the remembered
492
- // choice routes straight in. A remembered server that stops answering
493
- // returns to the landing page rather than a blank window.
494
- const choice = readLandingChoice();
495
- if (!choice) {
496
- showLanding(window);
497
- } else if (choice.mode === 'remote') {
498
- if (await probeServer(choice.url)) {
499
- window.loadURL(choice.url);
500
- } else {
501
- showLanding(window);
502
- }
503
- } else {
504
- loadLocalApp(window);
505
- }
506
-
507
- // Open DevTools in dev mode
508
- if (isDev) {
509
- window.webContents.openDevTools();
510
- }
511
- } catch (error) {
512
- console.error('Failed to start application:', error);
513
- dialog.showErrorBox(
514
- 'Startup Error',
515
- `Failed to start Libre WebUI: ${error.message}\n\nPlease check the logs and try again.`
516
- );
517
- app.quit();
518
- }
519
- }
520
-
521
- // App lifecycle events
522
- app.whenReady().then(main);
523
-
524
- // Focus existing window if second instance tries to launch
525
- app.on('second-instance', () => {
526
- if (mainWindow) {
527
- if (mainWindow.isMinimized()) mainWindow.restore();
528
- mainWindow.focus();
529
- }
530
- });
531
-
532
- app.on('window-all-closed', () => {
533
- stopBackend();
534
- if (process.platform !== 'darwin') {
535
- app.quit();
536
- }
537
- });
538
-
539
- app.on('before-quit', () => {
540
- stopBackend();
541
- });
542
-
543
- app.on('activate', () => {
544
- if (BrowserWindow.getAllWindows().length === 0) {
545
- main();
546
- }
547
- });
548
-
549
- // Handle certificate errors for localhost
550
- app.on(
551
- 'certificate-error',
552
- (event, _webContents, url, _error, _certificate, callback) => {
553
- if (url.startsWith('https://localhost')) {
554
- event.preventDefault();
555
- callback(true);
556
- } else {
557
- callback(false);
558
- }
559
- }
560
- );