diff --git a/packages/shared/IpcChannel.ts b/packages/shared/IpcChannel.ts index 1a76c16e5c..7494b62fc3 100644 --- a/packages/shared/IpcChannel.ts +++ b/packages/shared/IpcChannel.ts @@ -192,6 +192,9 @@ export enum IpcChannel { DataMigrate_RequireBackup = 'data-migrate:require-backup', DataMigrate_BackupCompleted = 'data-migrate:backup-completed', DataMigrate_ShowBackupDialog = 'data-migrate:show-backup-dialog', + DataMigrate_StartFlow = 'data-migrate:start-flow', + DataMigrate_RestartApp = 'data-migrate:restart-app', + DataMigrate_CloseWindow = 'data-migrate:close-window', // zip Zip_Compress = 'zip:compress', diff --git a/src/main/data/migrate/dataRefactor/DataRefactorMigrateService.ts b/src/main/data/migrate/dataRefactor/DataRefactorMigrateService.ts index 9b92433d72..0f489b7742 100644 --- a/src/main/data/migrate/dataRefactor/DataRefactorMigrateService.ts +++ b/src/main/data/migrate/dataRefactor/DataRefactorMigrateService.ts @@ -40,7 +40,6 @@ class DataRefactorMigrateService { private migrateWindow: BrowserWindow | null = null private backupManager: BackupManager private backupCompletionResolver: ((value: boolean) => void) | null = null - private backupTimeout: NodeJS.Timeout | null = null private db = dbService.getDb() private currentProgress: MigrationProgress = { stage: 'idle', @@ -127,6 +126,35 @@ class DataRefactorMigrateService { } }) + ipcMain.handle(IpcChannel.DataMigrate_StartFlow, async () => { + try { + return await this.startMigrationFlow() + } catch (error) { + logger.error('IPC handler error: startMigrationFlow', error as Error) + throw error + } + }) + + ipcMain.handle(IpcChannel.DataMigrate_RestartApp, () => { + try { + this.restartApplication() + return true + } catch (error) { + logger.error('IPC handler error: restartApplication', error as Error) + throw error + } + }) + + ipcMain.handle(IpcChannel.DataMigrate_CloseWindow, () => { + try { + this.closeMigrateWindow() + return true + } catch (error) { + logger.error('IPC handler error: closeMigrateWindow', error as Error) + throw error + } + }) + logger.info('Migration IPC handlers registered successfully') } @@ -144,6 +172,9 @@ class DataRefactorMigrateService { ipcMain.removeAllListeners(IpcChannel.DataMigrate_Cancel) ipcMain.removeAllListeners(IpcChannel.DataMigrate_BackupCompleted) ipcMain.removeAllListeners(IpcChannel.DataMigrate_ShowBackupDialog) + ipcMain.removeAllListeners(IpcChannel.DataMigrate_StartFlow) + ipcMain.removeAllListeners(IpcChannel.DataMigrate_RestartApp) + ipcMain.removeAllListeners(IpcChannel.DataMigrate_CloseWindow) logger.info('Migration IPC handlers unregistered successfully') } catch (error) { @@ -294,32 +325,37 @@ class DataRefactorMigrateService { async runMigration(): Promise { if (this.isMigrating) { logger.warn('Migration already in progress') + this.migrateWindow?.show() return } + this.isMigrating = true + logger.info('Showing migration window') + + // Create migration window + const window = this.createMigrateWindow() + + // Wait for window to be ready + await new Promise((resolve) => { + if (window.webContents.isLoading()) { + window.webContents.once('did-finish-load', () => resolve()) + } else { + resolve() + } + }) + } + + async startMigrationFlow(): Promise { + if (!this.isMigrating) { + logger.warn('Migration not started, cannot execute flow.') + return + } + logger.info('Starting migration flow from user action') try { - this.isMigrating = true - logger.info('Starting migration process') - - // Create migration window - const window = this.createMigrateWindow() - - // Wait for window to be ready - await new Promise((resolve) => { - if (window.webContents.isLoading()) { - window.webContents.once('did-finish-load', () => resolve()) - } else { - resolve() - } - }) - - // Start the migration flow await this.executeMigrationFlow() } catch (error) { logger.error('Migration process failed', error as Error) - throw error - } finally { - this.isMigrating = false + // error is already handled in executeMigrationFlow } } @@ -355,33 +391,15 @@ class DataRefactorMigrateService { // Step 3: Mark as completed await this.markMigrationCompleted() - await this.updateProgress('completed', 100, 'Migration completed! App will restart in 3 seconds...') - - // Wait a moment to show success message, then restart the app - await new Promise((resolve) => { - setTimeout(() => { - logger.info('Migration completed successfully, restarting application') - this.restartApplication() - resolve() - }, 3000) - }) + await this.updateProgress('completed', 100, 'Migration completed! Please restart the app.') } catch (error) { logger.error('Migration flow failed', error as Error) await this.updateProgress( 'error', 0, - `Migration failed: ${error instanceof Error ? error.message : String(error)}. Please restart the app to try again.` + `Migration failed: ${error instanceof Error ? error.message : String(error)}. Please close this window and restart the app to try again.` ) - // Wait a moment to show error message, then close migration window - // Do NOT restart on error - let user handle the situation - await new Promise((resolve) => { - setTimeout(() => { - this.closeMigrateWindow() - resolve() - }, 8000) // Show error for longer (8 seconds) to give user time to read - }) - throw error } } @@ -425,14 +443,6 @@ class DataRefactorMigrateService { // Store resolver for later use this.backupCompletionResolver = resolve - // Set up timeout (5 minutes) - this.backupTimeout = setTimeout(() => { - logger.warn('Backup completion timeout') - this.backupCompletionResolver = null - this.backupTimeout = null - resolve(false) - }, 300000) // 5 minutes - // The actual completion will be triggered by notifyBackupCompleted() method }) } @@ -444,12 +454,6 @@ class DataRefactorMigrateService { if (this.backupCompletionResolver) { logger.info('Backup completed by user') - // Clear timeout if it exists - if (this.backupTimeout) { - clearTimeout(this.backupTimeout) - this.backupTimeout = null - } - this.backupCompletionResolver(true) this.backupCompletionResolver = null } @@ -535,6 +539,7 @@ class DataRefactorMigrateService { this.migrateWindow = null } + this.isMigrating = false // Clean up migration-specific IPC handlers this.unregisterMigrationIpcHandlers() } diff --git a/src/renderer/src/windows/dataRefactorMigrate/MigrateApp.tsx b/src/renderer/src/windows/dataRefactorMigrate/MigrateApp.tsx index c0c81a2e70..27798f7761 100644 --- a/src/renderer/src/windows/dataRefactorMigrate/MigrateApp.tsx +++ b/src/renderer/src/windows/dataRefactorMigrate/MigrateApp.tsx @@ -1,80 +1,11 @@ -import { CheckCircleOutlined, ExclamationCircleOutlined, LoadingOutlined } from '@ant-design/icons' +import { AppLogo } from '@renderer/config/env' import { IpcChannel } from '@shared/IpcChannel' -import { Alert, Button, Card, Progress, Space, Typography } from 'antd' -import React, { useEffect, useState } from 'react' +import { Alert, Button, Card, Progress, Space, Steps, Typography } from 'antd' +import React, { useEffect, useMemo, useState } from 'react' import styled from 'styled-components' const { Title, Text } = Typography -const Container = styled.div` - width: 100%; - height: 100vh; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - padding: 20px; -` - -const MigrationCard = styled(Card)` - width: 100%; - max-width: 500px; - border-radius: 12px; - box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); - - .ant-card-head { - background: #f8f9fa; - border-bottom: 1px solid #e9ecef; - } - - .ant-card-body { - padding: 32px 24px; - } -` - -const LogoContainer = styled.div` - display: flex; - justify-content: center; - margin-bottom: 24px; - - img { - width: 64px; - height: 64px; - } -` - -const StageIndicator = styled.div<{ stage: string }>` - display: flex; - align-items: center; - gap: 8px; - margin-bottom: 16px; - - .stage-icon { - font-size: 20px; - color: ${(props) => { - switch (props.stage) { - case 'completed': - return '#52c41a' - case 'error': - return '#ff4d4f' - default: - return '#1890ff' - } - }}; - } -` - -const ProgressContainer = styled.div` - margin: 24px 0; -` - -const MessageContainer = styled.div` - text-align: center; - margin: 16px 0; - min-height: 24px; -` - interface MigrationProgress { stage: string progress: number @@ -87,7 +18,7 @@ const MigrateApp: React.FC = () => { stage: 'idle', progress: 0, total: 100, - message: 'Initializing migration...' + message: '准备开始迁移...' }) const [showBackupRequired, setShowBackupRequired] = useState(false) @@ -120,6 +51,43 @@ const MigrateApp: React.FC = () => { } }, []) + const currentStep = useMemo(() => { + switch (progress.stage) { + case 'idle': + return 0 + case 'backup': + return 1 + case 'migration': + return 2 + case 'completed': + return 4 + case 'error': + case 'cancelled': + return 3 + default: + return 0 + } + }, [progress.stage]) + + const stepStatus = useMemo(() => { + if (progress.stage === 'error' || progress.stage === 'cancelled') { + return 'error' + } + return 'process' + }, [progress.stage]) + + const handleStartMigration = () => { + window.electron.ipcRenderer.invoke(IpcChannel.DataMigrate_StartFlow) + } + + const handleRestartApp = () => { + window.electron.ipcRenderer.invoke(IpcChannel.DataMigrate_RestartApp) + } + + const handleCloseWindow = () => { + window.electron.ipcRenderer.invoke(IpcChannel.DataMigrate_CloseWindow) + } + const handleCancel = () => { window.electron.ipcRenderer.invoke(IpcChannel.DataMigrate_Cancel) } @@ -135,35 +103,6 @@ const MigrateApp: React.FC = () => { window.electron.ipcRenderer.invoke(IpcChannel.DataMigrate_BackupCompleted) } - const getStageTitle = () => { - switch (progress.stage) { - case 'backup': - return 'Creating Backup' - case 'migration': - return 'Migrating Data' - case 'completed': - return 'Migration Completed' - case 'error': - return 'Migration Failed' - case 'cancelled': - return 'Migration Cancelled' - default: - return 'Preparing Migration' - } - } - - const getStageIcon = () => { - switch (progress.stage) { - case 'completed': - return - case 'error': - case 'cancelled': - return - default: - return - } - } - const getProgressColor = () => { switch (progress.stage) { case 'completed': @@ -176,8 +115,37 @@ const MigrateApp: React.FC = () => { } } - const showCancelButton = () => { - return progress.stage !== 'completed' && progress.stage !== 'error' && progress.stage !== 'cancelled' + const renderActionButtons = () => { + switch (progress.stage) { + case 'idle': + return ( + + ) + case 'completed': + return ( + + ) + case 'error': + case 'cancelled': + return ( + + + + ) + case 'backup': + case 'migration': + return ( + + ) + default: + return null + } } return ( @@ -192,28 +160,28 @@ const MigrateApp: React.FC = () => { } bordered={false}> - Cherry Studio + Cherry Studio - - {getStageIcon()} - - {getStageTitle()} - - - - - + - + + + {progress.stage !== 'idle' && ( + + + + )} {progress.message} @@ -259,18 +227,59 @@ const MigrateApp: React.FC = () => { /> )} - {showCancelButton() && ( -
- - - -
- )} +
{renderActionButtons()}
) } +const Container = styled.div` + width: 100%; + height: 100vh; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + padding: 20px; +` + +const MigrationCard = styled(Card)` + width: 100%; + max-width: 500px; + border-radius: 12px; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); + + .ant-card-head { + background: #f8f9fa; + border-bottom: 1px solid #e9ecef; + } + + .ant-card-body { + padding: 32px 24px; + } +` + +const LogoContainer = styled.div` + display: flex; + justify-content: center; + margin-bottom: 24px; + + img { + width: 64px; + height: 64px; + border-radius: 8px; + } +` + +const ProgressContainer = styled.div` + margin: 24px 0; +` + +const MessageContainer = styled.div` + text-align: center; + margin: 16px 0; + min-height: 24px; +` + export default MigrateApp