mirror of
https://github.com/hellodigua/ChatLab.git
synced 2026-05-18 20:39:01 +08:00
feat: 新增批量管理,支持批量删除和合并
This commit is contained in:
@@ -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) }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+9
-1
@@ -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
@@ -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 功能
|
||||
|
||||
Reference in New Issue
Block a user