@lobehub/lobehub 2.0.0-next.333 → 2.0.0-next.334

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,31 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## [Version 2.0.0-next.334](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.333...v2.0.0-next.334)
6
+
7
+ <sup>Released on **2026-01-21**</sup>
8
+
9
+ #### ✨ Features
10
+
11
+ - **misc**: Add platform-aware download client menu option.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's improved
19
+
20
+ - **misc**: Add platform-aware download client menu option, closes [#11676](https://github.com/lobehub/lobe-chat/issues/11676) ([55abddc](https://github.com/lobehub/lobe-chat/commit/55abddc))
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
+
5
30
  ## [Version 2.0.0-next.333](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.332...v2.0.0-next.333)
6
31
 
7
32
  <sup>Released on **2026-01-21**</sup>
package/changelog/v1.json CHANGED
@@ -1,4 +1,13 @@
1
1
  [
2
+ {
3
+ "children": {
4
+ "features": [
5
+ "Add platform-aware download client menu option."
6
+ ]
7
+ },
8
+ "date": "2026-01-21",
9
+ "version": "2.0.0-next.334"
10
+ },
2
11
  {
3
12
  "children": {
4
13
  "features": [
@@ -179,6 +179,7 @@
179
179
  "delete": "حذف",
180
180
  "document": "دليل المستخدم",
181
181
  "download": "تنزيل",
182
+ "downloadClient": "تنزيل العميل",
182
183
  "duplicate": "تكرار",
183
184
  "edit": "تعديل",
184
185
  "errors.invalidFileFormat": "تنسيق الملف غير صالح",
@@ -179,6 +179,7 @@
179
179
  "delete": "Изтрий",
180
180
  "document": "Ръководство за потребителя",
181
181
  "download": "Изтегли",
182
+ "downloadClient": "Изтегли клиент",
182
183
  "duplicate": "Дублирай",
183
184
  "edit": "Редактирай",
184
185
  "errors.invalidFileFormat": "Невалиден файлов формат",
@@ -179,6 +179,7 @@
179
179
  "delete": "Löschen",
180
180
  "document": "Benutzerhandbuch",
181
181
  "download": "Herunterladen",
182
+ "downloadClient": "Client herunterladen",
182
183
  "duplicate": "Duplizieren",
183
184
  "edit": "Bearbeiten",
184
185
  "errors.invalidFileFormat": "Ungültiges Dateiformat",
@@ -179,6 +179,7 @@
179
179
  "delete": "Delete",
180
180
  "document": "User Manual",
181
181
  "download": "Download",
182
+ "downloadClient": "Download Client",
182
183
  "duplicate": "Duplicate",
183
184
  "edit": "Edit",
184
185
  "errors.invalidFileFormat": "Invalid file format",
@@ -179,6 +179,7 @@
179
179
  "delete": "Eliminar",
180
180
  "document": "Manual de usuario",
181
181
  "download": "Descargar",
182
+ "downloadClient": "Descargar cliente",
182
183
  "duplicate": "Duplicar",
183
184
  "edit": "Editar",
184
185
  "errors.invalidFileFormat": "Formato de archivo no válido",
@@ -179,6 +179,7 @@
179
179
  "delete": "حذف",
180
180
  "document": "راهنمای کاربر",
181
181
  "download": "دانلود",
182
+ "downloadClient": "دانلود کلاینت",
182
183
  "duplicate": "تکراری",
183
184
  "edit": "ویرایش",
184
185
  "errors.invalidFileFormat": "فرمت فایل نامعتبر است",
@@ -179,6 +179,7 @@
179
179
  "delete": "Supprimer",
180
180
  "document": "Manuel utilisateur",
181
181
  "download": "Télécharger",
182
+ "downloadClient": "Télécharger le client",
182
183
  "duplicate": "Dupliquer",
183
184
  "edit": "Modifier",
184
185
  "errors.invalidFileFormat": "Format de fichier invalide",
@@ -179,6 +179,7 @@
179
179
  "delete": "Elimina",
180
180
  "document": "Manuale utente",
181
181
  "download": "Scarica",
182
+ "downloadClient": "Scarica client",
182
183
  "duplicate": "Duplica",
183
184
  "edit": "Modifica",
184
185
  "errors.invalidFileFormat": "Formato file non valido",
@@ -179,6 +179,7 @@
179
179
  "delete": "削除",
180
180
  "document": "ドキュメント",
181
181
  "download": "ダウンロード",
182
+ "downloadClient": "クライアントをダウンロード",
182
183
  "duplicate": "コピーを作成",
183
184
  "edit": "編集",
184
185
  "errors.invalidFileFormat": "ファイルフォーマットエラー",
@@ -179,6 +179,7 @@
179
179
  "delete": "삭제",
180
180
  "document": "사용 설명서",
181
181
  "download": "다운로드",
182
+ "downloadClient": "클라이언트 다운로드",
182
183
  "duplicate": "복사본 만들기",
183
184
  "edit": "편집",
184
185
  "errors.invalidFileFormat": "파일 형식 오류",
@@ -179,6 +179,7 @@
179
179
  "delete": "Verwijderen",
180
180
  "document": "Gebruikershandleiding",
181
181
  "download": "Downloaden",
182
+ "downloadClient": "Client downloaden",
182
183
  "duplicate": "Dupliceren",
183
184
  "edit": "Bewerken",
184
185
  "errors.invalidFileFormat": "Ongeldig bestandsformaat",
@@ -179,6 +179,7 @@
179
179
  "delete": "Usuń",
180
180
  "document": "Instrukcja obsługi",
181
181
  "download": "Pobierz",
182
+ "downloadClient": "Pobierz klienta",
182
183
  "duplicate": "Duplikuj",
183
184
  "edit": "Edytuj",
184
185
  "errors.invalidFileFormat": "Nieprawidłowy format pliku",
@@ -179,6 +179,7 @@
179
179
  "delete": "Excluir",
180
180
  "document": "Manual do Usuário",
181
181
  "download": "Baixar",
182
+ "downloadClient": "Baixar cliente",
182
183
  "duplicate": "Duplicar",
183
184
  "edit": "Editar",
184
185
  "errors.invalidFileFormat": "Formato de arquivo inválido",
@@ -179,6 +179,7 @@
179
179
  "delete": "Удалить",
180
180
  "document": "Руководство пользователя",
181
181
  "download": "Скачать",
182
+ "downloadClient": "Скачать клиент",
182
183
  "duplicate": "Дублировать",
183
184
  "edit": "Редактировать",
184
185
  "errors.invalidFileFormat": "Недопустимый формат файла",
@@ -179,6 +179,7 @@
179
179
  "delete": "Sil",
180
180
  "document": "Kullanıcı Kılavuzu",
181
181
  "download": "İndir",
182
+ "downloadClient": "İstemciyi indir",
182
183
  "duplicate": "Çoğalt",
183
184
  "edit": "Düzenle",
184
185
  "errors.invalidFileFormat": "Geçersiz dosya formatı",
@@ -179,6 +179,7 @@
179
179
  "delete": "Xóa",
180
180
  "document": "Hướng dẫn sử dụng",
181
181
  "download": "Tải xuống",
182
+ "downloadClient": "Tải xuống client",
182
183
  "duplicate": "Nhân bản",
183
184
  "edit": "Chỉnh sửa",
184
185
  "errors.invalidFileFormat": "Định dạng tệp không hợp lệ",
@@ -179,6 +179,7 @@
179
179
  "delete": "删除",
180
180
  "document": "使用文档",
181
181
  "download": "下载",
182
+ "downloadClient": "下载客户端",
182
183
  "duplicate": "创建副本",
183
184
  "edit": "编辑",
184
185
  "errors.invalidFileFormat": "文件格式错误",
@@ -179,6 +179,7 @@
179
179
  "delete": "刪除",
180
180
  "document": "使用說明文件",
181
181
  "download": "下載",
182
+ "downloadClient": "下載客戶端",
182
183
  "duplicate": "建立副本",
183
184
  "edit": "編輯",
184
185
  "errors.invalidFileFormat": "檔案格式錯誤",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/lobehub",
3
- "version": "2.0.0-next.333",
3
+ "version": "2.0.0-next.334",
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",
@@ -63,3 +63,9 @@ export const AES_GCM_URL = 'https://datatracker.ietf.org/doc/html/draft-ietf-avt
63
63
  export const BASE_PROVIDER_DOC_URL = 'https://lobehub.com/docs/usage/providers';
64
64
  export const SITEMAP_BASE_URL = isDev ? '/sitemap.xml/' : 'sitemap';
65
65
  export const CHANGELOG_URL = urlJoin(OFFICIAL_SITE, 'changelog/versions');
66
+
67
+ export const DOWNLOAD_URL = {
68
+ android: 'https://play.google.com/store/apps/details?id=com.lobehub.app',
69
+ default: urlJoin(OFFICIAL_SITE, '/download'),
70
+ ios: 'https://testflight.apple.com/join/2ZbjX4Qp',
71
+ } as const;
@@ -1,5 +1,5 @@
1
- import { UTM_SOURCE , LOBE_CHAT_CLOUD } from '@lobechat/business-const';
2
- import { OFFICIAL_URL } from '@lobechat/const';
1
+ import { LOBE_CHAT_CLOUD, UTM_SOURCE } from '@lobechat/business-const';
2
+ import { DOWNLOAD_URL, OFFICIAL_URL } from '@lobechat/const';
3
3
  import {
4
4
  Book,
5
5
  CircleUserRound,
@@ -9,22 +9,29 @@ import {
9
9
  FileClockIcon,
10
10
  Settings2,
11
11
  } from 'lucide-react';
12
+ import { useMemo } from 'react';
12
13
  import { useTranslation } from 'react-i18next';
13
14
  import { useNavigate } from 'react-router-dom';
14
15
 
15
16
  import { type CellProps } from '@/components/Cell';
16
17
  import { DOCUMENTS, FEEDBACK } from '@/const/index';
17
- import { usePWAInstall } from '@/hooks/usePWAInstall';
18
+ import { usePlatform } from '@/hooks/usePlatform';
18
19
  import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
19
20
  import { useUserStore } from '@/store/user';
20
21
  import { authSelectors } from '@/store/user/selectors';
21
22
 
22
23
  export const useCategory = (onOpenChangelogModal: () => void) => {
23
24
  const navigate = useNavigate();
24
- const { canInstall, install } = usePWAInstall();
25
25
  const { t } = useTranslation(['common', 'setting', 'auth']);
26
26
  const { showCloudPromotion, hideDocs } = useServerConfigStore(featureFlagsSelectors);
27
27
  const [isLoginWithAuth] = useUserStore((s) => [authSelectors.isLoginWithAuth(s)]);
28
+ const { isIOS, isAndroid } = usePlatform();
29
+
30
+ const downloadUrl = useMemo(() => {
31
+ if (isIOS) return DOWNLOAD_URL.ios;
32
+ if (isAndroid) return DOWNLOAD_URL.android;
33
+ return DOWNLOAD_URL.default;
34
+ }, [isIOS, isAndroid]);
28
35
 
29
36
  const profile: CellProps[] = [
30
37
  {
@@ -47,12 +54,12 @@ export const useCategory = (onOpenChangelogModal: () => void) => {
47
54
  },
48
55
  ];
49
56
 
50
- const pwa: CellProps[] = [
57
+ const downloadClient: CellProps[] = [
51
58
  {
52
59
  icon: Download,
53
- key: 'pwa',
54
- label: t('installPWA'),
55
- onClick: () => install(),
60
+ key: 'download-client',
61
+ label: t('downloadClient'),
62
+ onClick: () => window.open(downloadUrl, '__blank'),
56
63
  },
57
64
  {
58
65
  type: 'divider',
@@ -96,7 +103,7 @@ export const useCategory = (onOpenChangelogModal: () => void) => {
96
103
  /* ↓ cloud slot ↓ */
97
104
 
98
105
  /* ↑ cloud slot ↑ */
99
- ...(canInstall ? pwa : []),
106
+ ...downloadClient,
100
107
  ...(!hideDocs ? helps : []),
101
108
  ].filter(Boolean) as CellProps[];
102
109
 
@@ -9,7 +9,6 @@ import BusinessGlobalProvider from '@/business/client/BusinessGlobalProvider';
9
9
  import Analytics from '@/components/Analytics';
10
10
  import { DEFAULT_LANG } from '@/const/locale';
11
11
  import { isDesktop } from '@/const/version';
12
- import PWAInstall from '@/features/PWAInstall';
13
12
  import AuthProvider from '@/layout/AuthProvider';
14
13
  import GlobalProvider from '@/layout/GlobalProvider';
15
14
  import { type Locales } from '@/locales/resources';
@@ -40,9 +39,6 @@ const RootLayout = async ({ children, params }: RootLayoutProps) => {
40
39
  variants={variants}
41
40
  >
42
41
  <AuthProvider>{children}</AuthProvider>
43
- <Suspense fallback={null}>
44
- <PWAInstall />
45
- </Suspense>
46
42
  </GlobalProvider>
47
43
  );
48
44
  };
@@ -1,9 +1,9 @@
1
1
  import { LOBE_CHAT_CLOUD, UTM_SOURCE } from '@lobechat/business-const';
2
- import { isDesktop } from '@lobechat/const';
2
+ import { DOWNLOAD_URL, isDesktop } from '@lobechat/const';
3
3
  import { Flexbox, Hotkey, Icon, Tag } from '@lobehub/ui';
4
4
  import { type ItemType } from 'antd/es/menu/interface';
5
5
  import { Cloudy, Download, HardDriveDownload, LogOut, Settings2 } from 'lucide-react';
6
- import { type PropsWithChildren, memo } from 'react';
6
+ import { type PropsWithChildren, memo, useMemo } from 'react';
7
7
  import { useTranslation } from 'react-i18next';
8
8
  import { Link } from 'react-router-dom';
9
9
 
@@ -12,7 +12,7 @@ import type { MenuProps } from '@/components/Menu';
12
12
  import { DEFAULT_DESKTOP_HOTKEY_CONFIG } from '@/const/desktop';
13
13
  import { OFFICIAL_URL } from '@/const/url';
14
14
  import DataImporter from '@/features/DataImporter';
15
- import { usePWAInstall } from '@/hooks/usePWAInstall';
15
+ import { usePlatform } from '@/hooks/usePlatform';
16
16
  import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
17
17
  import { useUserStore } from '@/store/user';
18
18
  import { authSelectors } from '@/store/user/selectors';
@@ -44,7 +44,6 @@ const NewVersionBadge = memo(
44
44
  );
45
45
 
46
46
  export const useMenu = () => {
47
- const { canInstall, install } = usePWAInstall();
48
47
  const hasNewVersion = useNewVersion();
49
48
  const { t } = useTranslation(['common', 'setting', 'auth']);
50
49
  const { showCloudPromotion, hideDocs } = useServerConfigStore(featureFlagsSelectors);
@@ -53,6 +52,13 @@ export const useMenu = () => {
53
52
  authSelectors.isLoginWithAuth(s),
54
53
  ]);
55
54
  const businessMenuItems = useBusinessMenuItems(isLogin);
55
+ const { isIOS, isAndroid } = usePlatform();
56
+
57
+ const downloadUrl = useMemo(() => {
58
+ if (isIOS) return DOWNLOAD_URL.ios;
59
+ if (isAndroid) return DOWNLOAD_URL.android;
60
+ return DOWNLOAD_URL.default;
61
+ }, [isIOS, isAndroid]);
56
62
 
57
63
  const settings: MenuProps['items'] = [
58
64
  {
@@ -71,12 +77,15 @@ export const useMenu = () => {
71
77
  },
72
78
  ];
73
79
 
74
- const pwa: MenuProps['items'] = [
80
+ const downloadClient: MenuProps['items'] = [
75
81
  {
76
82
  icon: <Icon icon={Download} />,
77
- key: 'pwa',
78
- label: t('installPWA'),
79
- onClick: () => install(),
83
+ key: 'download-client',
84
+ label: (
85
+ <a href={downloadUrl} rel="noopener noreferrer" target="_blank">
86
+ {t('downloadClient')}
87
+ </a>
88
+ ),
80
89
  },
81
90
  {
82
91
  type: 'divider',
@@ -119,7 +128,7 @@ export const useMenu = () => {
119
128
 
120
129
  ...(isLogin ? settings : []),
121
130
  ...businessMenuItems,
122
- ...(canInstall ? pwa : []),
131
+ ...(!isDesktop ? downloadClient : []),
123
132
  ...data,
124
133
  ...(!hideDocs ? helps : []),
125
134
  ].filter(Boolean) as MenuProps['items'];
@@ -25,6 +25,7 @@ describe('usePlatform', () => {
25
25
  const { result } = renderHook(() => usePlatform());
26
26
 
27
27
  expect(result.current).toEqual({
28
+ isAndroid: false,
28
29
  isApple: true,
29
30
  isChrome: true,
30
31
  isChromium: true,
@@ -50,6 +51,7 @@ describe('usePlatform', () => {
50
51
  const { result } = renderHook(() => usePlatform());
51
52
 
52
53
  expect(result.current).toEqual({
54
+ isAndroid: false,
53
55
  isApple: true,
54
56
  isChrome: false,
55
57
  isChromium: false,
@@ -75,6 +77,7 @@ describe('usePlatform', () => {
75
77
  const { result } = renderHook(() => usePlatform());
76
78
 
77
79
  expect(result.current).toEqual({
80
+ isAndroid: false,
78
81
  isApple: false,
79
82
  isChrome: false,
80
83
  isChromium: true,
@@ -100,6 +103,7 @@ describe('usePlatform', () => {
100
103
  const { result } = renderHook(() => usePlatform());
101
104
 
102
105
  expect(result.current).toEqual({
106
+ isAndroid: false,
103
107
  isApple: false,
104
108
  isChrome: false,
105
109
  isChromium: false,
@@ -125,6 +129,7 @@ describe('usePlatform', () => {
125
129
  const { result } = renderHook(() => usePlatform());
126
130
 
127
131
  expect(result.current).toEqual({
132
+ isAndroid: false,
128
133
  isApple: true,
129
134
  isChrome: true,
130
135
  isChromium: true,
@@ -13,6 +13,7 @@ export const usePlatform = () => {
13
13
  const browser = useRef(getBrowser());
14
14
 
15
15
  const platformInfo = {
16
+ isAndroid: platform.current?.toLowerCase() === 'android',
16
17
  isApple: platform.current && ['mac os', 'ios'].includes(platform.current?.toLowerCase()),
17
18
  isArc: isArc(),
18
19
  isChrome: browser.current?.toLowerCase() === 'chrome',
@@ -193,6 +193,7 @@ export default {
193
193
  'delete': 'Delete',
194
194
  'document': 'User Manual',
195
195
  'download': 'Download',
196
+ 'downloadClient': 'Download Client',
196
197
  'duplicate': 'Duplicate',
197
198
  'edit': 'Edit',
198
199
  'errors.invalidFileFormat': 'Invalid file format',