feat: add platform-aware download client menu option (#11676)

*  feat: add platform-aware download client menu option

* update test

Signed-off-by: Innei <tukon479@gmail.com>

---------

Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
Innei
2026-01-22 00:49:03 +08:00
committed by GitHub
parent bae270e7da
commit 55abddc532
25 changed files with 65 additions and 22 deletions

View File

@@ -179,6 +179,7 @@
"delete": "حذف",
"document": "دليل المستخدم",
"download": "تنزيل",
"downloadClient": "تنزيل العميل",
"duplicate": "تكرار",
"edit": "تعديل",
"errors.invalidFileFormat": "تنسيق الملف غير صالح",

View File

@@ -179,6 +179,7 @@
"delete": "Изтрий",
"document": "Ръководство за потребителя",
"download": "Изтегли",
"downloadClient": "Изтегли клиент",
"duplicate": "Дублирай",
"edit": "Редактирай",
"errors.invalidFileFormat": "Невалиден файлов формат",

View File

@@ -179,6 +179,7 @@
"delete": "Löschen",
"document": "Benutzerhandbuch",
"download": "Herunterladen",
"downloadClient": "Client herunterladen",
"duplicate": "Duplizieren",
"edit": "Bearbeiten",
"errors.invalidFileFormat": "Ungültiges Dateiformat",

View File

@@ -179,6 +179,7 @@
"delete": "Delete",
"document": "User Manual",
"download": "Download",
"downloadClient": "Download Client",
"duplicate": "Duplicate",
"edit": "Edit",
"errors.invalidFileFormat": "Invalid file format",

View File

@@ -179,6 +179,7 @@
"delete": "Eliminar",
"document": "Manual de usuario",
"download": "Descargar",
"downloadClient": "Descargar cliente",
"duplicate": "Duplicar",
"edit": "Editar",
"errors.invalidFileFormat": "Formato de archivo no válido",

View File

@@ -179,6 +179,7 @@
"delete": "حذف",
"document": "راهنمای کاربر",
"download": "دانلود",
"downloadClient": "دانلود کلاینت",
"duplicate": "تکراری",
"edit": "ویرایش",
"errors.invalidFileFormat": "فرمت فایل نامعتبر است",

View File

@@ -179,6 +179,7 @@
"delete": "Supprimer",
"document": "Manuel utilisateur",
"download": "Télécharger",
"downloadClient": "Télécharger le client",
"duplicate": "Dupliquer",
"edit": "Modifier",
"errors.invalidFileFormat": "Format de fichier invalide",

View File

@@ -179,6 +179,7 @@
"delete": "Elimina",
"document": "Manuale utente",
"download": "Scarica",
"downloadClient": "Scarica client",
"duplicate": "Duplica",
"edit": "Modifica",
"errors.invalidFileFormat": "Formato file non valido",

View File

@@ -179,6 +179,7 @@
"delete": "削除",
"document": "ドキュメント",
"download": "ダウンロード",
"downloadClient": "クライアントをダウンロード",
"duplicate": "コピーを作成",
"edit": "編集",
"errors.invalidFileFormat": "ファイルフォーマットエラー",

View File

@@ -179,6 +179,7 @@
"delete": "삭제",
"document": "사용 설명서",
"download": "다운로드",
"downloadClient": "클라이언트 다운로드",
"duplicate": "복사본 만들기",
"edit": "편집",
"errors.invalidFileFormat": "파일 형식 오류",

View File

@@ -179,6 +179,7 @@
"delete": "Verwijderen",
"document": "Gebruikershandleiding",
"download": "Downloaden",
"downloadClient": "Client downloaden",
"duplicate": "Dupliceren",
"edit": "Bewerken",
"errors.invalidFileFormat": "Ongeldig bestandsformaat",

View File

@@ -179,6 +179,7 @@
"delete": "Usuń",
"document": "Instrukcja obsługi",
"download": "Pobierz",
"downloadClient": "Pobierz klienta",
"duplicate": "Duplikuj",
"edit": "Edytuj",
"errors.invalidFileFormat": "Nieprawidłowy format pliku",

View File

@@ -179,6 +179,7 @@
"delete": "Excluir",
"document": "Manual do Usuário",
"download": "Baixar",
"downloadClient": "Baixar cliente",
"duplicate": "Duplicar",
"edit": "Editar",
"errors.invalidFileFormat": "Formato de arquivo inválido",

View File

@@ -179,6 +179,7 @@
"delete": "Удалить",
"document": "Руководство пользователя",
"download": "Скачать",
"downloadClient": "Скачать клиент",
"duplicate": "Дублировать",
"edit": "Редактировать",
"errors.invalidFileFormat": "Недопустимый формат файла",

View File

@@ -179,6 +179,7 @@
"delete": "Sil",
"document": "Kullanıcı Kılavuzu",
"download": "İndir",
"downloadClient": "İstemciyi indir",
"duplicate": "Çoğalt",
"edit": "Düzenle",
"errors.invalidFileFormat": "Geçersiz dosya formatı",

View File

@@ -179,6 +179,7 @@
"delete": "Xóa",
"document": "Hướng dẫn sử dụng",
"download": "Tải xuống",
"downloadClient": "Tải xuống client",
"duplicate": "Nhân bản",
"edit": "Chỉnh sửa",
"errors.invalidFileFormat": "Định dạng tệp không hợp lệ",

View File

@@ -179,6 +179,7 @@
"delete": "删除",
"document": "使用文档",
"download": "下载",
"downloadClient": "下载客户端",
"duplicate": "创建副本",
"edit": "编辑",
"errors.invalidFileFormat": "文件格式错误",

View File

@@ -179,6 +179,7 @@
"delete": "刪除",
"document": "使用說明文件",
"download": "下載",
"downloadClient": "下載客戶端",
"duplicate": "建立副本",
"edit": "編輯",
"errors.invalidFileFormat": "檔案格式錯誤",

View File

@@ -63,3 +63,9 @@ export const AES_GCM_URL = 'https://datatracker.ietf.org/doc/html/draft-ietf-avt
export const BASE_PROVIDER_DOC_URL = 'https://lobehub.com/docs/usage/providers';
export const SITEMAP_BASE_URL = isDev ? '/sitemap.xml/' : 'sitemap';
export const CHANGELOG_URL = urlJoin(OFFICIAL_SITE, 'changelog/versions');
export const DOWNLOAD_URL = {
android: 'https://play.google.com/store/apps/details?id=com.lobehub.app',
default: urlJoin(OFFICIAL_SITE, '/download'),
ios: 'https://testflight.apple.com/join/2ZbjX4Qp',
} as const;

View File

@@ -1,5 +1,5 @@
import { UTM_SOURCE , LOBE_CHAT_CLOUD } from '@lobechat/business-const';
import { OFFICIAL_URL } from '@lobechat/const';
import { LOBE_CHAT_CLOUD, UTM_SOURCE } from '@lobechat/business-const';
import { DOWNLOAD_URL, OFFICIAL_URL } from '@lobechat/const';
import {
Book,
CircleUserRound,
@@ -9,22 +9,29 @@ import {
FileClockIcon,
Settings2,
} from 'lucide-react';
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import { type CellProps } from '@/components/Cell';
import { DOCUMENTS, FEEDBACK } from '@/const/index';
import { usePWAInstall } from '@/hooks/usePWAInstall';
import { usePlatform } from '@/hooks/usePlatform';
import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
import { useUserStore } from '@/store/user';
import { authSelectors } from '@/store/user/selectors';
export const useCategory = (onOpenChangelogModal: () => void) => {
const navigate = useNavigate();
const { canInstall, install } = usePWAInstall();
const { t } = useTranslation(['common', 'setting', 'auth']);
const { showCloudPromotion, hideDocs } = useServerConfigStore(featureFlagsSelectors);
const [isLoginWithAuth] = useUserStore((s) => [authSelectors.isLoginWithAuth(s)]);
const { isIOS, isAndroid } = usePlatform();
const downloadUrl = useMemo(() => {
if (isIOS) return DOWNLOAD_URL.ios;
if (isAndroid) return DOWNLOAD_URL.android;
return DOWNLOAD_URL.default;
}, [isIOS, isAndroid]);
const profile: CellProps[] = [
{
@@ -47,12 +54,12 @@ export const useCategory = (onOpenChangelogModal: () => void) => {
},
];
const pwa: CellProps[] = [
const downloadClient: CellProps[] = [
{
icon: Download,
key: 'pwa',
label: t('installPWA'),
onClick: () => install(),
key: 'download-client',
label: t('downloadClient'),
onClick: () => window.open(downloadUrl, '__blank'),
},
{
type: 'divider',
@@ -96,7 +103,7 @@ export const useCategory = (onOpenChangelogModal: () => void) => {
/* ↓ cloud slot ↓ */
/* ↑ cloud slot ↑ */
...(canInstall ? pwa : []),
...downloadClient,
...(!hideDocs ? helps : []),
].filter(Boolean) as CellProps[];

View File

@@ -9,7 +9,6 @@ import BusinessGlobalProvider from '@/business/client/BusinessGlobalProvider';
import Analytics from '@/components/Analytics';
import { DEFAULT_LANG } from '@/const/locale';
import { isDesktop } from '@/const/version';
import PWAInstall from '@/features/PWAInstall';
import AuthProvider from '@/layout/AuthProvider';
import GlobalProvider from '@/layout/GlobalProvider';
import { type Locales } from '@/locales/resources';
@@ -40,9 +39,6 @@ const RootLayout = async ({ children, params }: RootLayoutProps) => {
variants={variants}
>
<AuthProvider>{children}</AuthProvider>
<Suspense fallback={null}>
<PWAInstall />
</Suspense>
</GlobalProvider>
);
};

View File

@@ -1,9 +1,9 @@
import { LOBE_CHAT_CLOUD, UTM_SOURCE } from '@lobechat/business-const';
import { isDesktop } from '@lobechat/const';
import { DOWNLOAD_URL, isDesktop } from '@lobechat/const';
import { Flexbox, Hotkey, Icon, Tag } from '@lobehub/ui';
import { type ItemType } from 'antd/es/menu/interface';
import { Cloudy, Download, HardDriveDownload, LogOut, Settings2 } from 'lucide-react';
import { type PropsWithChildren, memo } from 'react';
import { type PropsWithChildren, memo, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
@@ -12,7 +12,7 @@ import type { MenuProps } from '@/components/Menu';
import { DEFAULT_DESKTOP_HOTKEY_CONFIG } from '@/const/desktop';
import { OFFICIAL_URL } from '@/const/url';
import DataImporter from '@/features/DataImporter';
import { usePWAInstall } from '@/hooks/usePWAInstall';
import { usePlatform } from '@/hooks/usePlatform';
import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
import { useUserStore } from '@/store/user';
import { authSelectors } from '@/store/user/selectors';
@@ -44,7 +44,6 @@ const NewVersionBadge = memo(
);
export const useMenu = () => {
const { canInstall, install } = usePWAInstall();
const hasNewVersion = useNewVersion();
const { t } = useTranslation(['common', 'setting', 'auth']);
const { showCloudPromotion, hideDocs } = useServerConfigStore(featureFlagsSelectors);
@@ -53,6 +52,13 @@ export const useMenu = () => {
authSelectors.isLoginWithAuth(s),
]);
const businessMenuItems = useBusinessMenuItems(isLogin);
const { isIOS, isAndroid } = usePlatform();
const downloadUrl = useMemo(() => {
if (isIOS) return DOWNLOAD_URL.ios;
if (isAndroid) return DOWNLOAD_URL.android;
return DOWNLOAD_URL.default;
}, [isIOS, isAndroid]);
const settings: MenuProps['items'] = [
{
@@ -71,12 +77,15 @@ export const useMenu = () => {
},
];
const pwa: MenuProps['items'] = [
const downloadClient: MenuProps['items'] = [
{
icon: <Icon icon={Download} />,
key: 'pwa',
label: t('installPWA'),
onClick: () => install(),
key: 'download-client',
label: (
<a href={downloadUrl} rel="noopener noreferrer" target="_blank">
{t('downloadClient')}
</a>
),
},
{
type: 'divider',
@@ -119,7 +128,7 @@ export const useMenu = () => {
...(isLogin ? settings : []),
...businessMenuItems,
...(canInstall ? pwa : []),
...(!isDesktop ? downloadClient : []),
...data,
...(!hideDocs ? helps : []),
].filter(Boolean) as MenuProps['items'];

View File

@@ -25,6 +25,7 @@ describe('usePlatform', () => {
const { result } = renderHook(() => usePlatform());
expect(result.current).toEqual({
isAndroid: false,
isApple: true,
isChrome: true,
isChromium: true,
@@ -50,6 +51,7 @@ describe('usePlatform', () => {
const { result } = renderHook(() => usePlatform());
expect(result.current).toEqual({
isAndroid: false,
isApple: true,
isChrome: false,
isChromium: false,
@@ -75,6 +77,7 @@ describe('usePlatform', () => {
const { result } = renderHook(() => usePlatform());
expect(result.current).toEqual({
isAndroid: false,
isApple: false,
isChrome: false,
isChromium: true,
@@ -100,6 +103,7 @@ describe('usePlatform', () => {
const { result } = renderHook(() => usePlatform());
expect(result.current).toEqual({
isAndroid: false,
isApple: false,
isChrome: false,
isChromium: false,
@@ -125,6 +129,7 @@ describe('usePlatform', () => {
const { result } = renderHook(() => usePlatform());
expect(result.current).toEqual({
isAndroid: false,
isApple: true,
isChrome: true,
isChromium: true,

View File

@@ -13,6 +13,7 @@ export const usePlatform = () => {
const browser = useRef(getBrowser());
const platformInfo = {
isAndroid: platform.current?.toLowerCase() === 'android',
isApple: platform.current && ['mac os', 'ios'].includes(platform.current?.toLowerCase()),
isArc: isArc(),
isChrome: browser.current?.toLowerCase() === 'chrome',

View File

@@ -193,6 +193,7 @@ export default {
'delete': 'Delete',
'document': 'User Manual',
'download': 'Download',
'downloadClient': 'Download Client',
'duplicate': 'Duplicate',
'edit': 'Edit',
'errors.invalidFileFormat': 'Invalid file format',