fix: 图片/文件读取改用 base64,修复 atob Latin1 错误
- 主进程新增 fs:readFileBase64 IPC,读 Buffer 直接返回 base64 - 图片读取从 atob 转换改为直接使用 readFileBase64 - .metona 备份导入同理修复 - 根因:fs:readFile 用 utf-8 编码读取二进制文件,UTF-8 多字节序列超出 Latin1 范围导致 atob 失败
This commit is contained in:
@@ -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) => {
|
ipcMain.handle('fs:writeFile', async (_, filePath: string, content: string, encoding?: string) => {
|
||||||
try {
|
try {
|
||||||
await fs.promises.writeFile(filePath, content, (encoding as BufferEncoding) || 'utf-8');
|
await fs.promises.writeFile(filePath, content, (encoding as BufferEncoding) || 'utf-8');
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ contextBridge.exposeInMainWorld('metonaDesktop', {
|
|||||||
},
|
},
|
||||||
fs: {
|
fs: {
|
||||||
readFile: (filePath: string) => ipcRenderer.invoke('fs:readFile', filePath),
|
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)
|
writeFile: (filePath: string, content: string, encoding?: string) => ipcRenderer.invoke('fs:writeFile', filePath, content, encoding)
|
||||||
},
|
},
|
||||||
tool: {
|
tool: {
|
||||||
|
|||||||
@@ -136,20 +136,19 @@ async function handleImagePaths(paths: string[]): Promise<void> {
|
|||||||
for (const filePath of paths) {
|
for (const filePath of paths) {
|
||||||
const name = filePath.split(/[/\\]/).pop() || filePath;
|
const name = filePath.split(/[/\\]/).pop() || filePath;
|
||||||
try {
|
try {
|
||||||
const result = await bridge.fs.readFile(filePath);
|
const result = await bridge.fs.readFileBase64(filePath);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
appendSystemMessage(`❌ 读取 ${name} 失败: ${result.error}`);
|
appendSystemMessage(`❌ 读取 ${name} 失败: ${result.error}`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// IPC 返回的 content 是 UTF-8 字符串,转为 base64
|
const size = result.size ?? 0;
|
||||||
const binary = Uint8Array.from(atob(result.content as string), c => c.charCodeAt(0));
|
if (size > maxSize) {
|
||||||
const base64 = btoa(String.fromCharCode(...binary));
|
|
||||||
if (binary.length > maxSize) {
|
|
||||||
appendSystemMessage(`⚠️ 图片 ${name} 超过 20MB 限制`);
|
appendSystemMessage(`⚠️ 图片 ${name} 超过 20MB 限制`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
const base64 = result.content!;
|
||||||
pendingImages.push({ name, base64 });
|
pendingImages.push({ name, base64 });
|
||||||
logInfo(`图片已添加: ${name}`, formatSize(binary.length));
|
logInfo(`图片已添加: ${name}`, formatSize(size));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logError('图片读取失败', (err as Error).message);
|
logError('图片读取失败', (err as Error).message);
|
||||||
appendSystemMessage(`❌ 读取 ${name} 失败`);
|
appendSystemMessage(`❌ 读取 ${name} 失败`);
|
||||||
|
|||||||
@@ -298,12 +298,12 @@ async function importSessions(filePath: string): Promise<void> {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const bridge = (window as any).metonaDesktop;
|
const bridge = (window as any).metonaDesktop;
|
||||||
const result = await bridge.fs.readFile(filePath);
|
const result = await bridge.fs.readFileBase64(filePath);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
showToast(`读取文件失败: ${result.error}`, 'error');
|
showToast(`读取文件失败: ${result.error}`, 'error');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// IPC 返回 base64 编码的二进制,转 ArrayBuffer
|
// 主进程返回 base64,直接解码为二进制
|
||||||
const binary = atob(result.content as string);
|
const binary = atob(result.content as string);
|
||||||
const bytes = new Uint8Array(binary.length);
|
const bytes = new Uint8Array(binary.length);
|
||||||
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
||||||
|
|||||||
Vendored
+1
@@ -211,6 +211,7 @@ export interface MetonaDesktopAPI {
|
|||||||
};
|
};
|
||||||
fs: {
|
fs: {
|
||||||
readFile: (filePath: string) => Promise<{ success: boolean; content?: string; name?: string; error?: string }>;
|
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 }>;
|
writeFile: (filePath: string, content: string, encoding?: string) => Promise<{ success: boolean; error?: string }>;
|
||||||
};
|
};
|
||||||
tool: {
|
tool: {
|
||||||
|
|||||||
Reference in New Issue
Block a user