Skip to content

Commit 5664b84

Browse files
authored
🔧 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>
1 parent 404c577 commit 5664b84

File tree

11 files changed

+109
-20
lines changed

11 files changed

+109
-20
lines changed

apps/desktop/src/main/const/dir.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ export const userDataDir = app.getPath('userData');
2323

2424
export const appStorageDir = join(userDataDir, 'lobehub-storage');
2525

26+
// Legacy local database directory used in older desktop versions
27+
export const legacyLocalDbDir = join(appStorageDir, 'lobehub-local-db');
28+
2629
// ------ Application storage directory ---- //
2730

2831
// Local storage files (simulating S3)

apps/desktop/src/main/controllers/SystemCtr.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import { ElectronAppState, ThemeMode } from '@lobechat/electron-client-ipc';
22
import { app, dialog, nativeTheme, shell } from 'electron';
33
import { macOS } from 'electron-is';
4+
import { pathExists, readdir } from 'fs-extra';
45
import process from 'node:process';
56

7+
import { legacyLocalDbDir } from '@/const/dir';
68
import { createLogger } from '@/utils/logger';
79
import {
810
getAccessibilityStatus,
@@ -214,6 +216,23 @@ export default class SystemController extends ControllerModule {
214216
return nativeTheme.themeSource;
215217
}
216218

219+
/**
220+
* Detect whether user used the legacy local database in older desktop versions.
221+
* Legacy path: {app.getPath('userData')}/lobehub-storage/lobehub-local-db
222+
*/
223+
@IpcMethod()
224+
async hasLegacyLocalDb(): Promise<boolean> {
225+
if (!(await pathExists(legacyLocalDbDir))) return false;
226+
227+
try {
228+
const entries = await readdir(legacyLocalDbDir);
229+
return entries.length > 0;
230+
} catch {
231+
// If directory exists but cannot be read, treat as "used" to surface guidance.
232+
return true;
233+
}
234+
}
235+
217236
private async setSystemThemeMode(themeMode: ThemeMode) {
218237
nativeTheme.themeSource = themeMode;
219238
}

apps/desktop/src/main/controllers/__tests__/SystemCtr.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ vi.mock('@/utils/logger', () => ({
5656
// Mock electron
5757
vi.mock('electron', () => ({
5858
app: {
59+
getAppPath: vi.fn(() => '/mock/app/path'),
5960
getLocale: vi.fn(() => 'en-US'),
6061
getPath: vi.fn((name: string) => `/mock/path/${name}`),
6162
},

apps/desktop/src/main/menus/impls/macOS.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ vi.mock('electron', () => ({
1313
setApplicationMenu: vi.fn(),
1414
},
1515
app: {
16+
getAppPath: vi.fn(() => '/mock/app/path'),
1617
getName: vi.fn(() => 'LobeChat'),
1718
getPath: vi.fn((type: string) => {
1819
if (type === 'logs') return '/path/to/logs';

locales/en-US/desktop-onboarding.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
"screen5.badge": "Sign in",
7474
"screen5.description": "Sign in to sync Agents, Groups, settings, and Context across all devices.",
7575
"screen5.errors.desktopOnlyOidc": "OIDC authorization is only available in the desktop app runtime.",
76+
"screen5.legacyLocalDb.link": "Migrate legacy local database",
7677
"screen5.methods.cloud.description": "Sign in with your LobeHub Cloud account to sync everything seamlessly",
7778
"screen5.methods.cloud.name": "LobeHub Cloud",
7879
"screen5.methods.selfhost.description": "Connect to your own LobeHub server instance",

locales/zh-CN/desktop-onboarding.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
"screen5.badge": "登录",
7474
"screen5.description": "登录以在所有设备间同步代理、群组、设置和上下文。",
7575
"screen5.errors.desktopOnlyOidc": "OIDC 授权仅在桌面端运行时可用。",
76+
"screen5.legacyLocalDb.link": "迁移旧版本地数据库",
7677
"screen5.methods.cloud.description": "使用您的 LobeHub 云账户登录,实现无缝同步",
7778
"screen5.methods.cloud.name": "LobeHub Cloud",
7879
"screen5.methods.selfhost.description": "连接到你自己的 LobeHub 服务实例",

package.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,12 @@
3535
"prebuild": "tsx scripts/prebuild.mts && npm run lint",
3636
"build": "cross-env NODE_OPTIONS=--max-old-space-size=8192 next build --webpack",
3737
"postbuild": "npm run build-sitemap && npm run build-migrate-db",
38+
"build-migrate-db": "bun run db:migrate",
39+
"build-sitemap": "tsx ./scripts/buildSitemapIndex/index.ts",
3840
"build:analyze": "NODE_OPTIONS=--max-old-space-size=81920 ANALYZE=true next build --webpack",
3941
"build:docker": "npm run prebuild && NODE_OPTIONS=--max-old-space-size=8192 DOCKER=true next build --webpack && npm run build-sitemap",
4042
"build:electron": "cross-env NODE_OPTIONS=--max-old-space-size=8192 NEXT_PUBLIC_IS_DESKTOP_APP=1 tsx scripts/electronWorkflow/buildNextApp.mts",
4143
"build:vercel": "npm run prebuild && cross-env NODE_OPTIONS=--max-old-space-size=6144 next build --webpack && npm run postbuild",
42-
"build-migrate-db": "bun run db:migrate",
43-
"build-sitemap": "tsx ./scripts/buildSitemapIndex/index.ts",
4444
"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.\"'",
4545
"db:generate": "drizzle-kit generate && npm run workflow:dbml",
4646
"db:migrate": "MIGRATION_DB=1 tsx ./scripts/migrateServerDB/index.ts",
@@ -87,11 +87,11 @@
8787
"start": "next start -p 3210",
8888
"stylelint": "stylelint \"src/**/*.{js,jsx,ts,tsx}\" --fix",
8989
"test": "npm run test-app && npm run test-server",
90+
"test-app": "vitest run",
91+
"test-app:coverage": "vitest --coverage --silent='passed-only'",
9092
"test:e2e": "pnpm --filter @lobechat/e2e-tests test",
9193
"test:e2e:smoke": "pnpm --filter @lobechat/e2e-tests test:smoke",
9294
"test:update": "vitest -u",
93-
"test-app": "vitest run",
94-
"test-app:coverage": "vitest --coverage --silent='passed-only'",
9595
"tunnel:cloudflare": "cloudflared tunnel --url http://localhost:3010",
9696
"tunnel:ngrok": "ngrok http http://localhost:3011",
9797
"type-check": "tsgo --noEmit",
@@ -207,7 +207,7 @@
207207
"@lobehub/icons": "^4.0.2",
208208
"@lobehub/market-sdk": "0.29.1",
209209
"@lobehub/tts": "^4.0.2",
210-
"@lobehub/ui": "^4.24.0",
210+
"@lobehub/ui": "^4.25.0",
211211
"@modelcontextprotocol/sdk": "^1.25.1",
212212
"@neondatabase/serverless": "^1.0.2",
213213
"@next/third-parties": "^16.1.1",

src/app/[variants]/(desktop)/desktop-onboarding/features/LoginStep.tsx

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,23 @@ import { cssVar } from 'antd-style';
1111
import { Cloud, Server, Undo2Icon } from 'lucide-react';
1212
import { memo, useEffect, useState } from 'react';
1313
import { useTranslation } from 'react-i18next';
14+
import urlJoin from 'url-join';
1415

16+
import { OFFICIAL_SITE } from '@/const/url';
1517
import { isDesktop } from '@/const/version';
1618
import UserInfo from '@/features/User/UserInfo';
1719
import { remoteServerService } from '@/services/electron/remoteServer';
20+
import { electronSystemService } from '@/services/electron/system';
1821
import { useElectronStore } from '@/store/electron';
1922
import { setDesktopAutoOidcFirstOpenHandled } from '@/utils/electron/autoOidc';
2023

2124
import LobeMessage from '../components/LobeMessage';
2225

26+
const LEGACY_LOCAL_DB_MIGRATION_GUIDE_URL = urlJoin(
27+
OFFICIAL_SITE,
28+
'/docs/usage/migrate-from-local-database',
29+
);
30+
2331
// 登录方式类型
2432
type LoginMethod = 'cloud' | 'selfhost';
2533

@@ -62,6 +70,7 @@ const LoginStep = memo<LoginStepProps>(({ onBack, onNext }) => {
6270
const [remoteError, setRemoteError] = useState<string | null>(null);
6371
const [isSigningOut, setIsSigningOut] = useState(false);
6472
const [showEndpoint, setShowEndpoint] = useState(false);
73+
const [hasLegacyLocalDb, setHasLegacyLocalDb] = useState(false);
6574

6675
const [
6776
dataSyncConfig,
@@ -83,9 +92,24 @@ const LoginStep = memo<LoginStepProps>(({ onBack, onNext }) => {
8392
s.disconnectRemoteServer,
8493
]);
8594

86-
// Ensure remote server config is loaded early (desktop only hook)
8795
useDataSyncConfig();
8896

97+
useEffect(() => {
98+
if (!isDesktop) return;
99+
100+
let mounted = true;
101+
electronSystemService
102+
.hasLegacyLocalDb()
103+
.then((value) => {
104+
if (mounted) setHasLegacyLocalDb(value);
105+
})
106+
.catch(() => undefined);
107+
108+
return () => {
109+
mounted = false;
110+
};
111+
}, []);
112+
89113
const isCloudAuthed = !!dataSyncConfig?.active && dataSyncConfig.storageMode === 'cloud';
90114
const isSelfHostAuthed = !!dataSyncConfig?.active && dataSyncConfig.storageMode === 'selfHost';
91115
const isSelfHostEndpointVerified =
@@ -441,6 +465,19 @@ const LoginStep = memo<LoginStepProps>(({ onBack, onNext }) => {
441465

442466
<Flexbox align={'flex-start'} gap={16} style={{ width: '100%' }} width={'100%'}>
443467
{renderCloudContent()}
468+
<Flexbox horizontal justify={'center'} style={{ width: '100%' }}>
469+
{hasLegacyLocalDb && (
470+
<Button
471+
onClick={() =>
472+
electronSystemService.openExternalLink(LEGACY_LOCAL_DB_MIGRATION_GUIDE_URL)
473+
}
474+
style={{ padding: 0 }}
475+
type={'link'}
476+
>
477+
{t('screen5.legacyLocalDb.link', 'Migrate legacy local database')}
478+
</Button>
479+
)}
480+
</Flexbox>
444481
{!showEndpoint ? (
445482
<Center width={'100%'}>
446483
<Button
@@ -460,6 +497,7 @@ const LoginStep = memo<LoginStepProps>(({ onBack, onNext }) => {
460497
OR
461498
</Text>
462499
</Divider>
500+
463501
{/* Self-host 选项 */}
464502
{renderSelfhostContent()}
465503
</>

src/app/[variants]/(main)/settings/common/features/Common/Common.tsx

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
'use client';
22

3-
import { Form, type FormGroupItemType, Icon, ImageSelect } from '@lobehub/ui';
4-
import { Select, Skeleton } from '@lobehub/ui';
3+
import {
4+
Flexbox,
5+
Form,
6+
type FormGroupItemType,
7+
Icon,
8+
ImageSelect,
9+
LobeSelect as Select,
10+
Skeleton,
11+
} from '@lobehub/ui';
512
import { Segmented, Switch } from 'antd';
613
import isEqual from 'fast-deep-equal';
714
import { Ban, Gauge, Loader2Icon, Monitor, Moon, Mouse, Sun, Waves } from 'lucide-react';
@@ -76,11 +83,19 @@ const Common = memo(() => {
7683
},
7784
{
7885
children: (
79-
<Select
80-
defaultValue={language}
81-
onChange={handleLangChange}
82-
options={[{ label: t('settingCommon.lang.autoMode'), value: 'auto' }, ...localeOptions]}
83-
/>
86+
<Flexbox horizontal justify={'flex-end'}>
87+
<Select
88+
defaultValue={language}
89+
onChange={handleLangChange}
90+
options={[
91+
{ label: t('settingCommon.lang.autoMode'), value: 'auto' },
92+
...localeOptions,
93+
]}
94+
style={{
95+
width: '50%',
96+
}}
97+
/>
98+
</Flexbox>
8499
),
85100
label: t('settingCommon.lang.title'),
86101
},
@@ -136,13 +151,18 @@ const Common = memo(() => {
136151

137152
{
138153
children: (
139-
<Select
140-
options={[
141-
{ label: t('settingCommon.responseLanguage.auto'), value: '' },
142-
...localeOptions,
143-
]}
144-
placeholder={t('settingCommon.responseLanguage.placeholder')}
145-
/>
154+
<Flexbox horizontal justify={'flex-end'}>
155+
<Select
156+
options={[
157+
{ label: t('settingCommon.responseLanguage.auto'), value: '' },
158+
...localeOptions,
159+
]}
160+
placeholder={t('settingCommon.responseLanguage.placeholder')}
161+
style={{
162+
width: '50%',
163+
}}
164+
/>
165+
</Flexbox>
146166
),
147167
desc: t('settingCommon.responseLanguage.desc'),
148168
label: t('settingCommon.responseLanguage.title'),

src/locales/default/desktop-onboarding.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ export default {
9090
'Sign in to sync Agents, Groups, settings, and Context across all devices.',
9191
'screen5.errors.desktopOnlyOidc':
9292
'OIDC authorization is only available in the desktop app runtime.',
93+
'screen5.legacyLocalDb.link': 'Migrate legacy local database',
9394
'screen5.methods.cloud.description':
9495
'Sign in with your LobeHub Cloud account to sync everything seamlessly',
9596
'screen5.methods.cloud.name': 'LobeHub Cloud',

0 commit comments

Comments
 (0)