mirror of
https://github.com/lobehub/lobehub.git
synced 2026-03-27 13:29:15 +07:00
🔧 feat(desktop): add legacy local database detection and migration guidance (#11682)
* 🔧 feat(desktop): add legacy local database detection and migration guidance - Add hasLegacyLocalDb method to SystemController for detecting legacy DB - Update LoginStep to show migration link for users with legacy DB - Add i18n translations for legacy database migration feature - Improve common settings data sync configuration * 🔧 test: mock getAppPath in electron for improved testing - Add mock implementation of getAppPath in SystemCtr and macOS test files - Update LoginStep to use urlJoin for constructing migration guide URL Signed-off-by: Innei <tukon479@gmail.com> --------- Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
@@ -23,6 +23,9 @@ export const userDataDir = app.getPath('userData');
|
||||
|
||||
export const appStorageDir = join(userDataDir, 'lobehub-storage');
|
||||
|
||||
// Legacy local database directory used in older desktop versions
|
||||
export const legacyLocalDbDir = join(appStorageDir, 'lobehub-local-db');
|
||||
|
||||
// ------ Application storage directory ---- //
|
||||
|
||||
// Local storage files (simulating S3)
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { ElectronAppState, ThemeMode } from '@lobechat/electron-client-ipc';
|
||||
import { app, dialog, nativeTheme, shell } from 'electron';
|
||||
import { macOS } from 'electron-is';
|
||||
import { pathExists, readdir } from 'fs-extra';
|
||||
import process from 'node:process';
|
||||
|
||||
import { legacyLocalDbDir } from '@/const/dir';
|
||||
import { createLogger } from '@/utils/logger';
|
||||
import {
|
||||
getAccessibilityStatus,
|
||||
@@ -214,6 +216,23 @@ export default class SystemController extends ControllerModule {
|
||||
return nativeTheme.themeSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect whether user used the legacy local database in older desktop versions.
|
||||
* Legacy path: {app.getPath('userData')}/lobehub-storage/lobehub-local-db
|
||||
*/
|
||||
@IpcMethod()
|
||||
async hasLegacyLocalDb(): Promise<boolean> {
|
||||
if (!(await pathExists(legacyLocalDbDir))) return false;
|
||||
|
||||
try {
|
||||
const entries = await readdir(legacyLocalDbDir);
|
||||
return entries.length > 0;
|
||||
} catch {
|
||||
// If directory exists but cannot be read, treat as "used" to surface guidance.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private async setSystemThemeMode(themeMode: ThemeMode) {
|
||||
nativeTheme.themeSource = themeMode;
|
||||
}
|
||||
|
||||
@@ -56,6 +56,7 @@ vi.mock('@/utils/logger', () => ({
|
||||
// Mock electron
|
||||
vi.mock('electron', () => ({
|
||||
app: {
|
||||
getAppPath: vi.fn(() => '/mock/app/path'),
|
||||
getLocale: vi.fn(() => 'en-US'),
|
||||
getPath: vi.fn((name: string) => `/mock/path/${name}`),
|
||||
},
|
||||
|
||||
@@ -13,6 +13,7 @@ vi.mock('electron', () => ({
|
||||
setApplicationMenu: vi.fn(),
|
||||
},
|
||||
app: {
|
||||
getAppPath: vi.fn(() => '/mock/app/path'),
|
||||
getName: vi.fn(() => 'LobeChat'),
|
||||
getPath: vi.fn((type: string) => {
|
||||
if (type === 'logs') return '/path/to/logs';
|
||||
|
||||
@@ -73,6 +73,7 @@
|
||||
"screen5.badge": "Sign in",
|
||||
"screen5.description": "Sign in to sync Agents, Groups, settings, and Context across all devices.",
|
||||
"screen5.errors.desktopOnlyOidc": "OIDC authorization is only available in the desktop app runtime.",
|
||||
"screen5.legacyLocalDb.link": "Migrate legacy local database",
|
||||
"screen5.methods.cloud.description": "Sign in with your LobeHub Cloud account to sync everything seamlessly",
|
||||
"screen5.methods.cloud.name": "LobeHub Cloud",
|
||||
"screen5.methods.selfhost.description": "Connect to your own LobeHub server instance",
|
||||
|
||||
@@ -73,6 +73,7 @@
|
||||
"screen5.badge": "登录",
|
||||
"screen5.description": "登录以在所有设备间同步代理、群组、设置和上下文。",
|
||||
"screen5.errors.desktopOnlyOidc": "OIDC 授权仅在桌面端运行时可用。",
|
||||
"screen5.legacyLocalDb.link": "迁移旧版本地数据库",
|
||||
"screen5.methods.cloud.description": "使用您的 LobeHub 云账户登录,实现无缝同步",
|
||||
"screen5.methods.cloud.name": "LobeHub Cloud",
|
||||
"screen5.methods.selfhost.description": "连接到你自己的 LobeHub 服务实例",
|
||||
|
||||
10
package.json
10
package.json
@@ -35,12 +35,12 @@
|
||||
"prebuild": "tsx scripts/prebuild.mts && npm run lint",
|
||||
"build": "cross-env NODE_OPTIONS=--max-old-space-size=8192 next build --webpack",
|
||||
"postbuild": "npm run build-sitemap && npm run build-migrate-db",
|
||||
"build-migrate-db": "bun run db:migrate",
|
||||
"build-sitemap": "tsx ./scripts/buildSitemapIndex/index.ts",
|
||||
"build:analyze": "NODE_OPTIONS=--max-old-space-size=81920 ANALYZE=true next build --webpack",
|
||||
"build:docker": "npm run prebuild && NODE_OPTIONS=--max-old-space-size=8192 DOCKER=true next build --webpack && npm run build-sitemap",
|
||||
"build:electron": "cross-env NODE_OPTIONS=--max-old-space-size=8192 NEXT_PUBLIC_IS_DESKTOP_APP=1 tsx scripts/electronWorkflow/buildNextApp.mts",
|
||||
"build:vercel": "npm run prebuild && cross-env NODE_OPTIONS=--max-old-space-size=6144 next build --webpack && npm run postbuild",
|
||||
"build-migrate-db": "bun run db:migrate",
|
||||
"build-sitemap": "tsx ./scripts/buildSitemapIndex/index.ts",
|
||||
"clean:node_modules": "bash -lc 'set -e; echo \"Removing all node_modules...\"; rm -rf node_modules; pnpm -r exec rm -rf node_modules; rm -rf apps/desktop/node_modules; echo \"All node_modules removed.\"'",
|
||||
"db:generate": "drizzle-kit generate && npm run workflow:dbml",
|
||||
"db:migrate": "MIGRATION_DB=1 tsx ./scripts/migrateServerDB/index.ts",
|
||||
@@ -87,11 +87,11 @@
|
||||
"start": "next start -p 3210",
|
||||
"stylelint": "stylelint \"src/**/*.{js,jsx,ts,tsx}\" --fix",
|
||||
"test": "npm run test-app && npm run test-server",
|
||||
"test-app": "vitest run",
|
||||
"test-app:coverage": "vitest --coverage --silent='passed-only'",
|
||||
"test:e2e": "pnpm --filter @lobechat/e2e-tests test",
|
||||
"test:e2e:smoke": "pnpm --filter @lobechat/e2e-tests test:smoke",
|
||||
"test:update": "vitest -u",
|
||||
"test-app": "vitest run",
|
||||
"test-app:coverage": "vitest --coverage --silent='passed-only'",
|
||||
"tunnel:cloudflare": "cloudflared tunnel --url http://localhost:3010",
|
||||
"tunnel:ngrok": "ngrok http http://localhost:3011",
|
||||
"type-check": "tsgo --noEmit",
|
||||
@@ -207,7 +207,7 @@
|
||||
"@lobehub/icons": "^4.0.2",
|
||||
"@lobehub/market-sdk": "0.29.1",
|
||||
"@lobehub/tts": "^4.0.2",
|
||||
"@lobehub/ui": "^4.24.0",
|
||||
"@lobehub/ui": "^4.25.0",
|
||||
"@modelcontextprotocol/sdk": "^1.25.1",
|
||||
"@neondatabase/serverless": "^1.0.2",
|
||||
"@next/third-parties": "^16.1.1",
|
||||
|
||||
@@ -11,15 +11,23 @@ import { cssVar } from 'antd-style';
|
||||
import { Cloud, Server, Undo2Icon } from 'lucide-react';
|
||||
import { memo, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import urlJoin from 'url-join';
|
||||
|
||||
import { OFFICIAL_SITE } from '@/const/url';
|
||||
import { isDesktop } from '@/const/version';
|
||||
import UserInfo from '@/features/User/UserInfo';
|
||||
import { remoteServerService } from '@/services/electron/remoteServer';
|
||||
import { electronSystemService } from '@/services/electron/system';
|
||||
import { useElectronStore } from '@/store/electron';
|
||||
import { setDesktopAutoOidcFirstOpenHandled } from '@/utils/electron/autoOidc';
|
||||
|
||||
import LobeMessage from '../components/LobeMessage';
|
||||
|
||||
const LEGACY_LOCAL_DB_MIGRATION_GUIDE_URL = urlJoin(
|
||||
OFFICIAL_SITE,
|
||||
'/docs/usage/migrate-from-local-database',
|
||||
);
|
||||
|
||||
// 登录方式类型
|
||||
type LoginMethod = 'cloud' | 'selfhost';
|
||||
|
||||
@@ -62,6 +70,7 @@ const LoginStep = memo<LoginStepProps>(({ onBack, onNext }) => {
|
||||
const [remoteError, setRemoteError] = useState<string | null>(null);
|
||||
const [isSigningOut, setIsSigningOut] = useState(false);
|
||||
const [showEndpoint, setShowEndpoint] = useState(false);
|
||||
const [hasLegacyLocalDb, setHasLegacyLocalDb] = useState(false);
|
||||
|
||||
const [
|
||||
dataSyncConfig,
|
||||
@@ -83,9 +92,24 @@ const LoginStep = memo<LoginStepProps>(({ onBack, onNext }) => {
|
||||
s.disconnectRemoteServer,
|
||||
]);
|
||||
|
||||
// Ensure remote server config is loaded early (desktop only hook)
|
||||
useDataSyncConfig();
|
||||
|
||||
useEffect(() => {
|
||||
if (!isDesktop) return;
|
||||
|
||||
let mounted = true;
|
||||
electronSystemService
|
||||
.hasLegacyLocalDb()
|
||||
.then((value) => {
|
||||
if (mounted) setHasLegacyLocalDb(value);
|
||||
})
|
||||
.catch(() => undefined);
|
||||
|
||||
return () => {
|
||||
mounted = false;
|
||||
};
|
||||
}, []);
|
||||
|
||||
const isCloudAuthed = !!dataSyncConfig?.active && dataSyncConfig.storageMode === 'cloud';
|
||||
const isSelfHostAuthed = !!dataSyncConfig?.active && dataSyncConfig.storageMode === 'selfHost';
|
||||
const isSelfHostEndpointVerified =
|
||||
@@ -441,6 +465,19 @@ const LoginStep = memo<LoginStepProps>(({ onBack, onNext }) => {
|
||||
|
||||
<Flexbox align={'flex-start'} gap={16} style={{ width: '100%' }} width={'100%'}>
|
||||
{renderCloudContent()}
|
||||
<Flexbox horizontal justify={'center'} style={{ width: '100%' }}>
|
||||
{hasLegacyLocalDb && (
|
||||
<Button
|
||||
onClick={() =>
|
||||
electronSystemService.openExternalLink(LEGACY_LOCAL_DB_MIGRATION_GUIDE_URL)
|
||||
}
|
||||
style={{ padding: 0 }}
|
||||
type={'link'}
|
||||
>
|
||||
{t('screen5.legacyLocalDb.link', 'Migrate legacy local database')}
|
||||
</Button>
|
||||
)}
|
||||
</Flexbox>
|
||||
{!showEndpoint ? (
|
||||
<Center width={'100%'}>
|
||||
<Button
|
||||
@@ -460,6 +497,7 @@ const LoginStep = memo<LoginStepProps>(({ onBack, onNext }) => {
|
||||
OR
|
||||
</Text>
|
||||
</Divider>
|
||||
|
||||
{/* Self-host 选项 */}
|
||||
{renderSelfhostContent()}
|
||||
</>
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
'use client';
|
||||
|
||||
import { Form, type FormGroupItemType, Icon, ImageSelect } from '@lobehub/ui';
|
||||
import { Select, Skeleton } from '@lobehub/ui';
|
||||
import {
|
||||
Flexbox,
|
||||
Form,
|
||||
type FormGroupItemType,
|
||||
Icon,
|
||||
ImageSelect,
|
||||
LobeSelect as Select,
|
||||
Skeleton,
|
||||
} from '@lobehub/ui';
|
||||
import { Segmented, Switch } from 'antd';
|
||||
import isEqual from 'fast-deep-equal';
|
||||
import { Ban, Gauge, Loader2Icon, Monitor, Moon, Mouse, Sun, Waves } from 'lucide-react';
|
||||
@@ -76,11 +83,19 @@ const Common = memo(() => {
|
||||
},
|
||||
{
|
||||
children: (
|
||||
<Select
|
||||
defaultValue={language}
|
||||
onChange={handleLangChange}
|
||||
options={[{ label: t('settingCommon.lang.autoMode'), value: 'auto' }, ...localeOptions]}
|
||||
/>
|
||||
<Flexbox horizontal justify={'flex-end'}>
|
||||
<Select
|
||||
defaultValue={language}
|
||||
onChange={handleLangChange}
|
||||
options={[
|
||||
{ label: t('settingCommon.lang.autoMode'), value: 'auto' },
|
||||
...localeOptions,
|
||||
]}
|
||||
style={{
|
||||
width: '50%',
|
||||
}}
|
||||
/>
|
||||
</Flexbox>
|
||||
),
|
||||
label: t('settingCommon.lang.title'),
|
||||
},
|
||||
@@ -136,13 +151,18 @@ const Common = memo(() => {
|
||||
|
||||
{
|
||||
children: (
|
||||
<Select
|
||||
options={[
|
||||
{ label: t('settingCommon.responseLanguage.auto'), value: '' },
|
||||
...localeOptions,
|
||||
]}
|
||||
placeholder={t('settingCommon.responseLanguage.placeholder')}
|
||||
/>
|
||||
<Flexbox horizontal justify={'flex-end'}>
|
||||
<Select
|
||||
options={[
|
||||
{ label: t('settingCommon.responseLanguage.auto'), value: '' },
|
||||
...localeOptions,
|
||||
]}
|
||||
placeholder={t('settingCommon.responseLanguage.placeholder')}
|
||||
style={{
|
||||
width: '50%',
|
||||
}}
|
||||
/>
|
||||
</Flexbox>
|
||||
),
|
||||
desc: t('settingCommon.responseLanguage.desc'),
|
||||
label: t('settingCommon.responseLanguage.title'),
|
||||
|
||||
@@ -90,6 +90,7 @@ export default {
|
||||
'Sign in to sync Agents, Groups, settings, and Context across all devices.',
|
||||
'screen5.errors.desktopOnlyOidc':
|
||||
'OIDC authorization is only available in the desktop app runtime.',
|
||||
'screen5.legacyLocalDb.link': 'Migrate legacy local database',
|
||||
'screen5.methods.cloud.description':
|
||||
'Sign in with your LobeHub Cloud account to sync everything seamlessly',
|
||||
'screen5.methods.cloud.name': 'LobeHub Cloud',
|
||||
|
||||
@@ -48,6 +48,10 @@ class ElectronSystemService {
|
||||
return this.ipc.system.openExternalLink(url);
|
||||
}
|
||||
|
||||
async hasLegacyLocalDb(): Promise<boolean> {
|
||||
return this.ipc.system.hasLegacyLocalDb();
|
||||
}
|
||||
|
||||
showContextMenu = async (type: string, data?: any) => {
|
||||
return this.ipc.menu.showContextMenu({ data, type });
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user