Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5,441 changes: 4,796 additions & 645 deletions resources/model-db/providers.json

Large diffs are not rendered by default.

36 changes: 35 additions & 1 deletion src/main/presenter/sqlitePresenter/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,11 @@ export class SQLitePresenter implements ISQLitePresenter {
private acpSessionsTable!: AcpSessionsTable
private currentVersion: number = 0
private dbPath: string
private password?: string

constructor(dbPath: string, password?: string) {
this.dbPath = dbPath
this.password = password
try {
// 确保数据库目录存在
const dbDir = path.dirname(dbPath)
Expand Down Expand Up @@ -215,7 +217,39 @@ export class SQLitePresenter implements ISQLitePresenter {

// 关闭数据库连接
public close() {
this.db.close()
try {
this.db.close()
} catch (error) {
console.warn('Failed to close database:', error)
}
}

public reopen() {
try {
this.close()

const dbDir = path.dirname(this.dbPath)
if (!fs.existsSync(dbDir)) {
fs.mkdirSync(dbDir, { recursive: true })
}

this.db = new Database(this.dbPath)
this.db.pragma('journal_mode = WAL')

if (this.password) {
this.db.pragma(`cipher='sqlcipher'`)
this.db.pragma(`key='${this.password}'`)
}

this.db.prepare('SELECT 1').get()

this.initTables()
this.initVersionTable()
this.migrate()
} catch (error) {
console.error('Failed to reopen database:', error)
throw error
}
}

// 创建新对话
Expand Down
83 changes: 83 additions & 0 deletions src/main/presenter/syncPresenter/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,8 @@ export class SyncPresenter implements ISyncPresenter {
mcpSettings: null
}

let sqliteClosed = false

try {
this.extractBackupArchive(backupZipPath, extractionDir)

Expand All @@ -196,6 +198,7 @@ export class SyncPresenter implements ISyncPresenter {
}

this.sqlitePresenter.close()
sqliteClosed = true

tempCurrentFiles.db = this.createTempBackup(this.DB_PATH, 'chat.db')
tempCurrentFiles.appSettings = this.createTempBackup(
Expand Down Expand Up @@ -226,6 +229,7 @@ export class SyncPresenter implements ISyncPresenter {
backupDb.close()

this.copyFile(backupDbPath, this.DB_PATH)
this.cleanupDatabaseSidecarFiles(this.DB_PATH)
this.mergeAppSettingsPreservingSync(backupAppSettingsPath, this.APP_SETTINGS_PATH)

if (fs.existsSync(backupCustomPromptsPath)) {
Expand Down Expand Up @@ -257,6 +261,13 @@ export class SyncPresenter implements ISyncPresenter {
}
}

if (sqliteClosed) {
this.sqlitePresenter.reopen()
}
await this.broadcastThreadListUpdateAfterImport()
if (importMode === ImportMode.OVERWRITE) {
await this.resetShellWindowsToSingleNewChatTab()
}
eventBus.send(SYNC_EVENTS.IMPORT_COMPLETED, SendTarget.ALL_WINDOWS)
return {
success: true,
Expand All @@ -266,6 +277,14 @@ export class SyncPresenter implements ISyncPresenter {
} catch (error) {
console.error('import failed,reverting:', error)
this.restoreFromTempBackup(tempCurrentFiles)
if (sqliteClosed) {
try {
this.sqlitePresenter.reopen()
await this.broadcastThreadListUpdateAfterImport()
} catch (reopenError) {
console.error('Failed to reopen sqlite after import failure:', reopenError)
}
}
eventBus.send(
SYNC_EVENTS.IMPORT_ERROR,
SendTarget.ALL_WINDOWS,
Expand Down Expand Up @@ -535,6 +554,70 @@ export class SyncPresenter implements ISyncPresenter {
fs.copyFileSync(source, target)
}

private async broadcastThreadListUpdateAfterImport(): Promise<void> {
try {
const { presenter } = await import('../index')
await (presenter?.threadPresenter as any)?.broadcastThreadListUpdate?.()
} catch (error) {
console.warn('Failed to broadcast thread list update after import:', error)
}
}

private async resetShellWindowsToSingleNewChatTab(): Promise<void> {
try {
const { presenter } = await import('../index')
const windowPresenter = presenter?.windowPresenter as any
const tabPresenter = presenter?.tabPresenter as any

const windows = (windowPresenter?.getAllWindows?.() as Array<{ id: number }>) ?? []
await Promise.all(
windows.map(async ({ id: windowId }) => {
const tabsData =
(await tabPresenter?.getWindowTabsData?.(windowId)) ??
([] as Array<{ id: number; isActive?: boolean }>)

if (tabsData.length === 0) {
await tabPresenter?.createTab?.(windowId, 'local://chat', { active: true })
return
}

const tabToKeep = tabsData.find((tab) => tab.isActive) ?? tabsData[0]
if (!tabToKeep) {
return
}

await tabPresenter?.resetTabToBlank?.(tabToKeep.id)
await tabPresenter?.switchTab?.(tabToKeep.id)

const tabsToClose = tabsData.filter((tab) => tab.id !== tabToKeep.id).map((tab) => tab.id)
for (const tabId of tabsToClose) {
try {
await tabPresenter?.closeTab?.(tabId)
} catch (error) {
console.warn('Failed to close tab after overwrite import:', tabId, error)
}
}
})
)
} catch (error) {
console.warn('Failed to reset shell windows after overwrite import:', error)
}
}

private cleanupDatabaseSidecarFiles(dbFilePath: string): void {
const sidecarFiles = [`${dbFilePath}-wal`, `${dbFilePath}-shm`]
for (const filePath of sidecarFiles) {
if (!fs.existsSync(filePath)) {
continue
}
try {
fs.unlinkSync(filePath)
} catch (error) {
console.warn('Failed to remove database sidecar file:', filePath, error)
}
}
}

private restoreFromTempBackup(tempFiles: Record<string, string | null>): void {
if (tempFiles.db) {
this.copyFile(tempFiles.db, this.DB_PATH)
Expand Down
21 changes: 6 additions & 15 deletions src/renderer/settings/components/DataSettings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -242,14 +242,10 @@
</AlertDialogContent>
</AlertDialog>

<AlertDialog :open="!!syncStore.importResult">
<AlertDialog :open="!!syncStore.importResult && !syncStore.importResult?.success">
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>{{
syncStore.importResult?.success
? t('settings.data.importSuccessTitle')
: t('settings.data.importErrorTitle')
}}</AlertDialogTitle>
<AlertDialogTitle>{{ t('settings.data.importErrorTitle') }}</AlertDialogTitle>
<AlertDialogDescription>
{{
syncStore.importResult?.message
Expand Down Expand Up @@ -397,8 +393,8 @@ const handleBackup = async () => {
}

toast({
title: t('settings.data.toast.backupSuccessTitle'),
description: t('settings.data.toast.backupSuccessMessage', {
title: t('settings.provider.toast.backupSuccessTitle'),
description: t('settings.provider.toast.backupSuccessMessage', {
time: new Date(backupInfo.createdAt).toLocaleString(),
size: formatBytes(backupInfo.size)
}),
Expand All @@ -423,8 +419,8 @@ const handleImport = async () => {
)
if (result?.success) {
toast({
title: t('settings.data.toast.importSuccessTitle'),
description: t('settings.data.toast.importSuccessMessage', {
title: t('settings.provider.toast.importSuccessTitle'),
description: t('settings.provider.toast.importSuccessMessage', {
count: result.count ?? 0
}),
duration: 4000
Expand All @@ -435,11 +431,6 @@ const handleImport = async () => {

// 处理警告对话框的确认操作
const handleAlertAction = () => {
// 如果导入成功,则重启应用
console.log(syncStore.importResult)
if (syncStore.importResult?.success) {
syncStore.restartApp()
}
syncStore.clearImportResult()
}

Expand Down
2 changes: 1 addition & 1 deletion src/renderer/src/i18n/da-DK/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@
"selectBackupPlaceholder": "Vælg en backup",
"noBackupsAvailable": "Ingen backups tilgængelige. Kør først en sikkerhedskopi.",
"importConfirmTitle": "Bekræft dataimport",
"importConfirmDescription": "Import overskriver alle aktuelle data, inklusiv chat- og indstillingshistorik. Sørg for at sikkerhedskopiere vigtige data. Appen skal genstartes efter import.",
"importConfirmDescription": "Import overskriver alle aktuelle data, inklusiv chat- og indstillingshistorik. Sørg for at sikkerhedskopiere vigtige data. Data bliver automatisk opdateret efter import.",
"importing": "Importerer...",
"confirmImport": "Bekræft import",
"importSuccessTitle": "Import gennemført",
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/src/i18n/da-DK/sync.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"success": {
"importComplete": "Import af {count} samtale(r) lykkedes. Programmet genstarter nu."
"importComplete": "Import af {count} samtale(r) lykkedes."
},
"error": {
"notEnabled": "Synkronisering er ikke aktiveret",
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/src/i18n/en-US/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@
"selectBackupPlaceholder": "Select a backup",
"noBackupsAvailable": "No backups available. Run a backup first.",
"importConfirmTitle": "Confirm Data Import",
"importConfirmDescription": "Importing will overwrite all current data, including chat history and settings. Make sure you have backed up important data. You'll need to restart the application after import.",
"importConfirmDescription": "Importing will overwrite all current data, including chat history and settings. Make sure you have backed up important data. Data will refresh automatically after import.",
"importing": "Importing...",
"confirmImport": "Confirm Import",
"importSuccessTitle": "Import Successful",
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/src/i18n/en-US/sync.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"success": {
"importComplete": "Successfully imported {count} conversation(s). The application will now restart."
"importComplete": "Successfully imported {count} conversation(s)."
},
"error": {
"notEnabled": "Sync is not enabled",
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/src/i18n/fa-IR/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@
"selectBackupPlaceholder": "یک نسخهٔ پشتیبان انتخاب کنید",
"noBackupsAvailable": "هیچ نسخهٔ پشتیبانی در دسترس نیست. ابتدا یک پشتیبان تهیه کنید.",
"importConfirmTitle": "پذیرش وارد کردن داده",
"importConfirmDescription": "وارد کردن داده همه داده‌های کنونی، دارای تاریخچه گفت‌وگو و تنظیمات را بازنویسی می‌کند. مطمئن شوید که از داده‌های مهم پشتیبان گرفته‌اید. پس از وارد کردن، نیاز به بازراه‌اندازی برنامه است.",
"importConfirmDescription": "وارد کردن داده همه داده‌های کنونی، دارای تاریخچه گفت‌وگو و تنظیمات را بازنویسی می‌کند. مطمئن شوید که از داده‌های مهم پشتیبان گرفته‌اید. پس از وارد کردن، داده‌ها به‌صورت خودکار تازه‌سازی می‌شوند.",
"importing": "در حال وارد کردن...",
"confirmImport": "پذیرش وارد کردن",
"importSuccessTitle": "وارد کردن موفق",
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/src/i18n/fa-IR/sync.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"success": {
"importComplete": "{count} مکالمه با موفقیت وارد شد. برای راه‌اندازی دوباره برنامه، روی دکمه خوب بزنید."
"importComplete": "{count} مکالمه با موفقیت وارد شد."
},
"error": {
"notEnabled": "ویژگی همگام‌سازی روشن نیست",
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/src/i18n/fr-FR/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@
"selectBackupPlaceholder": "Sélectionnez une sauvegarde",
"noBackupsAvailable": "Aucune sauvegarde disponible. Lancez d'abord une sauvegarde.",
"importConfirmTitle": "Confirmer l'importation des données",
"importConfirmDescription": "L'importation écrasera toutes les données actuelles, y compris l'historique des conversations et les paramètres. Assurez-vous d'avoir sauvegardé les données importantes. Vous devrez redémarrer l'application après l'importation.",
"importConfirmDescription": "L'importation écrasera toutes les données actuelles, y compris l'historique des conversations et les paramètres. Assurez-vous d'avoir sauvegardé les données importantes. Les données seront actualisées automatiquement après l'importation.",
"importing": "Importation en cours...",
"confirmImport": "Confirmer l'importation",
"importSuccessTitle": "Importation réussie",
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/src/i18n/fr-FR/sync.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"success": {
"importComplete": "{count} conversation(s) importée(s) avec succès. Cliquez sur OK pour redémarrer l'application."
"importComplete": "{count} conversation(s) importée(s) avec succès."
},
"error": {
"notEnabled": "La fonction de synchronisation n'est pas activée",
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/src/i18n/he-IL/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@
"selectBackupPlaceholder": "בחר גיבוי",
"noBackupsAvailable": "אין גיבויים זמינים. בצע גיבוי תחילה.",
"importConfirmTitle": "אשר ייבוא נתונים",
"importConfirmDescription": "הייבוא ידרוס את כל הנתונים הנוכחיים, כולל היסטוריית צ'אט והגדרות. ודא שגיבית נתונים חשובים. יהיה עליך להפעיל מחדש את האפליקציה לאחר הייבוא.",
"importConfirmDescription": "הייבוא ידרוס את כל הנתונים הנוכחיים, כולל היסטוריית צ'אט והגדרות. ודא שגיבית נתונים חשובים. הנתונים יעודכנו אוטומטית לאחר הייבוא.",
"importing": "מייבא...",
"confirmImport": "אשר ייבוא",
"importSuccessTitle": "הייבוא הושלם בהצלחה",
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/src/i18n/he-IL/sync.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"success": {
"importComplete": "יובאו בהצלחה {count} שיחות. היישום יופעל מחדש כעת."
"importComplete": "יובאו בהצלחה {count} שיחות."
},
"error": {
"notEnabled": "הסנכרון אינו מופעל",
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/src/i18n/ja-JP/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@
"selectBackupPlaceholder": "バックアップを選択",
"noBackupsAvailable": "利用可能なバックアップがありません。まずバックアップを作成してください。",
"importConfirmTitle": "データインポートの確認",
"importConfirmDescription": "インポートすると、チャット履歴や設定を含むすべての現在のデータが上書きされます。重要なデータをバックアップしたことを確認してください。インポート後はアプリケーションを再起動する必要があります。",
"importConfirmDescription": "インポートすると、チャット履歴や設定を含むすべての現在のデータが上書きされます。重要なデータをバックアップしたことを確認してください。インポート後、データは自動的に更新されます。",
"importing": "インポート中...",
"confirmImport": "インポートを確認",
"importSuccessTitle": "インポート成功",
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/src/i18n/ja-JP/sync.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"success": {
"importComplete": "{count}件の会話をインポートしました。OKをクリックして、アプリケーションを再起動します。"
"importComplete": "{count}件の会話をインポートしました。"
},
"error": {
"notEnabled": "同期機能が有効になっていません",
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/src/i18n/ko-KR/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@
"selectBackupPlaceholder": "백업을 선택하세요",
"noBackupsAvailable": "사용 가능한 백업이 없습니다. 먼저 백업을 생성하세요.",
"importConfirmTitle": "데이터 가져오기 확인",
"importConfirmDescription": "가져오기를 실행하면 채팅 기록과 설정을 포함한 모든 현재 데이터가 덮어쓰기됩니다. 중요한 데이터를 백업했는지 확인하세요. 가져오기 후에는 애플리케이션을 다시 시작해야 합니다.",
"importConfirmDescription": "가져오기를 실행하면 채팅 기록과 설정을 포함한 모든 현재 데이터가 덮어쓰기됩니다. 중요한 데이터를 백업했는지 확인하세요. 가져오기 후 데이터가 자동으로 새로고침됩니다.",
"importing": "가져오는 중...",
"confirmImport": "가져오기 확인",
"importSuccessTitle": "가져오기 성공",
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/src/i18n/ko-KR/sync.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"success": {
"importComplete": "{count}개의 대화를 성공적으로 가져왔습니다. 애플리케이션을 다시 시작하려면 확인을 클릭하세요."
"importComplete": "{count}개의 대화를 성공적으로 가져왔습니다."
},
"error": {
"notEnabled": "동기화 기능이 활성화되지 않았습니다",
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/src/i18n/pt-BR/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@
"selectBackupPlaceholder": "Selecione um backup",
"noBackupsAvailable": "Nenhum backup disponível. Execute um backup primeiro.",
"importConfirmTitle": "Confirmar Importação de Dados",
"importConfirmDescription": "A importação substituirá todos os dados atuais, incluindo histórico de chat e configurações. Certifique-se de ter feito backup de dados importantes. Você precisará reiniciar o aplicativo após a importação.",
"importConfirmDescription": "A importação substituirá todos os dados atuais, incluindo histórico de chat e configurações. Certifique-se de ter feito backup de dados importantes. Os dados serão atualizados automaticamente após a importação.",
"importing": "Importando...",
"confirmImport": "Confirmar Importação",
"importSuccessTitle": "Importação Bem-sucedida",
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/src/i18n/pt-BR/sync.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"success": {
"importComplete": "{count} conversa(s) importada(s) com sucesso. O aplicativo será reiniciado agora."
"importComplete": "{count} conversa(s) importada(s) com sucesso."
},
"error": {
"notEnabled": "A sincronização não está ativada",
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/src/i18n/ru-RU/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@
"selectBackupPlaceholder": "Выберите резервную копию",
"noBackupsAvailable": "Нет доступных резервных копий. Сначала выполните резервное копирование.",
"importConfirmTitle": "Подтверждение импорта данных",
"importConfirmDescription": "Импорт перезапишет все текущие данные, включая историю чатов и настройки. Убедитесь, что вы сделали резервную копию важных данных. После импорта потребуется перезапуск приложения.",
"importConfirmDescription": "Импорт перезапишет все текущие данные, включая историю чатов и настройки. Убедитесь, что вы сделали резервную копию важных данных. Данные будут автоматически обновлены после импорта.",
"importing": "Импорт...",
"confirmImport": "Подтвердить импорт",
"importSuccessTitle": "Импорт успешно завершен",
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/src/i18n/ru-RU/sync.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"success": {
"importComplete": "Успешно импортировано {count} диалогов. Нажмите OK для перезапуска приложения."
"importComplete": "Успешно импортировано {count} диалогов."
},
"error": {
"notEnabled": "Функция синхронизации не включена",
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/src/i18n/zh-CN/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@
"selectBackupPlaceholder": "请选择备份",
"noBackupsAvailable": "当前没有可用的备份,请先执行一次备份。",
"importConfirmTitle": "确认导入数据",
"importConfirmDescription": "导入将会覆盖当前所有数据,包括聊天记录和设置。请确保已备份重要数据。导入完成后需要重启应用程序。",
"importConfirmDescription": "导入将会覆盖当前所有数据,包括聊天记录和设置。请确保已备份重要数据。导入完成后将自动刷新数据。",
"importing": "导入中...",
"confirmImport": "确认导入",
"importSuccessTitle": "导入成功",
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/src/i18n/zh-CN/sync.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"success": {
"importComplete": "成功导入 {count} 个对话,点击确定后将重启应用"
"importComplete": "成功导入 {count} 个对话"
},
"error": {
"notEnabled": "同步功能未启用",
Expand Down
Loading