Files
ChatLab/apps/desktop/main/ipc/chat.ts
T
diguaandCursor cc4e67bd24 refactor(data): remove pluginCompute plugin-compute path
图表已统一走缓存端点,pluginCompute 不再被任何调用方使用,移除其全链路实现。
pluginQuery 保留供消息浏览/搜索/分页与 session-index 等通用 SQL 访问。

- 删除 ElectronDataAdapter(已无覆写),electron registry 直接使用 FetchDataAdapter
- 移除 IPC handler、preload API/类型、worker dispatch 与 workerManager 导出中的 pluginCompute

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-14 23:54:12 +08:00

243 lines
8.9 KiB
TypeScript

/**
* 聊天记录导入、迁移与摘要 IPC 处理器
*
* 数据查询/分析/成员管理/SQL/会话索引等业务已迁移到
* Internal HTTP Server (@openchatlab/http-routes)。
* 本文件仅保留:数据库迁移、文件导入、摘要生成、临时导出。
*/
import { ipcMain, app, dialog } from 'electron'
import * as databaseCore from '../database/core'
import * as worker from '../worker/workerManager'
import { detectFormat, findEntryFileInDirectory, scanMultiChatFile, type ParseProgress } from '../parser'
import * as parser from '../parser'
import type { IpcContext } from './types'
import { CURRENT_SCHEMA_VERSION, getPendingMigrationInfos } from '../database/migrations'
import { t } from '../i18n'
export function registerChatHandlers(ctx: IpcContext): void {
const { win } = ctx
// ==================== 数据库迁移 ====================
ipcMain.handle('chat:checkMigration', async () => {
try {
const result = databaseCore.checkMigrationNeeded()
const pendingMigrations = getPendingMigrationInfos(result.lowestVersion)
return {
needsMigration: result.count > 0,
count: result.count,
currentVersion: CURRENT_SCHEMA_VERSION,
pendingMigrations,
}
} catch (error) {
console.error('[IpcMain] Migration check failed:', error)
return { needsMigration: false, count: 0, currentVersion: CURRENT_SCHEMA_VERSION, pendingMigrations: [] }
}
})
ipcMain.handle('chat:runMigration', async () => {
try {
return databaseCore.migrateAllDatabases()
} catch (error) {
console.error('[IpcMain] Migration execution failed:', error)
return { success: false, migratedCount: 0, error: String(error) }
}
})
// ==================== 文件选择与格式检测 ====================
ipcMain.handle('chat:selectFile', async () => {
try {
const { canceled, filePaths } = await dialog.showOpenDialog({
title: t('dialog.selectChatFile'),
defaultPath: app.getPath('documents'),
properties: ['openFile'],
filters: [
{ name: t('dialog.chatRecords'), extensions: ['json', 'jsonl', 'txt'] },
{ name: t('dialog.allFiles'), extensions: ['*'] },
],
buttonLabel: t('dialog.import'),
})
if (canceled || filePaths.length === 0) return null
const filePath = filePaths[0]
const formatFeature = detectFormat(filePath)
const format = formatFeature?.name || null
if (!format) return { error: 'error.unrecognized_format' }
return { filePath, format }
} catch (error) {
console.error('[IpcMain] Error selecting file:', error)
return { error: String(error) }
}
})
ipcMain.handle('chat:detectFormat', async (_, filePath: string) => {
try {
const formatFeature = detectFormat(filePath)
if (!formatFeature) return null
return {
id: formatFeature.id,
name: formatFeature.name,
platform: formatFeature.platform,
multiChat: formatFeature.multiChat || false,
}
} catch {
return null
}
})
ipcMain.handle('chat:scanMultiChatFile', async (_, filePath: string) => {
try {
const chats = await scanMultiChatFile(filePath)
return { success: true, chats }
} catch (error) {
console.error('[IpcMain] Failed to scan multi-chat files:', error)
return { success: false, error: String(error), chats: [] }
}
})
ipcMain.handle('chat:getSupportedFormats', async () => {
return parser.getSupportedFormats()
})
// ==================== 导入 ====================
ipcMain.handle('chat:import', async (_, filePath: string) => {
try {
win.webContents.send('chat:importProgress', { stage: 'detecting', progress: 5, message: '' })
const result = await worker.streamImport(filePath, (progress: ParseProgress) => {
win.webContents.send('chat:importProgress', {
stage: progress.stage,
progress: progress.percentage,
message: progress.message,
bytesRead: progress.bytesRead,
totalBytes: progress.totalBytes,
messagesProcessed: progress.messagesProcessed,
})
})
if (result.success) {
return { success: true, sessionId: result.sessionId, diagnostics: result.diagnostics }
} else {
win.webContents.send('chat:importProgress', { stage: 'error', progress: 0, message: result.error })
return { success: false, error: result.error, diagnostics: result.diagnostics }
}
} catch (error) {
win.webContents.send('chat:importProgress', { stage: 'error', progress: 0, message: String(error) })
return { success: false, error: String(error) }
}
})
ipcMain.handle('chat:importDirectory', async (_, dirPath: string) => {
try {
const entryPath = findEntryFileInDirectory(dirPath)
if (!entryPath) return { success: false, error: 'No recognizable import format found in directory' }
win.webContents.send('chat:importProgress', { stage: 'detecting', progress: 5, message: '' })
const result = await worker.streamImport(entryPath, (progress: ParseProgress) => {
win.webContents.send('chat:importProgress', {
stage: progress.stage,
progress: progress.percentage,
message: progress.message,
bytesRead: progress.bytesRead,
totalBytes: progress.totalBytes,
messagesProcessed: progress.messagesProcessed,
})
})
if (result.success) {
return { success: true, sessionId: result.sessionId, diagnostics: result.diagnostics }
} else {
win.webContents.send('chat:importProgress', { stage: 'error', progress: 0, message: result.error })
return { success: false, error: result.error, diagnostics: result.diagnostics }
}
} catch (error) {
win.webContents.send('chat:importProgress', { stage: 'error', progress: 0, message: String(error) })
return { success: false, error: String(error) }
}
})
ipcMain.handle('chat:importWithOptions', async (_, filePath: string, formatOptions: Record<string, unknown>) => {
try {
win.webContents.send('chat:importProgress', { stage: 'detecting', progress: 5, message: '' })
const result = await worker.streamImport(
filePath,
(progress: ParseProgress) => {
win.webContents.send('chat:importProgress', {
stage: progress.stage,
progress: progress.percentage,
message: progress.message,
bytesRead: progress.bytesRead,
totalBytes: progress.totalBytes,
messagesProcessed: progress.messagesProcessed,
})
},
formatOptions
)
if (result.success) {
return { success: true, sessionId: result.sessionId, diagnostics: result.diagnostics }
} else {
win.webContents.send('chat:importProgress', { stage: 'error', progress: 0, message: result.error })
return { success: false, error: result.error, diagnostics: result.diagnostics }
}
} catch (error) {
win.webContents.send('chat:importProgress', { stage: 'error', progress: 0, message: String(error) })
return { success: false, error: String(error) }
}
})
// ==================== 增量导入 ====================
ipcMain.handle('chat:analyzeIncrementalImport', async (_, sessionId: string, filePath: string) => {
try {
const formatFeature = detectFormat(filePath)
if (!formatFeature) return { error: 'error.unrecognized_format' }
return await worker.analyzeIncrementalImport(sessionId, filePath)
} catch (error) {
console.error('[IpcMain] Failed to analyze incremental import:', error)
return { error: String(error) }
}
})
ipcMain.handle('chat:incrementalImport', async (_, sessionId: string, filePath: string) => {
try {
win.webContents.send('chat:importProgress', { stage: 'saving', progress: 0, message: '' })
const result = await worker.incrementalImport(sessionId, filePath, (progress) => {
win.webContents.send('chat:importProgress', {
stage: progress.stage,
progress: progress.percentage,
message: progress.message,
})
})
if (result.success) {
try {
await worker.generateIncrementalSessions(sessionId)
} catch (e) {
console.error('[IpcMain] Failed to incrementally generate session index:', e)
}
// 分析缓存按 DB 文件版本自动失效,无需手动清理
// 通知渲染进程刷新会话列表(与 API 路由的 notifySessionListChanged 保持一致)
win.webContents.send('api:importCompleted')
}
return result
} catch (error) {
console.error('[IpcMain] Failed to execute incremental import:', error)
return { success: false, error: String(error) }
}
})
// Session index and summary IPC handlers have been removed.
// All session-index operations now go through shared HTTP routes
// (FetchSessionIndexAdapter via /_web/sessions/* endpoints).
}