@lobehub/lobehub 2.0.0-next.325 → 2.0.0-next.327

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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,56 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## [Version 2.0.0-next.327](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.326...v2.0.0-next.327)
6
+
7
+ <sup>Released on **2026-01-20**</sup>
8
+
9
+ #### ♻ Code Refactoring
10
+
11
+ - **model-select**: Migrate FunctionCallingModelSelect to LobeSelect.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### Code refactoring
19
+
20
+ - **model-select**: Migrate FunctionCallingModelSelect to LobeSelect, closes [#11664](https://github.com/lobehub/lobe-chat/issues/11664) ([ad51305](https://github.com/lobehub/lobe-chat/commit/ad51305))
21
+
22
+ </details>
23
+
24
+ <div align="right">
25
+
26
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
27
+
28
+ </div>
29
+
30
+ ## [Version 2.0.0-next.326](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.325...v2.0.0-next.326)
31
+
32
+ <sup>Released on **2026-01-20**</sup>
33
+
34
+ #### 🐛 Bug Fixes
35
+
36
+ - **desktop**: Gracefully handle missing update manifest 404 errors.
37
+
38
+ <br/>
39
+
40
+ <details>
41
+ <summary><kbd>Improvements and Fixes</kbd></summary>
42
+
43
+ #### What's fixed
44
+
45
+ - **desktop**: Gracefully handle missing update manifest 404 errors, closes [#11625](https://github.com/lobehub/lobe-chat/issues/11625) ([13e95b9](https://github.com/lobehub/lobe-chat/commit/13e95b9))
46
+
47
+ </details>
48
+
49
+ <div align="right">
50
+
51
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
52
+
53
+ </div>
54
+
5
55
  ## [Version 2.0.0-next.325](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.324...v2.0.0-next.325)
6
56
 
7
57
  <sup>Released on **2026-01-20**</sup>
@@ -1,3 +1,5 @@
1
+ import type { UpdateInfo } from '@lobechat/electron-client-ipc';
2
+ import { app as electronApp } from 'electron';
1
3
  import log from 'electron-log';
2
4
  import { autoUpdater } from 'electron-updater';
3
5
 
@@ -120,10 +122,22 @@ export class UpdaterManager {
120
122
  try {
121
123
  await autoUpdater.checkForUpdates();
122
124
  } catch (error) {
123
- logger.error('Error checking for updates:', error.message);
125
+ const message = error instanceof Error ? error.message : String(error);
126
+
127
+ // Edge case: Release tag exists but update manifest assets (latest/stable-*.yml) aren't uploaded yet.
128
+ // Treat this gap period as "no updates available" instead of a user-facing error.
129
+ if (this.isMissingUpdateManifestError(error)) {
130
+ logger.warn('[Updater] Update manifest not ready yet, treating as no update:', message);
131
+ if (manual) {
132
+ this.mainWindow.broadcast('manualUpdateNotAvailable', this.getCurrentUpdateInfo());
133
+ }
134
+ return;
135
+ }
136
+
137
+ logger.error('Error checking for updates:', message);
124
138
 
125
139
  if (manual) {
126
- this.mainWindow.broadcast('updateError', (error as Error).message);
140
+ this.mainWindow.broadcast('updateError', message);
127
141
  }
128
142
  } finally {
129
143
  this.checking = false;
@@ -397,6 +411,18 @@ export class UpdaterManager {
397
411
  });
398
412
 
399
413
  autoUpdater.on('error', async (err) => {
414
+ const message = err instanceof Error ? err.message : String(err);
415
+
416
+ // Edge case: Release tag exists but update manifest assets aren't uploaded yet.
417
+ // Skip fallback switching and avoid user-facing errors.
418
+ if (this.isMissingUpdateManifestError(err)) {
419
+ logger.warn('[Updater] Update manifest not ready yet, skipping error handling:', message);
420
+ if (this.isManualCheck) {
421
+ this.mainWindow.broadcast('manualUpdateNotAvailable', this.getCurrentUpdateInfo());
422
+ }
423
+ return;
424
+ }
425
+
400
426
  logger.error('Error in auto-updater:', err);
401
427
  logger.error('[Updater Error Context] Channel:', autoUpdater.channel);
402
428
  logger.error('[Updater Error Context] allowPrerelease:', autoUpdater.allowPrerelease);
@@ -436,4 +462,28 @@ export class UpdaterManager {
436
462
 
437
463
  logger.debug('Updater events registered');
438
464
  }
465
+
466
+ private isMissingUpdateManifestError(error: unknown): boolean {
467
+ const message = error instanceof Error ? error.message : String(error ?? '');
468
+ if (!message) return false;
469
+
470
+ // Expect patterns like:
471
+ // - "Cannot find latest-mac.yml ... HttpError: 404 ..."
472
+ // - "Cannot find stable.yml ... 404 ..."
473
+ if (!/cannot find/i.test(message)) return false;
474
+ if (!/\b404\b/.test(message)) return false;
475
+
476
+ // Match channel manifest filenames across platforms/architectures:
477
+ // latest.yml, latest-mac.yml, latest-linux.yml, stable.yml, stable-mac.yml, etc.
478
+ const manifestMatch = message.match(/\b(?:latest|stable)(?:-[\da-z]+)?\.yml\b/i);
479
+ return Boolean(manifestMatch);
480
+ }
481
+
482
+ private getCurrentUpdateInfo(): UpdateInfo {
483
+ const version = autoUpdater.currentVersion?.version || electronApp.getVersion();
484
+ return {
485
+ releaseDate: new Date().toISOString(),
486
+ version,
487
+ };
488
+ }
439
489
  }
@@ -31,6 +31,7 @@ vi.mock('electron-updater', () => ({
31
31
  autoInstallOnAppQuit: false,
32
32
  channel: 'stable',
33
33
  checkForUpdates: vi.fn(),
34
+ currentVersion: undefined as any,
34
35
  downloadUpdate: vi.fn(),
35
36
  forceDevUpdateConfig: false,
36
37
  logger: null as any,
@@ -46,6 +47,7 @@ vi.mock('electron', () => ({
46
47
  getAllWindows: mockGetAllWindows,
47
48
  },
48
49
  app: {
50
+ getVersion: vi.fn().mockReturnValue('0.0.0'),
49
51
  releaseSingleInstanceLock: mockReleaseSingleInstanceLock,
50
52
  },
51
53
  }));
@@ -108,6 +110,7 @@ describe('UpdaterManager', () => {
108
110
  (autoUpdater as any).allowPrerelease = false;
109
111
  (autoUpdater as any).allowDowngrade = false;
110
112
  (autoUpdater as any).forceDevUpdateConfig = false;
113
+ (autoUpdater as any).currentVersion = undefined;
111
114
 
112
115
  // Capture registered events
113
116
  registeredEvents = new Map();
@@ -212,6 +215,24 @@ describe('UpdaterManager', () => {
212
215
 
213
216
  expect(mockBroadcast).toHaveBeenCalledWith('updateError', 'Network error');
214
217
  });
218
+
219
+ it('should treat missing latest/stable yml 404 as not-available during manual check', async () => {
220
+ const error = new Error(
221
+ 'Cannot find latest-mac.yml in the latest release artifacts (https://github.com/lobehub/lobe-chat/releases/download/v2.0.0-next.311/latest-mac.yml): HttpError: 404',
222
+ );
223
+ vi.mocked(autoUpdater.checkForUpdates).mockRejectedValueOnce(error);
224
+
225
+ await updaterManager.checkForUpdates({ manual: true });
226
+
227
+ expect(mockBroadcast).toHaveBeenCalledWith(
228
+ 'manualUpdateNotAvailable',
229
+ expect.objectContaining({
230
+ releaseDate: expect.any(String),
231
+ version: expect.any(String),
232
+ }),
233
+ );
234
+ expect(mockBroadcast).not.toHaveBeenCalledWith('updateError', expect.anything());
235
+ });
215
236
  });
216
237
 
217
238
  describe('downloadUpdate', () => {
@@ -486,6 +507,26 @@ describe('UpdaterManager', () => {
486
507
 
487
508
  expect(mockBroadcast).not.toHaveBeenCalledWith('updateError', expect.anything());
488
509
  });
510
+
511
+ it('should not broadcast updateError for missing manifest 404 (gap period)', async () => {
512
+ vi.mocked(autoUpdater.checkForUpdates).mockResolvedValue({} as any);
513
+ await updaterManager.checkForUpdates({ manual: true });
514
+
515
+ const error = new Error(
516
+ 'Cannot find latest-mac.yml in the latest release artifacts (https://github.com/lobehub/lobe-chat/releases/download/v2.0.0-next.311/latest-mac.yml): HttpError: 404',
517
+ );
518
+ const handler = registeredEvents.get('error');
519
+ await handler?.(error);
520
+
521
+ expect(mockBroadcast).toHaveBeenCalledWith(
522
+ 'manualUpdateNotAvailable',
523
+ expect.objectContaining({
524
+ releaseDate: expect.any(String),
525
+ version: expect.any(String),
526
+ }),
527
+ );
528
+ expect(mockBroadcast).not.toHaveBeenCalledWith('updateError', expect.anything());
529
+ });
489
530
  });
490
531
  });
491
532
 
package/changelog/v1.json CHANGED
@@ -1,4 +1,14 @@
1
1
  [
2
+ {
3
+ "children": {},
4
+ "date": "2026-01-20",
5
+ "version": "2.0.0-next.327"
6
+ },
7
+ {
8
+ "children": {},
9
+ "date": "2026-01-20",
10
+ "version": "2.0.0-next.326"
11
+ },
2
12
  {
3
13
  "children": {},
4
14
  "date": "2026-01-20",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/lobehub",
3
- "version": "2.0.0-next.325",
3
+ "version": "2.0.0-next.327",
4
4
  "description": "LobeHub - an open-source,comprehensive AI Agent framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
5
5
  "keywords": [
6
6
  "framework",
@@ -1,6 +1,6 @@
1
- import { Select, type SelectProps, TooltipGroup } from '@lobehub/ui';
1
+ import { LobeSelect, type LobeSelectProps, TooltipGroup } from '@lobehub/ui';
2
2
  import { createStaticStyles } from 'antd-style';
3
- import { memo, useMemo } from 'react';
3
+ import { type ReactNode, memo, useMemo } from 'react';
4
4
 
5
5
  import { ModelItemRender, ProviderItemRender } from '@/components/ModelSelect';
6
6
  import { useEnabledChatModels } from '@/hooks/useEnabledChatModels';
@@ -18,12 +18,12 @@ const styles = createStaticStyles(({ css }) => ({
18
18
  }));
19
19
 
20
20
  interface ModelOption {
21
- label: any;
21
+ label: ReactNode;
22
22
  provider: string;
23
23
  value: string;
24
24
  }
25
25
 
26
- interface ModelSelectProps extends SelectProps {
26
+ interface ModelSelectProps extends Omit<LobeSelectProps, 'onChange' | 'value'> {
27
27
  onChange?: (props: WorkingModel) => void;
28
28
  showAbility?: boolean;
29
29
  value?: WorkingModel;
@@ -32,7 +32,7 @@ interface ModelSelectProps extends SelectProps {
32
32
  const ModelSelect = memo<ModelSelectProps>(({ value, onChange, ...rest }) => {
33
33
  const enabledList = useEnabledChatModels();
34
34
 
35
- const options = useMemo<SelectProps['options']>(() => {
35
+ const options = useMemo<LobeSelectProps['options']>(() => {
36
36
  const getChatModels = (provider: EnabledProviderWithModels) =>
37
37
  provider.children
38
38
  .filter((model) => !!model.abilities.functionCall)
@@ -69,7 +69,7 @@ const ModelSelect = memo<ModelSelectProps>(({ value, onChange, ...rest }) => {
69
69
 
70
70
  return (
71
71
  <TooltipGroup>
72
- <Select
72
+ <LobeSelect
73
73
  onChange={(value, option) => {
74
74
  const model = value.split('/').slice(1).join('/');
75
75
  onChange?.({ model, provider: (option as unknown as ModelOption).provider });
@@ -17,9 +17,7 @@ interface TaskDetailPanelProps {
17
17
  }
18
18
 
19
19
  const TaskDetailPanel = memo<TaskDetailPanelProps>(({ taskDetail, content, messageId }) => {
20
- return (
21
- <StatusContent content={content} messageId={messageId} taskDetail={taskDetail} />
22
- );
20
+ return <StatusContent content={content} messageId={messageId} taskDetail={taskDetail} />;
23
21
  });
24
22
 
25
23
  TaskDetailPanel.displayName = 'TaskDetailPanel';
@@ -8,6 +8,8 @@ import { type ReactNode, memo } from 'react';
8
8
  import { flushSync } from 'react-dom';
9
9
  import { useNavigate } from 'react-router-dom';
10
10
 
11
+ import { isDesktop } from '@/const/version';
12
+
11
13
  import ToggleLeftPanelButton from './ToggleLeftPanelButton';
12
14
  import BackButton from './components/BackButton';
13
15
 
@@ -35,7 +37,7 @@ const styles = createStaticStyles(({ css, cssVar }) => ({
35
37
  `,
36
38
  container: css`
37
39
  overflow: hidden;
38
- margin-block-start: 8px;
40
+ margin-block-start: ${isDesktop ? '' : '8px'};
39
41
  `,
40
42
  }));
41
43