返回 oh-my-ppt
handlers-history.ts
根目录 / src / main / image-generation / handlers-history.ts
1 import { ipcMain } from 'electron'
2 import fs from 'fs'
3 import path from 'path'
4 import type {
5 GeneratedImageAsset,
6 ImageGenerationHistoryRecord,
7 ImageModelProvider
8 } from '@shared/image-generation'
9 import type { ImageGenerationHistoryRow } from '../db/database'
10 import type { IpcContext } from '../ipc/context'
11 import { allowLocalAssetRoot } from '../io/local-asset-roots'
12
13 const VALID_IMAGE_PROVIDERS = [
14 'jimeng',
15 'jimeng4',
16 'agnes',
17 'siliconflow',
18 'openaiCompatible',
19 'gemini',
20 'seedream'
21 ] as const
22
23 const resolveProvider = (provider: unknown): ImageModelProvider => {
24 if (VALID_IMAGE_PROVIDERS.includes(provider as ImageModelProvider)) {
25 return provider as ImageModelProvider
26 }
27 throw new Error('Unsupported image provider')
28 }
29
30 const mimeFromFileName = (fileName: string): string => {
31 const lower = fileName.toLowerCase()
32 if (lower.endsWith('.jpg') || lower.endsWith('.jpeg')) return 'image/jpeg'
33 if (lower.endsWith('.webp')) return 'image/webp'
34 if (lower.endsWith('.gif')) return 'image/gif'
35 return 'image/png'
36 }
37
38 const readImagePaths = (value: string): string[] => {
39 try {
40 const parsed = JSON.parse(value)
41 return Array.isArray(parsed)
42 ? parsed
43 .map((item) => String(item || '').trim())
44 .filter((item) => item.startsWith('./images/'))
45 .slice(0, 12)
46 : []
47 } catch {
48 return []
49 }
50 }
51
52 const buildHistoryRecord = async (
53 ctx: IpcContext,
54 row: ImageGenerationHistoryRow
55 ): Promise<ImageGenerationHistoryRecord> => {
56 const projectDir = await ctx.resolveSessionProjectDir(row.sessionId)
57 const imagesDir = path.join(projectDir, 'images')
58 allowLocalAssetRoot(imagesDir)
59 const imagePaths = readImagePaths(row.imagePaths)
60 const provider = resolveProvider(row.provider)
61 const assets: GeneratedImageAsset[] = []
62 for (const relativePath of imagePaths) {
63 const fileName = path.basename(relativePath)
64 const absolutePath = path.join(projectDir, relativePath.replace(/^\.\//, ''))
65 let size = 0
66 try {
67 size = (await fs.promises.stat(absolutePath)).size
68 } catch {
69 size = 0
70 }
71 assets.push({
72 id: `${row.id}-${assets.length}`,
73 fileName,
74 originalName: fileName,
75 relativePath,
76 absolutePath,
77 mimeType: mimeFromFileName(fileName),
78 size,
79 prompt: row.prompt,
80 modelConfigId: row.modelConfigId,
81 provider,
82 model: row.model,
83 pageId: row.pageId,
84 createdAt: row.createdAt
85 })
86 }
87 return {
88 id: row.id,
89 sessionId: row.sessionId,
90 pageId: row.pageId,
91 prompt: row.prompt,
92 imagePaths,
93 assets,
94 modelConfigId: row.modelConfigId,
95 provider,
96 model: row.model,
97 createdAt: row.createdAt
98 }
99 }
100
101 export function registerImageGenerationHistoryHandlers(ctx: IpcContext): void {
102 ipcMain.handle('images:listHistory', async (_event, payload) => {
103 const record = payload && typeof payload === 'object' ? (payload as Record<string, unknown>) : {}
104 const sessionId = typeof record.sessionId === 'string' ? record.sessionId.trim() : ''
105 const pageId = typeof record.pageId === 'string' ? record.pageId.trim() : ''
106 if (!sessionId || !pageId) return []
107 const rows = await ctx.db.listImageGenerationHistories(sessionId, pageId)
108 return Promise.all(rows.map((row) => buildHistoryRecord(ctx, row)))
109 })
110 }
111
111 lines TYPESCRIPT