diff --git a/src/main/ipc.ts b/src/main/ipc.ts index a42c95c..da5e1cf 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -53,6 +53,15 @@ export function setupIPC(): void { } }); + ipcMain.handle('fs:readFileBase64', async (_, filePath: string) => { + try { + const buf = await fs.promises.readFile(filePath); + return { success: true, content: buf.toString('base64'), size: buf.length, name: path.basename(filePath) }; + } catch (err) { + return { success: false, error: (err as Error).message }; + } + }); + ipcMain.handle('fs:writeFile', async (_, filePath: string, content: string, encoding?: string) => { try { await fs.promises.writeFile(filePath, content, (encoding as BufferEncoding) || 'utf-8'); diff --git a/src/main/preload.ts b/src/main/preload.ts index 0ba6b14..62e77b5 100644 --- a/src/main/preload.ts +++ b/src/main/preload.ts @@ -13,6 +13,7 @@ contextBridge.exposeInMainWorld('metonaDesktop', { }, fs: { readFile: (filePath: string) => ipcRenderer.invoke('fs:readFile', filePath), + readFileBase64: (filePath: string) => ipcRenderer.invoke('fs:readFileBase64', filePath), writeFile: (filePath: string, content: string, encoding?: string) => ipcRenderer.invoke('fs:writeFile', filePath, content, encoding) }, tool: { diff --git a/src/renderer/components/input-area.ts b/src/renderer/components/input-area.ts index 7dc7662..3c696eb 100644 --- a/src/renderer/components/input-area.ts +++ b/src/renderer/components/input-area.ts @@ -136,20 +136,19 @@ async function handleImagePaths(paths: string[]): Promise { for (const filePath of paths) { const name = filePath.split(/[/\\]/).pop() || filePath; try { - const result = await bridge.fs.readFile(filePath); + const result = await bridge.fs.readFileBase64(filePath); if (!result.success) { appendSystemMessage(`❌ 读取 ${name} 失败: ${result.error}`); continue; } - // IPC 返回的 content 是 UTF-8 字符串,转为 base64 - const binary = Uint8Array.from(atob(result.content as string), c => c.charCodeAt(0)); - const base64 = btoa(String.fromCharCode(...binary)); - if (binary.length > maxSize) { + const size = result.size ?? 0; + if (size > maxSize) { appendSystemMessage(`⚠️ 图片 ${name} 超过 20MB 限制`); continue; } + const base64 = result.content!; pendingImages.push({ name, base64 }); - logInfo(`图片已添加: ${name}`, formatSize(binary.length)); + logInfo(`图片已添加: ${name}`, formatSize(size)); } catch (err) { logError('图片读取失败', (err as Error).message); appendSystemMessage(`❌ 读取 ${name} 失败`); diff --git a/src/renderer/components/settings-modal.ts b/src/renderer/components/settings-modal.ts index f2c2a22..d3a03cd 100644 --- a/src/renderer/components/settings-modal.ts +++ b/src/renderer/components/settings-modal.ts @@ -298,12 +298,12 @@ async function importSessions(filePath: string): Promise { try { const bridge = (window as any).metonaDesktop; - const result = await bridge.fs.readFile(filePath); + const result = await bridge.fs.readFileBase64(filePath); if (!result.success) { showToast(`读取文件失败: ${result.error}`, 'error'); return; } - // IPC 返回 base64 编码的二进制,转 ArrayBuffer + // 主进程返回 base64,直接解码为二进制 const binary = atob(result.content as string); const bytes = new Uint8Array(binary.length); for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i); diff --git a/src/renderer/types.d.ts b/src/renderer/types.d.ts index ce063b8..dc2aab8 100644 --- a/src/renderer/types.d.ts +++ b/src/renderer/types.d.ts @@ -211,6 +211,7 @@ export interface MetonaDesktopAPI { }; fs: { readFile: (filePath: string) => Promise<{ success: boolean; content?: string; name?: string; error?: string }>; + readFileBase64: (filePath: string) => Promise<{ success: boolean; content?: string; size?: number; name?: string; error?: string }>; writeFile: (filePath: string, content: string, encoding?: string) => Promise<{ success: boolean; error?: string }>; }; tool: {