feat: 新增批量管理,支持批量删除和合并

This commit is contained in:
digua
2026-01-30 23:50:38 +08:00
committed by digua
parent 3d333476a5
commit 51ed4a9f53
12 changed files with 907 additions and 848 deletions
+33
View File
@@ -9,6 +9,7 @@ import * as parser from '../parser'
import { detectFormat, diagnoseFormat, type ParseProgress } from '../parser'
import type { IpcContext } from './types'
import { CURRENT_SCHEMA_VERSION, getPendingMigrationInfos, type MigrationInfo } from '../database/migrations'
import { exportSessionToTempFile, cleanupTempExportFiles } from '../merger'
/**
* 注册聊天记录相关 IPC 处理器
@@ -978,4 +979,36 @@ export function registerChatHandlers(ctx: IpcContext): void {
return { success: false, error: String(error) }
}
})
// ==================== 批量管理:导出会话为临时文件 ====================
/**
* 导出多个会话为临时文件(用于合并)
*/
ipcMain.handle('chat:exportSessionsToTempFiles', async (_, sessionIds: string[]) => {
try {
const tempFiles: string[] = []
for (const sessionId of sessionIds) {
const tempPath = await exportSessionToTempFile(sessionId)
tempFiles.push(tempPath)
}
return { success: true, tempFiles }
} catch (error) {
console.error('[IpcMain] 导出会话失败:', error)
return { success: false, error: String(error), tempFiles: [] }
}
})
/**
* 清理临时导出文件
*/
ipcMain.handle('chat:cleanupTempExportFiles', async (_, filePaths: string[]) => {
try {
cleanupTempExportFiles(filePaths)
return { success: true }
} catch (error) {
console.error('[IpcMain] 清理临时文件失败:', error)
return { success: false, error: String(error) }
}
})
}
+125
View File
@@ -599,3 +599,128 @@ export async function mergeFilesWithTempDb(
}
}
}
// ==================== 从会话数据库导出 ====================
import Database from 'better-sqlite3'
import { getDbPath } from '../database/core'
/**
* 从已导入的会话数据库导出为临时 JSON 文件
* 用于批量管理中的合并功能
*/
export async function exportSessionToTempFile(sessionId: string): Promise<string> {
const dbPath = getDbPath(sessionId)
if (!fs.existsSync(dbPath)) {
throw new Error(`会话数据库不存在: ${sessionId}`)
}
const db = new Database(dbPath, { readonly: true })
try {
// 读取 meta
const meta = db.prepare('SELECT * FROM meta').get() as {
name: string
platform: string
type: string
group_id?: string
group_avatar?: string
}
if (!meta) {
throw new Error('无法读取会话元信息')
}
// 读取 members
const members = db
.prepare('SELECT platform_id, account_name, group_nickname, avatar FROM member')
.all() as Array<{
platform_id: string
account_name?: string
group_nickname?: string
avatar?: string
}>
// 读取 messages(通过 JOIN 获取发送者信息)
const messages = db
.prepare(
`SELECT
m.platform_id as sender,
msg.sender_account_name as accountName,
msg.sender_group_nickname as groupNickname,
msg.ts as timestamp,
msg.type,
msg.content
FROM message msg
JOIN member m ON msg.sender_id = m.id
ORDER BY msg.ts`
)
.all() as Array<{
sender: string
accountName?: string
groupNickname?: string
timestamp: number
type: number
content?: string
}>
// 构建 ChatLab 格式数据
const chatLabData: ChatLabFormat = {
chatlab: {
version: '0.0.1',
exportedAt: Math.floor(Date.now() / 1000),
generator: 'ChatLab Export',
description: `导出自会话: ${meta.name}`,
},
meta: {
name: meta.name,
platform: meta.platform as ChatPlatform,
type: meta.type as ChatType,
groupId: meta.group_id,
groupAvatar: meta.group_avatar,
},
members: members.map((m) => ({
platformId: m.platform_id,
accountName: m.account_name,
groupNickname: m.group_nickname,
avatar: m.avatar,
})),
messages: messages.map((msg) => ({
sender: msg.sender,
accountName: msg.accountName,
groupNickname: msg.groupNickname,
timestamp: msg.timestamp,
type: msg.type,
content: msg.content,
})),
}
// 写入临时文件
const tempDir = path.join(getDefaultOutputDir(), '.chatlab_temp')
ensureOutputDir(tempDir)
const tempFilePath = path.join(tempDir, `export_${sessionId}_${Date.now()}.json`)
fs.writeFileSync(tempFilePath, JSON.stringify(chatLabData, null, 2), 'utf-8')
console.log(`[Merger] 导出会话到临时文件: ${tempFilePath}, 消息数: ${messages.length}`)
return tempFilePath
} finally {
db.close()
}
}
/**
* 清理临时导出文件
*/
export function cleanupTempExportFiles(filePaths: string[]): void {
for (const filePath of filePaths) {
try {
if (fs.existsSync(filePath)) {
fs.unlinkSync(filePath)
console.log(`[Merger] 清理临时文件: ${filePath}`)
}
} catch (err) {
console.error(`[Merger] 清理临时文件失败: ${filePath}`, err)
}
}
}
+9 -1
View File
@@ -165,6 +165,15 @@ interface ChatApi {
newMessageCount: number
error?: string
}>
exportSessionsToTempFiles: (sessionIds: string[]) => Promise<{
success: boolean
tempFiles: string[]
error?: string
}>
cleanupTempExportFiles: (filePaths: string[]) => Promise<{
success: boolean
error?: string
}>
}
interface Api {
@@ -193,7 +202,6 @@ interface MergeApi {
checkConflicts: (filePaths: string[]) => Promise<ConflictCheckResult>
mergeFiles: (params: MergeParams) => Promise<MergeResult>
clearCache: (filePath?: string) => Promise<boolean>
onParseProgress: (callback: (data: { filePath: string; progress: ImportProgress }) => void) => () => void
}
// AI 相关类型
+25 -13
View File
@@ -452,6 +452,31 @@ const chatApi = {
}> => {
return ipcRenderer.invoke('chat:incrementalImport', sessionId, filePath)
},
/**
* 导出多个会话为临时文件(用于批量管理中的合并)
*/
exportSessionsToTempFiles: (
sessionIds: string[]
): Promise<{
success: boolean
tempFiles: string[]
error?: string
}> => {
return ipcRenderer.invoke('chat:exportSessionsToTempFiles', sessionIds)
},
/**
* 清理临时导出文件
*/
cleanupTempExportFiles: (
filePaths: string[]
): Promise<{
success: boolean
error?: string
}> => {
return ipcRenderer.invoke('chat:cleanupTempExportFiles', filePaths)
},
}
// Merge API - 合并功能
@@ -485,19 +510,6 @@ const mergeApi = {
clearCache: (filePath?: string): Promise<boolean> => {
return ipcRenderer.invoke('merge:clearCache', filePath)
},
/**
* 监听解析进度(用于大文件)
*/
onParseProgress: (callback: (data: { filePath: string; progress: ImportProgress }) => void) => {
const handler = (_event: Electron.IpcRendererEvent, data: { filePath: string; progress: ImportProgress }) => {
callback(data)
}
ipcRenderer.on('merge:parseProgress', handler)
return () => {
ipcRenderer.removeListener('merge:parseProgress', handler)
}
},
}
// AI API - AI 功能