const { app, BrowserWindow, ipcMain, dialog, safeStorage } = require('electron'); const path = require('path'); const { PythonBackend } = require('./python-bridge'); let mainWindow = null; let pythonBackend = null; const isDev = !app.isPackaged; const BACKEND_PORT = 8642; function getProjectDirectory() { const fs = require('fs'); // Keep project files alongside the TalkEdit workspace in development. // electron/main.js lives under /electron, so repo root is ../ const projectsDir = path.join(__dirname, '..', 'Projects'); if (!fs.existsSync(projectsDir)) { fs.mkdirSync(projectsDir, { recursive: true }); } return projectsDir; } function createWindow() { mainWindow = new BrowserWindow({ width: 1400, height: 900, minWidth: 1024, minHeight: 700, title: 'CutScript', webPreferences: { preload: path.join(__dirname, 'preload.js'), contextIsolation: true, nodeIntegration: false, webSecurity: isDev ? false : true, }, show: false, }); if (isDev) { mainWindow.loadURL('http://localhost:5173'); mainWindow.webContents.openDevTools(); } else { mainWindow.loadFile(path.join(__dirname, '..', 'frontend', 'dist', 'index.html')); } mainWindow.once('ready-to-show', () => { mainWindow.show(); }); mainWindow.on('closed', () => { mainWindow = null; }); } app.whenReady().then(async () => { pythonBackend = new PythonBackend(BACKEND_PORT, isDev); await pythonBackend.start(); createWindow(); app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) { createWindow(); } }); }); app.on('window-all-closed', () => { if (process.platform !== 'darwin') { app.quit(); } }); app.on('before-quit', () => { if (pythonBackend) { pythonBackend.stop(); } }); // IPC Handlers ipcMain.handle('dialog:openFile', async (_event, options) => { const result = await dialog.showOpenDialog(mainWindow, { properties: ['openFile'], filters: [ { name: 'Video Files', extensions: ['mp4', 'avi', 'mov', 'mkv', 'webm'] }, { name: 'Audio Files', extensions: ['m4a', 'wav', 'mp3', 'flac'] }, { name: 'All Files', extensions: ['*'] }, ], ...options, }); return result.canceled ? null : result.filePaths[0]; }); ipcMain.handle('dialog:saveFile', async (_event, options) => { const result = await dialog.showSaveDialog(mainWindow, { filters: [ { name: 'Video Files', extensions: ['mp4', 'mov', 'webm'] }, { name: 'Project Files', extensions: ['aive'] }, ], ...options, }); return result.canceled ? null : result.filePath; }); ipcMain.handle('dialog:openProject', async () => { const projectDir = getProjectDirectory(); const result = await dialog.showOpenDialog(mainWindow, { defaultPath: projectDir, properties: ['openFile'], filters: [ { name: 'AI Video Editor Project', extensions: ['aive'] }, ], }); return result.canceled ? null : result.filePaths[0]; }); ipcMain.handle('dialog:saveProject', async (_event, options) => { const projectDir = getProjectDirectory(); const result = await dialog.showSaveDialog(mainWindow, { defaultPath: path.join(projectDir, 'project.aive'), filters: [ { name: 'AI Video Editor Project', extensions: ['aive'] }, ], ...options, }); return result.canceled ? null : result.filePath; }); ipcMain.handle('safe-storage:encrypt', (_event, data) => { if (safeStorage.isEncryptionAvailable()) { return safeStorage.encryptString(data).toString('base64'); } return data; }); ipcMain.handle('safe-storage:decrypt', (_event, encrypted) => { if (safeStorage.isEncryptionAvailable()) { return safeStorage.decryptString(Buffer.from(encrypted, 'base64')); } return encrypted; }); ipcMain.handle('get-backend-url', () => { return `http://localhost:${BACKEND_PORT}`; }); ipcMain.handle('backend:ensureModel', async (_event, modelName) => { // Python backend downloads models lazily during transcription. // Keep this IPC for compatibility with existing renderer flow. return modelName; }); ipcMain.handle('backend:transcribe', async (_event, payload) => { const filePath = payload?.filePath; const modelName = payload?.modelName || 'base'; const language = payload?.language; if (!filePath) { throw new Error('Missing file path for transcription'); } const res = await fetch(`http://127.0.0.1:${BACKEND_PORT}/transcribe`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ file_path: filePath, model: modelName, language, use_gpu: true, use_cache: true, diarize: false, }), }); if (!res.ok) { let detail = `${res.status} ${res.statusText}`; try { const body = await res.json(); if (body?.detail) detail = `${detail}: ${body.detail}`; } catch (_err) { // ignore JSON parse errors for non-JSON responses } throw new Error(`Transcription failed: ${detail}`); } return await res.json(); }); ipcMain.handle('fs:readFile', async (_event, filePath) => { const fs = require('fs'); return fs.readFileSync(filePath, 'utf-8'); }); ipcMain.handle('fs:writeFile', async (_event, filePath, content) => { const fs = require('fs'); fs.writeFileSync(filePath, content, 'utf-8'); return true; });