Spaces:
Build error
Build error
| 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(); | |
| } | |
| }); | |
| }); |