52 lines
1.9 KiB
TypeScript
52 lines
1.9 KiB
TypeScript
import { writeFile } from 'node:fs/promises';
|
|
import { BrowserWindow, dialog, ipcMain } from 'electron';
|
|
import { resolveMainWindow, type MainWindowProvider } from '../window-provider';
|
|
|
|
interface TranscriptSaveRequest {
|
|
defaultPath?: unknown;
|
|
markdown?: unknown;
|
|
}
|
|
|
|
const MAX_TRANSCRIPT_MARKDOWN_BYTES = 10 * 1024 * 1024;
|
|
|
|
function safeDefaultPath(value: unknown): string {
|
|
if (typeof value !== 'string') return 'chat-transcript.md';
|
|
const fileName = value.split(/[\\/]/).filter(Boolean).at(-1)?.trim();
|
|
if (!fileName) return 'chat-transcript.md';
|
|
return fileName.toLowerCase().endsWith('.md') ? fileName : `${fileName}.md`;
|
|
}
|
|
|
|
export function registerTranscriptExportHandler(mainWindow: MainWindowProvider): void {
|
|
ipcMain.handle('transcript:save', async (event, request: TranscriptSaveRequest) => {
|
|
if (typeof request?.markdown !== 'string') {
|
|
throw new Error('Invalid transcript markdown');
|
|
}
|
|
if (Buffer.byteLength(request.markdown, 'utf8') > MAX_TRANSCRIPT_MARKDOWN_BYTES) {
|
|
throw new Error('Transcript is too large');
|
|
}
|
|
|
|
let selected: Electron.SaveDialogReturnValue;
|
|
try {
|
|
const owner = (event?.sender && BrowserWindow.fromWebContents(event.sender))
|
|
|| resolveMainWindow(mainWindow);
|
|
const options: Electron.SaveDialogOptions = {
|
|
defaultPath: safeDefaultPath(request.defaultPath),
|
|
filters: [{ name: 'Markdown', extensions: ['md'] }],
|
|
};
|
|
selected = owner
|
|
? await dialog.showSaveDialog(owner, options)
|
|
: await dialog.showSaveDialog(options);
|
|
} catch {
|
|
throw new Error('Failed to save transcript');
|
|
}
|
|
if (selected.canceled || !selected.filePath) return { status: 'cancelled' as const };
|
|
|
|
try {
|
|
await writeFile(selected.filePath, request.markdown, 'utf8');
|
|
} catch {
|
|
throw new Error('Failed to save transcript');
|
|
}
|
|
return { status: 'saved' as const };
|
|
});
|
|
}
|