const { app, BrowserWindow, Menu, ipcMain, dialog, shell } = require('electron'); const path = require('path'); const { spawn } = require('child_process'); const Store = require('electron-store'); const express = require('express'); const cors = require('cors'); const http = require('http'); const socketIo = require('socket.io'); const Docker = require('dockerode'); const fs = require('fs'); const crypto = require('crypto'); // Initialize electron store for settings const store = new Store(); // Create Express server for backend API const expressApp = express(); const server = http.createServer(expressApp); const io = socketIo(server, { cors: { origin: "http://localhost:3000", methods: ["GET", "POST"] } }); // Docker client const docker = new Docker(); // Server port - use 3001 to avoid conflict with Next.js dev server const SERVER_PORT = process.env.PORT || 3001; // Keep a global reference of the window object let mainWindow; let nextjsProcess; function createWindow() { // Create the browser window mainWindow = new BrowserWindow({ width: 1400, height: 900, minWidth: 1200, minHeight: 700, icon: path.join(__dirname, '../public/icon.png'), webPreferences: { nodeIntegration: false, contextIsolation: true, preload: path.join(__dirname, 'preload.js'), sandbox: false }, titleBarStyle: 'default', show: false }); // Load the Next.js app const startUrl = process.env.ELECTRON_START_URL || `http://localhost:3000`; mainWindow.loadURL(startUrl); // Show window when ready mainWindow.once('ready-to-show', () => { mainWindow.show(); // Open DevTools in development if (process.env.NODE_ENV === 'development') { mainWindow.webContents.openDevTools(); } }); // Set up menu setupMenu(); // Emitted when the window is closed mainWindow.on('closed', () => { mainWindow = null; }); // Handle external links mainWindow.webContents.setWindowOpenHandler(({ url }) => { shell.openExternal(url); return { action: 'deny' }; }); } function setupMenu() { const template = [ { label: 'فایل', submenu: [ { label: 'تنظیمات', accelerator: 'Ctrl+,', click: () => { mainWindow.webContents.send('navigate-to-settings'); } }, { type: 'separator' }, { label: 'خروج', accelerator: process.platform === 'darwin' ? 'Cmd+Q' : 'Ctrl+Q', click: () => { app.quit(); } } ] }, { label: 'ویرایش', submenu: [ { role: 'undo', label: 'بازگشت' }, { role: 'redo', label: 'دوباره' }, { type: 'separator' }, { role: 'cut', label: 'برش' }, { role: 'copy', label: 'کپی' }, { role: 'paste', label: 'چسباندن' } ] }, { label: 'دیدن', submenu: [ { role: 'reload', label: 'بارگذاری مجدد' }, { role: 'forceReload', label: 'بارگذاری اجباری' }, { role: 'toggleDevTools', label: 'ابزار توسعه' }, { type: 'separator' }, { role: 'resetZoom', label: 'بازنشانی زوم' }, { role: 'zoomIn', label: 'بزرگنمایی' }, { role: 'zoomOut', label: 'کوچکنمایی' }, { type: 'separator' }, { role: 'togglefullscreen', label: 'تمام صفحه' } ] }, { label: 'کمک', submenu: [ { label: 'راهنما', click: () => { mainWindow.webContents.send('navigate-to-help'); } }, { label: 'درباره', click: () => { dialog.showMessageBox(mainWindow, { type: 'info', title: 'درباره GhadirSync-AI', message: 'GhadirSync-AI نسخه 1.0.0', detail: 'دستیار هوشمند توسعه نرمافزار با پشتیبانی از زبان فارسی\nساخته شده با Electron و Next.js\n\nBuilt with anycoder - https://huggingface.co/spaces/akhaliq/anycoder' }); } } ] } ]; const menu = Menu.buildFromTemplate(template); Menu.setApplicationMenu(menu); } // Express API Routes expressApp.use(cors()); expressApp.use(express.json({ limit: '50mb' })); expressApp.use(express.urlencoded({ extended: true, limit: '50mb' })); // API Routes expressApp.get('/api/health', (req, res) => { res.json({ status: 'ok', timestamp: new Date().toISOString() }); }); // Model management expressApp.get('/api/models', (req, res) => { const modelsDir = path.join(app.getPath('userData'), 'models'); if (!fs.existsSync(modelsDir)) { fs.mkdirSync(modelsDir, { recursive: true }); } const models = fs.readdirSync(modelsDir).filter(file => file.endsWith('.gguf') || file.endsWith('.onnx') || file.endsWith('.bin') ).map(file => ({ id: crypto.createHash('md5').update(file).digest('hex'), name: file, path: path.join(modelsDir, file), size: fs.statSync(path.join(modelsDir, file)).size, loaded: false })); res.json(models); }); // Voice processing expressApp.post('/api/voice/transcribe', async (req, res) => { try { const { audioData, language = 'fa' } = req.body; // Here you would integrate with whisper-node or similar // For now, return mock response res.json({ text: language === 'fa' ? 'این یک تست صوتی است' : 'This is a voice test', language: language, confidence: 0.95 }); } catch (error) { res.status(500).json({ error: error.message }); } }); // Text to speech expressApp.post('/api/voice/synthesize', async (req, res) => { try { const { text, voice = 'default', language = 'fa' } = req.body; // Here you would integrate with VITS or similar TTS // For now, return mock response res.json({ audioUrl: `/api/voice/audio/${Date.now()}.wav`, duration: text.length * 0.1 }); } catch (error) { res.status(500).json({ error: error.message }); } }); // Code execution sandbox expressApp.post('/api/sandbox/execute', async (req, res) => { try { const { code, language, timeout = 30000 } = req.body; // Create Docker container for sandboxed execution const container = await docker.createContainer({ Image: language === 'javascript' ? 'node:20-alpine' : 'python:3.11-alpine', Cmd: language === 'javascript' ? ['node', '-e', code] : ['python', '-c', code], HostConfig: { Memory: 512 * 1024 * 1024, // 512MB CpuShares: 512, NetworkMode: 'none', // No network access ReadonlyRootfs: true, SecurityOpt: ['no-new-privileges:true'] }, WorkingDir: '/sandbox', OpenStdin: false, AttachStdin: false, AttachStdout: true, AttachStderr: true, Tty: false }); await container.start(); const logs = await container.logs({ follow: true, stdout: true, stderr: true }); let output = ''; logs.on('data', (chunk) => { output += chunk.toString(); }); // Wait for completion or timeout const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('Execution timeout')), timeout) ); const waitPromise = container.wait(); try { await Promise.race([waitPromise, timeoutPromise]); } catch (error) { await container.kill(); throw error; } const result = await container.inspect(); await container.remove(); res.json({ output: output, exitCode: result.State.ExitCode, success: result.State.ExitCode === 0, executionTime: result.State.FinishedAt ? new Date(result.State.FinishedAt).getTime() - new Date(result.State.StartedAt).getTime() : 0 }); } catch (error) { res.status(500).json({ error: error.message }); } }); // File operations expressApp.post('/api/files/save', (req, res) => { try { const { path: filePath, content } = req.body; const fullPath = path.join(app.getPath('documents'), 'GhadirSync-Projects', filePath); fs.mkdirSync(path.dirname(fullPath), { recursive: true }); fs.writeFileSync(fullPath, content, 'utf8'); res.json({ success: true, path: fullPath }); } catch (error) { res.status(500).json({ error: error.message }); } }); expressApp.get('/api/files/read', (req, res) => { try { const { path: filePath } = req.query; const fullPath = path.join(app.getPath('documents'), 'GhadirSync-Projects', filePath); if (!fs.existsSync(fullPath)) { return res.status(404).json({ error: 'File not found' }); } const content = fs.readFileSync(fullPath, 'utf8'); res.json({ content, path: fullPath }); } catch (error) { res.status(500).json({ error: error.message }); } }); // Web browsing sandbox expressApp.post('/api/web/browse', async (req, res) => { try { const { url, method = 'GET', headers = {} } = req.body; // Validate URL against whitelist const whitelist = store.get('webWhitelist', ['https://api.github.com', 'https://registry.npmjs.org']); const urlObj = new URL(url); if (!whitelist.some(w => url.startsWith(w))) { return res.status(403).json({ error: 'URL not in whitelist' }); } const response = await axios({ method, url, headers, timeout: 30000 }); // Log the request for audit const logsDir = path.join(app.getPath('userData'), 'logs'); fs.mkdirSync(logsDir, { recursive: true }); fs.appendFileSync( path.join(logsDir, 'web-access.log'), `${new Date().toISOString()} - ${method} ${url} - ${response.status}\n` ); res.json({ status: response.status, data: response.data, headers: response.headers }); } catch (error) { res.status(500).json({ error: error.message }); } }); // Socket.IO for real-time communication io.on('connection', (socket) => { console.log('Client connected:', socket.id); socket.on('voice-data', (data) => { // Process voice data in real-time socket.broadcast.emit('voice-processing', { status: 'processing' }); }); socket.on('agent-task', (task) => { // Handle agent tasks io.emit('agent-status', { taskId: task.id, status: 'started' }); }); socket.on('disconnect', () => { console.log('Client disconnected:', socket.id); }); }); // Initialize Next.js process function startNextJSServer() { const isDev = process.env.NODE_ENV === 'development'; const command = isDev ? 'npm' : 'node'; const args = isDev ? ['run', 'next-dev'] : ['node_modules/next/dist/bin/next', 'start']; nextjsProcess = spawn(command, args, { cwd: __dirname, stdio: 'inherit', shell: true }); nextjsProcess.on('error', (error) => { console.error('Failed to start Next.js:', error); }); nextjsProcess.on('exit', (code) => { console.log(`Next.js process exited with code ${code}`); }); } // IPC handlers ipcMain.handle('get-app-version', () => app.getVersion()); ipcMain.handle('get-user-data-path', () => app.getPath('userData')); ipcMain.handle('get-documents-path', () => app.getPath('documents')); ipcMain.handle('show-open-dialog', (event, options) => dialog.showOpenDialog(mainWindow, options)); ipcMain.handle('show-save-dialog', (event, options) => dialog.showSaveDialog(mainWindow, options)); ipcMain.handle('set-store', (event, key, value) => store.set(key, value)); ipcMain.handle('get-store', (event, key, defaultValue) => store.get(key, defaultValue)); ipcMain.handle('delete-store', (event, key) => store.delete(key)); ipcMain.handle('clear-store', () => store.clear()); // This method will be called when Electron has finished initialization app.whenReady().then(() => { createWindow(); startNextJSServer(); server.listen(SERVER_PORT, () => { console.log(`Backend server running on port ${SERVER_PORT}`); }); app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) { createWindow(); } }); }); // Quit when all windows are closed app.on('window-all-closed', () => { if (process.platform !== 'darwin') { app.quit(); } }); app.on('before-quit', () => { if (nextjsProcess) { nextjsProcess.kill(); } server.close(); }); // Security: Prevent navigation to external URLs app.on('web-contents-created', (event, contents) => { contents.on('will-navigate', (event, navigationUrl) => { const parsedUrl = new URL(navigationUrl); if (parsedUrl.origin !== 'http://localhost:3000') { event.preventDefault(); } }); });