mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-03-24 23:06:51 +00:00
feat(export): 多会话导出布局选择与无媒体直出
- 多会话媒体导出支持共享/分会话目录 - 无媒体导出时直接输出到目标目录
This commit is contained in:
@@ -72,6 +72,7 @@ export interface ExportOptions {
|
||||
exportEmojis?: boolean
|
||||
exportVoiceAsText?: boolean
|
||||
excelCompactColumns?: boolean
|
||||
sessionLayout?: 'shared' | 'per-session'
|
||||
}
|
||||
|
||||
interface MediaExportItem {
|
||||
@@ -408,14 +409,15 @@ class ExportService {
|
||||
private async exportMediaForMessage(
|
||||
msg: any,
|
||||
sessionId: string,
|
||||
mediaDir: string,
|
||||
mediaRootDir: string,
|
||||
mediaRelativePrefix: string,
|
||||
options: { exportImages?: boolean; exportVoices?: boolean; exportEmojis?: boolean; exportVoiceAsText?: boolean }
|
||||
): Promise<MediaExportItem | null> {
|
||||
const localType = msg.localType
|
||||
|
||||
// 图片消息
|
||||
if (localType === 3 && options.exportImages) {
|
||||
const result = await this.exportImage(msg, sessionId, mediaDir)
|
||||
const result = await this.exportImage(msg, sessionId, mediaRootDir, mediaRelativePrefix)
|
||||
if (result) {
|
||||
}
|
||||
return result
|
||||
@@ -429,13 +431,13 @@ class ExportService {
|
||||
}
|
||||
// 否则导出语音文件
|
||||
if (options.exportVoices) {
|
||||
return this.exportVoice(msg, sessionId, mediaDir)
|
||||
return this.exportVoice(msg, sessionId, mediaRootDir, mediaRelativePrefix)
|
||||
}
|
||||
}
|
||||
|
||||
// 动画表情
|
||||
if (localType === 47 && options.exportEmojis) {
|
||||
const result = await this.exportEmoji(msg, sessionId, mediaDir)
|
||||
const result = await this.exportEmoji(msg, sessionId, mediaRootDir, mediaRelativePrefix)
|
||||
if (result) {
|
||||
}
|
||||
return result
|
||||
@@ -447,9 +449,14 @@ class ExportService {
|
||||
/**
|
||||
* 导出图片文件
|
||||
*/
|
||||
private async exportImage(msg: any, sessionId: string, mediaDir: string): Promise<MediaExportItem | null> {
|
||||
private async exportImage(
|
||||
msg: any,
|
||||
sessionId: string,
|
||||
mediaRootDir: string,
|
||||
mediaRelativePrefix: string
|
||||
): Promise<MediaExportItem | null> {
|
||||
try {
|
||||
const imagesDir = path.join(mediaDir, 'media', 'images')
|
||||
const imagesDir = path.join(mediaRootDir, mediaRelativePrefix, 'images')
|
||||
if (!fs.existsSync(imagesDir)) {
|
||||
fs.mkdirSync(imagesDir, { recursive: true })
|
||||
}
|
||||
@@ -494,7 +501,7 @@ class ExportService {
|
||||
fs.writeFileSync(destPath, Buffer.from(base64Data, 'base64'))
|
||||
|
||||
return {
|
||||
relativePath: `media/images/${fileName}`,
|
||||
relativePath: path.posix.join(mediaRelativePrefix, 'images', fileName),
|
||||
kind: 'image'
|
||||
}
|
||||
} else if (sourcePath.startsWith('file://')) {
|
||||
@@ -512,7 +519,7 @@ class ExportService {
|
||||
}
|
||||
|
||||
return {
|
||||
relativePath: `media/images/${fileName}`,
|
||||
relativePath: path.posix.join(mediaRelativePrefix, 'images', fileName),
|
||||
kind: 'image'
|
||||
}
|
||||
}
|
||||
@@ -526,9 +533,14 @@ class ExportService {
|
||||
/**
|
||||
* 导出语音文件
|
||||
*/
|
||||
private async exportVoice(msg: any, sessionId: string, mediaDir: string): Promise<MediaExportItem | null> {
|
||||
private async exportVoice(
|
||||
msg: any,
|
||||
sessionId: string,
|
||||
mediaRootDir: string,
|
||||
mediaRelativePrefix: string
|
||||
): Promise<MediaExportItem | null> {
|
||||
try {
|
||||
const voicesDir = path.join(mediaDir, 'media', 'voices')
|
||||
const voicesDir = path.join(mediaRootDir, mediaRelativePrefix, 'voices')
|
||||
if (!fs.existsSync(voicesDir)) {
|
||||
fs.mkdirSync(voicesDir, { recursive: true })
|
||||
}
|
||||
@@ -540,7 +552,7 @@ class ExportService {
|
||||
// 如果已存在则跳过
|
||||
if (fs.existsSync(destPath)) {
|
||||
return {
|
||||
relativePath: `media/voices/${fileName}`,
|
||||
relativePath: path.posix.join(mediaRelativePrefix, 'voices', fileName),
|
||||
kind: 'voice'
|
||||
}
|
||||
}
|
||||
@@ -556,7 +568,7 @@ class ExportService {
|
||||
fs.writeFileSync(destPath, wavBuffer)
|
||||
|
||||
return {
|
||||
relativePath: `media/voices/${fileName}`,
|
||||
relativePath: path.posix.join(mediaRelativePrefix, 'voices', fileName),
|
||||
kind: 'voice'
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -582,9 +594,14 @@ class ExportService {
|
||||
/**
|
||||
* 导出表情文件
|
||||
*/
|
||||
private async exportEmoji(msg: any, sessionId: string, mediaDir: string): Promise<MediaExportItem | null> {
|
||||
private async exportEmoji(
|
||||
msg: any,
|
||||
sessionId: string,
|
||||
mediaRootDir: string,
|
||||
mediaRelativePrefix: string
|
||||
): Promise<MediaExportItem | null> {
|
||||
try {
|
||||
const emojisDir = path.join(mediaDir, 'media', 'emojis')
|
||||
const emojisDir = path.join(mediaRootDir, mediaRelativePrefix, 'emojis')
|
||||
if (!fs.existsSync(emojisDir)) {
|
||||
fs.mkdirSync(emojisDir, { recursive: true })
|
||||
}
|
||||
@@ -613,7 +630,7 @@ class ExportService {
|
||||
// 如果已存在则跳过
|
||||
if (fs.existsSync(destPath)) {
|
||||
return {
|
||||
relativePath: `media/emojis/${fileName}`,
|
||||
relativePath: path.posix.join(mediaRelativePrefix, 'emojis', fileName),
|
||||
kind: 'emoji'
|
||||
}
|
||||
}
|
||||
@@ -621,13 +638,13 @@ class ExportService {
|
||||
// 下载表情
|
||||
if (emojiUrl) {
|
||||
const downloaded = await this.downloadFile(emojiUrl, destPath)
|
||||
if (downloaded) {
|
||||
return {
|
||||
relativePath: `media/emojis/${fileName}`,
|
||||
kind: 'emoji'
|
||||
}
|
||||
} else {
|
||||
}
|
||||
if (downloaded) {
|
||||
return {
|
||||
relativePath: path.posix.join(mediaRelativePrefix, 'emojis', fileName),
|
||||
kind: 'emoji'
|
||||
}
|
||||
} else {
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
@@ -1483,7 +1500,13 @@ class ExportService {
|
||||
|
||||
// 媒体导出设置
|
||||
const exportMediaEnabled = options.exportImages || options.exportVoices || options.exportEmojis
|
||||
const sessionDir = path.dirname(outputPath) // 会话目录,用于媒体导出
|
||||
const outputDir = path.dirname(outputPath)
|
||||
const outputBaseName = path.basename(outputPath, path.extname(outputPath))
|
||||
const useSharedMediaLayout = options.sessionLayout === 'shared'
|
||||
const mediaRelativePrefix = useSharedMediaLayout
|
||||
? path.posix.join('media', outputBaseName)
|
||||
: 'media'
|
||||
const mediaRootDir = outputDir
|
||||
|
||||
// 媒体导出缓存
|
||||
const mediaCache = new Map<string, MediaExportItem | null>()
|
||||
@@ -1498,7 +1521,7 @@ class ExportService {
|
||||
if (mediaCache.has(mediaKey)) {
|
||||
mediaItem = mediaCache.get(mediaKey) || null
|
||||
} else {
|
||||
mediaItem = await this.exportMediaForMessage(msg, sessionId, sessionDir, {
|
||||
mediaItem = await this.exportMediaForMessage(msg, sessionId, mediaRootDir, mediaRelativePrefix, {
|
||||
exportImages: options.exportImages,
|
||||
exportVoices: options.exportVoices,
|
||||
exportEmojis: options.exportEmojis,
|
||||
@@ -1656,9 +1679,15 @@ class ExportService {
|
||||
fs.mkdirSync(outputDir, { recursive: true })
|
||||
}
|
||||
|
||||
for (let i = 0; i < sessionIds.length; i++) {
|
||||
const sessionId = sessionIds[i]
|
||||
const sessionInfo = await this.getContactInfo(sessionId)
|
||||
const exportMediaEnabled = options.exportMedia === true &&
|
||||
Boolean(options.exportImages || options.exportVoices || options.exportEmojis)
|
||||
const sessionLayout = exportMediaEnabled
|
||||
? (options.sessionLayout ?? 'per-session')
|
||||
: 'shared'
|
||||
|
||||
for (let i = 0; i < sessionIds.length; i++) {
|
||||
const sessionId = sessionIds[i]
|
||||
const sessionInfo = await this.getContactInfo(sessionId)
|
||||
|
||||
onProgress?.({
|
||||
current: i + 1,
|
||||
@@ -1667,13 +1696,13 @@ class ExportService {
|
||||
phase: 'exporting'
|
||||
})
|
||||
|
||||
const safeName = sessionInfo.displayName.replace(/[<>:"/\\|?*]/g, '_')
|
||||
const safeName = sessionInfo.displayName.replace(/[<>:"/\\|?*]/g, '_')
|
||||
const useSessionFolder = sessionLayout === 'per-session'
|
||||
const sessionDir = useSessionFolder ? path.join(outputDir, safeName) : outputDir
|
||||
|
||||
// 为每个会话创建单独的文件夹
|
||||
const sessionDir = path.join(outputDir, safeName)
|
||||
if (!fs.existsSync(sessionDir)) {
|
||||
fs.mkdirSync(sessionDir, { recursive: true })
|
||||
}
|
||||
if (useSessionFolder && !fs.existsSync(sessionDir)) {
|
||||
fs.mkdirSync(sessionDir, { recursive: true })
|
||||
}
|
||||
|
||||
let ext = '.json'
|
||||
if (options.format === 'chatlab-jsonl') ext = '.jsonl'
|
||||
|
||||
Reference in New Issue
Block a user