From 7d5c5419337c4bc1d04e42e61e98e5fdcc22e38d Mon Sep 17 00:00:00 2001 From: digua Date: Fri, 22 May 2026 21:12:02 +0800 Subject: [PATCH] feat(sync): add option to delete imported chats when removing subscription - Show confirmation dialog with two choices: keep data or delete imported chats - Backend supports deleteData param across CLI routes and Electron IPC - DataSourceManager.removeSession now returns the removed session info - Add i18n strings for the delete confirmation dialog (zh-CN/en-US/ja-JP/zh-TW) --- apps/cli/src/sync/index.ts | 3 +- apps/cli/src/sync/routes.ts | 19 ++- apps/desktop/main/ipc/api.ts | 11 +- apps/desktop/preload/apis/api-server.ts | 4 +- apps/desktop/preload/index.d.ts | 2 +- packages/sync/src/data-source-manager.ts | 10 +- .../common/Settings/ApiSettingsTab.vue | 111 +++++++++++++++--- src/i18n/locales/en-US/settings.json | 8 ++ src/i18n/locales/ja-JP/settings.json | 8 ++ src/i18n/locales/zh-CN/settings.json | 8 ++ src/i18n/locales/zh-TW/settings.json | 8 ++ src/stores/apiServer.ts | 86 +++++++++++--- 12 files changed, 228 insertions(+), 50 deletions(-) diff --git a/apps/cli/src/sync/index.ts b/apps/cli/src/sync/index.ts index 32ac43c2..bd211422 100644 --- a/apps/cli/src/sync/index.ts +++ b/apps/cli/src/sync/index.ts @@ -15,6 +15,7 @@ import { registerAutomationRoutes } from './routes' export interface SyncRouteContext { dsManager: DataSourceManager pullEngine: PullEngine + dbManager: DatabaseManager serverInfo: { port: number; host: string; token: string } } @@ -49,7 +50,7 @@ export function initSync( logger: syncLogger, }) - registerAutomationRoutes(server, { dsManager, pullEngine, serverInfo }) + registerAutomationRoutes(server, { dsManager, pullEngine, dbManager, serverInfo }) initScheduler({ dsManager, diff --git a/apps/cli/src/sync/routes.ts b/apps/cli/src/sync/routes.ts index bdb4a5ac..a1909050 100644 --- a/apps/cli/src/sync/routes.ts +++ b/apps/cli/src/sync/routes.ts @@ -15,7 +15,7 @@ import { import type { SyncRouteContext } from './index' export function registerAutomationRoutes(server: FastifyInstance, ctx: SyncRouteContext): void { - const { dsManager, pullEngine, serverInfo } = ctx + const { dsManager, pullEngine, dbManager, serverInfo } = ctx // ==================== Config (read-only in CLI mode) ==================== @@ -85,10 +85,17 @@ export function registerAutomationRoutes(server: FastifyInstance, ctx: SyncRoute server.delete<{ Params: { id: string; sessId: string } + Querystring: { deleteData?: string } }>('/_web/automation/data-sources/:id/sessions/:sessId', async (request, reply) => { - const ok = dsManager.removeSession(request.params.id, request.params.sessId) - if (!ok) return reply.code(404).send({ error: 'Session not found' }) + const removed = dsManager.removeSession(request.params.id, request.params.sessId) + if (!removed) return reply.code(404).send({ error: 'Session not found' }) reloadTimer(request.params.id) + if (request.query.deleteData === 'true' && removed.targetSessionId) { + dbManager.close(removed.targetSessionId) + const dbPath = dbManager.getDbPath(removed.targetSessionId) + const fs = await import('node:fs') + if (fs.existsSync(dbPath)) fs.unlinkSync(dbPath) + } return { success: true } }) @@ -105,6 +112,12 @@ export function registerAutomationRoutes(server: FastifyInstance, ctx: SyncRoute return pullEngine.triggerPullAll(request.params.id) }) + // ==================== Sync Progress ==================== + + server.get('/_web/automation/sync-progress', async () => { + return pullEngine.getProgress() + }) + // ==================== Remote Session Discovery ==================== server.get<{ diff --git a/apps/desktop/main/ipc/api.ts b/apps/desktop/main/ipc/api.ts index 587319ae..9e420e5f 100644 --- a/apps/desktop/main/ipc/api.ts +++ b/apps/desktop/main/ipc/api.ts @@ -11,6 +11,7 @@ import { setConfigManager } from '../api' import { getSettingsDir } from '../paths' import { apiLogger } from '../api/logger' import { getImportingStatus } from '../api/routes/import' +import { deleteSession as deleteSessionFile } from '../database/core' import { ElectronFetcher, WorkerImporter, BrowserWindowNotifier } from '../api/adapters' import { ConfigManager, @@ -185,10 +186,14 @@ export function registerApiHandlers(_ctx: IpcContext): void { } ) - ipcMain.handle('api:removeImportSession', (_event, sourceId: string, sessionId: string) => { - const result = dsManager.removeSession(sourceId, sessionId) + ipcMain.handle('api:removeImportSession', (_event, sourceId: string, sessionId: string, deleteData?: boolean) => { + const removed = dsManager.removeSession(sourceId, sessionId) + if (!removed) return false reloadTimer(sourceId) - return result + if (deleteData && removed.targetSessionId) { + deleteSessionFile(removed.targetSessionId) + } + return true }) // ==================== Sync ==================== diff --git a/apps/desktop/preload/apis/api-server.ts b/apps/desktop/preload/apis/api-server.ts index 131644ac..df53086b 100644 --- a/apps/desktop/preload/apis/api-server.ts +++ b/apps/desktop/preload/apis/api-server.ts @@ -126,8 +126,8 @@ export const apiServerApi = { return ipcRenderer.invoke('api:addImportSessions', sourceId, sessions) }, - removeImportSession: (sourceId: string, sessionId: string): Promise => { - return ipcRenderer.invoke('api:removeImportSession', sourceId, sessionId) + removeImportSession: (sourceId: string, sessionId: string, deleteData?: boolean): Promise => { + return ipcRenderer.invoke('api:removeImportSession', sourceId, sessionId, deleteData) }, // ==================== 同步 ==================== diff --git a/apps/desktop/preload/index.d.ts b/apps/desktop/preload/index.d.ts index d6925be6..628e0c2f 100644 --- a/apps/desktop/preload/index.d.ts +++ b/apps/desktop/preload/index.d.ts @@ -1074,7 +1074,7 @@ interface ApiServerApi { sourceId: string, sessions: Array<{ name: string; remoteSessionId: string }> ) => Promise - removeImportSession: (sourceId: string, sessionId: string) => Promise + removeImportSession: (sourceId: string, sessionId: string, deleteData?: boolean) => Promise triggerPull: (sourceId: string, sessionId?: string) => Promise<{ success: boolean; error?: string }> triggerPullAll: (sourceId: string) => Promise<{ success: boolean; error?: string }> fetchRemoteSessions: ( diff --git a/packages/sync/src/data-source-manager.ts b/packages/sync/src/data-source-manager.ts index b49909eb..915d808a 100644 --- a/packages/sync/src/data-source-manager.ts +++ b/packages/sync/src/data-source-manager.ts @@ -166,15 +166,15 @@ export class DataSourceManager { return added } - removeSession(sourceId: string, sessionId: string): boolean { + removeSession(sourceId: string, sessionId: string): ImportSession | null { const sources = this.loadAll() const ds = sources.find((s) => s.id === sourceId) - if (!ds) return false - const before = ds.sessions.length + if (!ds) return null + const removed = ds.sessions.find((s) => s.id === sessionId) + if (!removed) return null ds.sessions = ds.sessions.filter((s) => s.id !== sessionId) - if (ds.sessions.length === before) return false this.saveAll(sources) - return true + return removed } updateSession(sourceId: string, sessionId: string, updates: Partial): ImportSession | null { diff --git a/src/components/common/Settings/ApiSettingsTab.vue b/src/components/common/Settings/ApiSettingsTab.vue index b6e34e76..63730f15 100644 --- a/src/components/common/Settings/ApiSettingsTab.vue +++ b/src/components/common/Settings/ApiSettingsTab.vue @@ -10,7 +10,7 @@ import McpSettingsTab from './McpSettingsTab.vue' const { t, locale } = useI18n() const store = useApiServerStore() -const { config, status, loading, isRunning, hasError, isPortInUse, dataSources, pullingId, isWebMode } = +const { config, status, loading, isRunning, hasError, isPortInUse, dataSources, pullingIds, syncProgress, isWebMode } = storeToRefs(store) const activeSubTab = ref('sync') @@ -45,6 +45,8 @@ const showManageModal = ref(false) const managingDataSource = ref(null) const showDeleteModal = ref(false) const deletingDataSource = ref(null) +const showDeleteSessionModal = ref(false) +const deletingSession = ref<{ ds: DataSource; sess: ImportSession } | null>(null) let unlistenStartupError: (() => void) | null = null let unlistenPullResult: (() => void) | null = null @@ -175,8 +177,25 @@ async function removeSource() { deletingDataSource.value = null } -async function removeSession(ds: DataSource, sess: ImportSession) { - await store.removeImportSession(ds.id, sess.id) +function confirmRemoveSession(ds: DataSource, sess: ImportSession) { + deletingSession.value = { ds, sess } + showDeleteSessionModal.value = true +} + +async function removeSessionKeepData() { + if (!deletingSession.value) return + const { ds, sess } = deletingSession.value + await store.removeImportSession(ds.id, sess.id, false) + showDeleteSessionModal.value = false + deletingSession.value = null +} + +async function removeSessionDeleteData() { + if (!deletingSession.value) return + const { ds, sess } = deletingSession.value + await store.removeImportSession(ds.id, sess.id, true) + showDeleteSessionModal.value = false + deletingSession.value = null } async function syncAllInSource(ds: DataSource) { @@ -248,10 +267,22 @@ function subscribedRemoteIds(ds: DataSource): Set { - + - + @@ -269,11 +300,13 @@ function subscribedRemoteIds(ds: DataSource): Set { {{ sess.name }} @@ -282,22 +315,41 @@ function subscribedRemoteIds(ds: DataSource): Set { - +
- - {{ t('settings.api.dataSources.every') }} {{ ds.intervalMinutes }} - {{ t('settings.api.dataSources.minutes') }} - -