libre-webui 0.19.5 → 0.19.6

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 (37) hide show
  1. package/backend/package.json +1 -1
  2. package/electron/main.js +196 -11
  3. package/frontend/dist/artifact-runtime/.build-fingerprint +1 -1
  4. package/frontend/dist/assets/{index-yeE8bw17.js → index-Cd0_AoYV.js} +19 -18
  5. package/frontend/dist/index.html +8 -8
  6. package/frontend/dist/js/{ArtifactContainer-BRqFYglf.js → ArtifactContainer-Dy_1KKWg.js} +1 -1
  7. package/frontend/dist/js/{ArtifactDemoPage-CQj2XRxx.js → ArtifactDemoPage-Big00RwH.js} +1 -1
  8. package/frontend/dist/js/{ArtifactSandboxFrame-BsrybHW3.js → ArtifactSandboxFrame-C_S4j076.js} +2 -2
  9. package/frontend/dist/js/{ArtifactSlideOutPanel-BcDkHDZY.js → ArtifactSlideOutPanel-B4-D3HkB.js} +1 -1
  10. package/frontend/dist/js/{ChatPage-CtN25kO5.js → ChatPage-BkFd1iac.js} +1 -1
  11. package/frontend/dist/js/{FirstTimeSetup-CX3zIxZR.js → FirstTimeSetup-C6YxrwR7.js} +1 -1
  12. package/frontend/dist/js/{GalleryPage-Dv5IJVVY.js → GalleryPage-BtcPs7SO.js} +1 -1
  13. package/frontend/dist/js/{HomePage-C7JjQJSz.js → HomePage-5PhpAt14.js} +1 -1
  14. package/frontend/dist/js/{LibreClawPage-iEHU_8KO.js → LibreClawPage-B14Go5Mn.js} +1 -1
  15. package/frontend/dist/js/{ModelSelector-DhRVhkDT.js → ModelSelector-C1wN91tl.js} +1 -1
  16. package/frontend/dist/js/{ModelsPage-BU-VYLs1.js → ModelsPage-Cc3L0ur8.js} +1 -1
  17. package/frontend/dist/js/{NotesPage-DKXMxPjC.js → NotesPage-BwEMW-Z3.js} +1 -1
  18. package/frontend/dist/js/{PersonasPage-DnjO6raM.js → PersonasPage-e-CbTxP3.js} +1 -1
  19. package/frontend/dist/js/{PluginUsagePage-DNihHooM.js → PluginUsagePage-CvZMJJwv.js} +1 -1
  20. package/frontend/dist/js/{RichMessageContent-DDJDx7_n.js → RichMessageContent-DNl3_CIw.js} +1 -1
  21. package/frontend/dist/js/{SettingsModal-Djij7CKl.js → SettingsModal-C8KJJEud.js} +1 -1
  22. package/frontend/dist/js/{SystemPage-CDBXVff3.js → SystemPage-8kEAS71H.js} +1 -1
  23. package/frontend/dist/js/{UserManagementPage-CZJLqvwO.js → UserManagementPage-BSrZ0ucu.js} +1 -1
  24. package/frontend/dist/js/{WorkPage-EpH5lfJx.js → WorkPage-spf92PVN.js} +3 -3
  25. package/frontend/dist/js/{api-DVeiqNG9.js → api-MySpmAK8.js} +2 -2
  26. package/frontend/dist/js/{appStore-hNTtRnlO.js → appStore-BpoXhleX.js} +2 -2
  27. package/frontend/dist/js/{authStore-Cl_7F0Si.js → authStore-DmrydYuS.js} +1 -1
  28. package/frontend/dist/js/{chatStore-Bp5u1tTv.js → chatStore-CWqfsOjc.js} +2 -2
  29. package/frontend/dist/js/{config-cqTxMjWo.js → config-CuXlcWP1.js} +1 -1
  30. package/frontend/dist/js/{estree-BAxGO-Uf.js → estree-BuWOCMW0.js} +1 -1
  31. package/frontend/dist/js/{html-DL8k6nha.js → html-D7tW7UvW.js} +1 -1
  32. package/frontend/dist/js/{markdown-CKunEqMI.js → markdown-CBSxWEtK.js} +1 -1
  33. package/frontend/dist/js/{messageCodeStyles-Cva_Rdk3.js → messageCodeStyles-BlTi46wI.js} +1 -1
  34. package/frontend/dist/js/{standalone-qHCG6_XI.js → standalone-CaXys7q3.js} +1 -1
  35. package/frontend/dist/js/{typescript-ClQp1SgP.js → typescript-DQ5zi5zo.js} +1 -1
  36. package/frontend/dist/js/{ui-ChYGf2Rg.js → ui-DFQiHo_k.js} +2 -2
  37. package/package.json +4 -1
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "libre-webui-backend",
3
- "version": "0.19.5",
3
+ "version": "0.19.6",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "description": "Backend API for Libre WebUI",
package/electron/main.js CHANGED
@@ -15,11 +15,14 @@ const {
15
15
  shell,
16
16
  Menu,
17
17
  dialog,
18
+ ipcMain,
18
19
  nativeTheme,
19
20
  nativeImage,
20
21
  } = require('electron');
21
22
  const path = require('path');
23
+ const fs = require('fs');
22
24
  const http = require('http');
25
+ const https = require('https');
23
26
  const { spawn } = require('child_process');
24
27
 
25
28
  // Get icon path for Linux (icons are in extraResources for production)
@@ -145,6 +148,179 @@ function createMainWindow() {
145
148
  return mainWindow;
146
149
  }
147
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
+
148
324
  // Check if backend is running
149
325
  async function checkBackend() {
150
326
  return new Promise(resolve => {
@@ -209,6 +385,13 @@ function createMenu() {
209
385
  }
210
386
  },
211
387
  },
388
+ {
389
+ label: 'Switch Server…',
390
+ click: () => {
391
+ fs.rmSync(getLandingChoicePath(), { force: true });
392
+ if (mainWindow) showLanding(mainWindow);
393
+ },
394
+ },
212
395
  { type: 'separator' },
213
396
  { role: 'services' },
214
397
  { type: 'separator' },
@@ -305,18 +488,20 @@ async function main() {
305
488
  // Create main window
306
489
  const window = createMainWindow();
307
490
 
308
- // Load the app
309
- if (isDev) {
310
- // In development, load from Vite dev server
311
- window.loadURL(`http://localhost:${FRONTEND_PORT}`);
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
+ }
312
503
  } else {
313
- // In production, load the built frontend
314
- const frontendPath = getResourcePath('frontend', 'dist', 'index.html');
315
- console.log('Loading frontend from:', frontendPath);
316
- window.loadFile(frontendPath).catch(err => {
317
- console.error('Failed to load frontend:', err);
318
- dialog.showErrorBox('Load Error', `Could not load app: ${err.message}`);
319
- });
504
+ loadLocalApp(window);
320
505
  }
321
506
 
322
507
  // Open DevTools in dev mode
@@ -1 +1 @@
1
- 4c0e1b4a838123baaffe3b0ef977b01674004244c3ed45e6fdd1e56c8c7def15
1
+ 793cdf43cecd594b17579f14b2300d8dba5e378ac842a2490a94238d5619e6a8