Mehdi commited on
Commit
0ecac7b
·
verified ·
1 Parent(s): 4b2d733

Upload electron/main.js with huggingface_hub

Browse files
Files changed (1) hide show
  1. electron/main.js +439 -0
electron/main.js ADDED
@@ -0,0 +1,439 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const { app, BrowserWindow, Menu, ipcMain, dialog, shell } = require('electron');
2
+ const path = require('path');
3
+ const { spawn } = require('child_process');
4
+ const Store = require('electron-store');
5
+ const express = require('express');
6
+ const cors = require('cors');
7
+ const http = require('http');
8
+ const socketIo = require('socket.io');
9
+ const Docker = require('dockerode');
10
+ const fs = require('fs');
11
+ const crypto = require('crypto');
12
+
13
+ // Initialize electron store for settings
14
+ const store = new Store();
15
+
16
+ // Create Express server for backend API
17
+ const expressApp = express();
18
+ const server = http.createServer(expressApp);
19
+ const io = socketIo(server, {
20
+ cors: {
21
+ origin: "http://localhost:3000",
22
+ methods: ["GET", "POST"]
23
+ }
24
+ });
25
+
26
+ // Docker client
27
+ const docker = new Docker();
28
+
29
+ // Server port - use 3001 to avoid conflict with Next.js dev server
30
+ const SERVER_PORT = process.env.PORT || 3001;
31
+
32
+ // Keep a global reference of the window object
33
+ let mainWindow;
34
+ let nextjsProcess;
35
+
36
+ function createWindow() {
37
+ // Create the browser window
38
+ mainWindow = new BrowserWindow({
39
+ width: 1400,
40
+ height: 900,
41
+ minWidth: 1200,
42
+ minHeight: 700,
43
+ icon: path.join(__dirname, '../public/icon.png'),
44
+ webPreferences: {
45
+ nodeIntegration: false,
46
+ contextIsolation: true,
47
+ preload: path.join(__dirname, 'preload.js'),
48
+ sandbox: false
49
+ },
50
+ titleBarStyle: 'default',
51
+ show: false
52
+ });
53
+
54
+ // Load the Next.js app
55
+ const startUrl = process.env.ELECTRON_START_URL || `http://localhost:3000`;
56
+ mainWindow.loadURL(startUrl);
57
+
58
+ // Show window when ready
59
+ mainWindow.once('ready-to-show', () => {
60
+ mainWindow.show();
61
+
62
+ // Open DevTools in development
63
+ if (process.env.NODE_ENV === 'development') {
64
+ mainWindow.webContents.openDevTools();
65
+ }
66
+ });
67
+
68
+ // Set up menu
69
+ setupMenu();
70
+
71
+ // Emitted when the window is closed
72
+ mainWindow.on('closed', () => {
73
+ mainWindow = null;
74
+ });
75
+
76
+ // Handle external links
77
+ mainWindow.webContents.setWindowOpenHandler(({ url }) => {
78
+ shell.openExternal(url);
79
+ return { action: 'deny' };
80
+ });
81
+ }
82
+
83
+ function setupMenu() {
84
+ const template = [
85
+ {
86
+ label: 'فایل',
87
+ submenu: [
88
+ {
89
+ label: 'تنظیمات',
90
+ accelerator: 'Ctrl+,',
91
+ click: () => {
92
+ mainWindow.webContents.send('navigate-to-settings');
93
+ }
94
+ },
95
+ { type: 'separator' },
96
+ {
97
+ label: 'خروج',
98
+ accelerator: process.platform === 'darwin' ? 'Cmd+Q' : 'Ctrl+Q',
99
+ click: () => {
100
+ app.quit();
101
+ }
102
+ }
103
+ ]
104
+ },
105
+ {
106
+ label: 'ویرایش',
107
+ submenu: [
108
+ { role: 'undo', label: 'بازگشت' },
109
+ { role: 'redo', label: 'دوباره' },
110
+ { type: 'separator' },
111
+ { role: 'cut', label: 'برش' },
112
+ { role: 'copy', label: 'کپی' },
113
+ { role: 'paste', label: 'چسباندن' }
114
+ ]
115
+ },
116
+ {
117
+ label: 'دیدن',
118
+ submenu: [
119
+ { role: 'reload', label: 'بارگذاری مجدد' },
120
+ { role: 'forceReload', label: 'بارگذاری اجباری' },
121
+ { role: 'toggleDevTools', label: 'ابزار توسعه' },
122
+ { type: 'separator' },
123
+ { role: 'resetZoom', label: 'بازنشانی زوم' },
124
+ { role: 'zoomIn', label: 'بزرگنمایی' },
125
+ { role: 'zoomOut', label: 'کوچکنمایی' },
126
+ { type: 'separator' },
127
+ { role: 'togglefullscreen', label: 'تمام صفحه' }
128
+ ]
129
+ },
130
+ {
131
+ label: 'کمک',
132
+ submenu: [
133
+ {
134
+ label: 'راهنما',
135
+ click: () => {
136
+ mainWindow.webContents.send('navigate-to-help');
137
+ }
138
+ },
139
+ {
140
+ label: 'درباره',
141
+ click: () => {
142
+ dialog.showMessageBox(mainWindow, {
143
+ type: 'info',
144
+ title: 'درباره GhadirSync-AI',
145
+ message: 'GhadirSync-AI نسخه 1.0.0',
146
+ detail: 'دستیار هوشمند توسعه نرمافزار با پشتیبانی از زبان فارسی\nساخته شده با Electron و Next.js\n\nBuilt with anycoder - https://huggingface.co/spaces/akhaliq/anycoder'
147
+ });
148
+ }
149
+ }
150
+ ]
151
+ }
152
+ ];
153
+
154
+ const menu = Menu.buildFromTemplate(template);
155
+ Menu.setApplicationMenu(menu);
156
+ }
157
+
158
+ // Express API Routes
159
+ expressApp.use(cors());
160
+ expressApp.use(express.json({ limit: '50mb' }));
161
+ expressApp.use(express.urlencoded({ extended: true, limit: '50mb' }));
162
+
163
+ // API Routes
164
+ expressApp.get('/api/health', (req, res) => {
165
+ res.json({ status: 'ok', timestamp: new Date().toISOString() });
166
+ });
167
+
168
+ // Model management
169
+ expressApp.get('/api/models', (req, res) => {
170
+ const modelsDir = path.join(app.getPath('userData'), 'models');
171
+ if (!fs.existsSync(modelsDir)) {
172
+ fs.mkdirSync(modelsDir, { recursive: true });
173
+ }
174
+
175
+ const models = fs.readdirSync(modelsDir).filter(file =>
176
+ file.endsWith('.gguf') || file.endsWith('.onnx') || file.endsWith('.bin')
177
+ ).map(file => ({
178
+ id: crypto.createHash('md5').update(file).digest('hex'),
179
+ name: file,
180
+ path: path.join(modelsDir, file),
181
+ size: fs.statSync(path.join(modelsDir, file)).size,
182
+ loaded: false
183
+ }));
184
+
185
+ res.json(models);
186
+ });
187
+
188
+ // Voice processing
189
+ expressApp.post('/api/voice/transcribe', async (req, res) => {
190
+ try {
191
+ const { audioData, language = 'fa' } = req.body;
192
+
193
+ // Here you would integrate with whisper-node or similar
194
+ // For now, return mock response
195
+ res.json({
196
+ text: language === 'fa' ? 'این یک تست صوتی است' : 'This is a voice test',
197
+ language: language,
198
+ confidence: 0.95
199
+ });
200
+ } catch (error) {
201
+ res.status(500).json({ error: error.message });
202
+ }
203
+ });
204
+
205
+ // Text to speech
206
+ expressApp.post('/api/voice/synthesize', async (req, res) => {
207
+ try {
208
+ const { text, voice = 'default', language = 'fa' } = req.body;
209
+
210
+ // Here you would integrate with VITS or similar TTS
211
+ // For now, return mock response
212
+ res.json({
213
+ audioUrl: `/api/voice/audio/${Date.now()}.wav`,
214
+ duration: text.length * 0.1
215
+ });
216
+ } catch (error) {
217
+ res.status(500).json({ error: error.message });
218
+ }
219
+ });
220
+
221
+ // Code execution sandbox
222
+ expressApp.post('/api/sandbox/execute', async (req, res) => {
223
+ try {
224
+ const { code, language, timeout = 30000 } = req.body;
225
+
226
+ // Create Docker container for sandboxed execution
227
+ const container = await docker.createContainer({
228
+ Image: language === 'javascript' ? 'node:20-alpine' : 'python:3.11-alpine',
229
+ Cmd: language === 'javascript' ? ['node', '-e', code] : ['python', '-c', code],
230
+ HostConfig: {
231
+ Memory: 512 * 1024 * 1024, // 512MB
232
+ CpuShares: 512,
233
+ NetworkMode: 'none', // No network access
234
+ ReadonlyRootfs: true,
235
+ SecurityOpt: ['no-new-privileges:true']
236
+ },
237
+ WorkingDir: '/sandbox',
238
+ OpenStdin: false,
239
+ AttachStdin: false,
240
+ AttachStdout: true,
241
+ AttachStderr: true,
242
+ Tty: false
243
+ });
244
+
245
+ await container.start();
246
+
247
+ const logs = await container.logs({
248
+ follow: true,
249
+ stdout: true,
250
+ stderr: true
251
+ });
252
+
253
+ let output = '';
254
+ logs.on('data', (chunk) => {
255
+ output += chunk.toString();
256
+ });
257
+
258
+ // Wait for completion or timeout
259
+ const timeoutPromise = new Promise((_, reject) =>
260
+ setTimeout(() => reject(new Error('Execution timeout')), timeout)
261
+ );
262
+
263
+ const waitPromise = container.wait();
264
+
265
+ try {
266
+ await Promise.race([waitPromise, timeoutPromise]);
267
+ } catch (error) {
268
+ await container.kill();
269
+ throw error;
270
+ }
271
+
272
+ const result = await container.inspect();
273
+ await container.remove();
274
+
275
+ res.json({
276
+ output: output,
277
+ exitCode: result.State.ExitCode,
278
+ success: result.State.ExitCode === 0,
279
+ executionTime: result.State.FinishedAt ?
280
+ new Date(result.State.FinishedAt).getTime() - new Date(result.State.StartedAt).getTime() : 0
281
+ });
282
+ } catch (error) {
283
+ res.status(500).json({ error: error.message });
284
+ }
285
+ });
286
+
287
+ // File operations
288
+ expressApp.post('/api/files/save', (req, res) => {
289
+ try {
290
+ const { path: filePath, content } = req.body;
291
+ const fullPath = path.join(app.getPath('documents'), 'GhadirSync-Projects', filePath);
292
+
293
+ fs.mkdirSync(path.dirname(fullPath), { recursive: true });
294
+ fs.writeFileSync(fullPath, content, 'utf8');
295
+
296
+ res.json({ success: true, path: fullPath });
297
+ } catch (error) {
298
+ res.status(500).json({ error: error.message });
299
+ }
300
+ });
301
+
302
+ expressApp.get('/api/files/read', (req, res) => {
303
+ try {
304
+ const { path: filePath } = req.query;
305
+ const fullPath = path.join(app.getPath('documents'), 'GhadirSync-Projects', filePath);
306
+
307
+ if (!fs.existsSync(fullPath)) {
308
+ return res.status(404).json({ error: 'File not found' });
309
+ }
310
+
311
+ const content = fs.readFileSync(fullPath, 'utf8');
312
+ res.json({ content, path: fullPath });
313
+ } catch (error) {
314
+ res.status(500).json({ error: error.message });
315
+ }
316
+ });
317
+
318
+ // Web browsing sandbox
319
+ expressApp.post('/api/web/browse', async (req, res) => {
320
+ try {
321
+ const { url, method = 'GET', headers = {} } = req.body;
322
+
323
+ // Validate URL against whitelist
324
+ const whitelist = store.get('webWhitelist', ['https://api.github.com', 'https://registry.npmjs.org']);
325
+ const urlObj = new URL(url);
326
+
327
+ if (!whitelist.some(w => url.startsWith(w))) {
328
+ return res.status(403).json({ error: 'URL not in whitelist' });
329
+ }
330
+
331
+ const response = await axios({ method, url, headers, timeout: 30000 });
332
+
333
+ // Log the request for audit
334
+ const logsDir = path.join(app.getPath('userData'), 'logs');
335
+ fs.mkdirSync(logsDir, { recursive: true });
336
+ fs.appendFileSync(
337
+ path.join(logsDir, 'web-access.log'),
338
+ `${new Date().toISOString()} - ${method} ${url} - ${response.status}\n`
339
+ );
340
+
341
+ res.json({
342
+ status: response.status,
343
+ data: response.data,
344
+ headers: response.headers
345
+ });
346
+ } catch (error) {
347
+ res.status(500).json({ error: error.message });
348
+ }
349
+ });
350
+
351
+ // Socket.IO for real-time communication
352
+ io.on('connection', (socket) => {
353
+ console.log('Client connected:', socket.id);
354
+
355
+ socket.on('voice-data', (data) => {
356
+ // Process voice data in real-time
357
+ socket.broadcast.emit('voice-processing', { status: 'processing' });
358
+ });
359
+
360
+ socket.on('agent-task', (task) => {
361
+ // Handle agent tasks
362
+ io.emit('agent-status', { taskId: task.id, status: 'started' });
363
+ });
364
+
365
+ socket.on('disconnect', () => {
366
+ console.log('Client disconnected:', socket.id);
367
+ });
368
+ });
369
+
370
+ // Initialize Next.js process
371
+ function startNextJSServer() {
372
+ const isDev = process.env.NODE_ENV === 'development';
373
+ const command = isDev ? 'npm' : 'node';
374
+ const args = isDev ? ['run', 'next-dev'] : ['node_modules/next/dist/bin/next', 'start'];
375
+
376
+ nextjsProcess = spawn(command, args, {
377
+ cwd: __dirname,
378
+ stdio: 'inherit',
379
+ shell: true
380
+ });
381
+
382
+ nextjsProcess.on('error', (error) => {
383
+ console.error('Failed to start Next.js:', error);
384
+ });
385
+
386
+ nextjsProcess.on('exit', (code) => {
387
+ console.log(`Next.js process exited with code ${code}`);
388
+ });
389
+ }
390
+
391
+ // IPC handlers
392
+ ipcMain.handle('get-app-version', () => app.getVersion());
393
+ ipcMain.handle('get-user-data-path', () => app.getPath('userData'));
394
+ ipcMain.handle('get-documents-path', () => app.getPath('documents'));
395
+ ipcMain.handle('show-open-dialog', (event, options) => dialog.showOpenDialog(mainWindow, options));
396
+ ipcMain.handle('show-save-dialog', (event, options) => dialog.showSaveDialog(mainWindow, options));
397
+ ipcMain.handle('set-store', (event, key, value) => store.set(key, value));
398
+ ipcMain.handle('get-store', (event, key, defaultValue) => store.get(key, defaultValue));
399
+ ipcMain.handle('delete-store', (event, key) => store.delete(key));
400
+ ipcMain.handle('clear-store', () => store.clear());
401
+
402
+ // This method will be called when Electron has finished initialization
403
+ app.whenReady().then(() => {
404
+ createWindow();
405
+ startNextJSServer();
406
+ server.listen(SERVER_PORT, () => {
407
+ console.log(`Backend server running on port ${SERVER_PORT}`);
408
+ });
409
+
410
+ app.on('activate', () => {
411
+ if (BrowserWindow.getAllWindows().length === 0) {
412
+ createWindow();
413
+ }
414
+ });
415
+ });
416
+
417
+ // Quit when all windows are closed
418
+ app.on('window-all-closed', () => {
419
+ if (process.platform !== 'darwin') {
420
+ app.quit();
421
+ }
422
+ });
423
+
424
+ app.on('before-quit', () => {
425
+ if (nextjsProcess) {
426
+ nextjsProcess.kill();
427
+ }
428
+ server.close();
429
+ });
430
+
431
+ // Security: Prevent navigation to external URLs
432
+ app.on('web-contents-created', (event, contents) => {
433
+ contents.on('will-navigate', (event, navigationUrl) => {
434
+ const parsedUrl = new URL(navigationUrl);
435
+ if (parsedUrl.origin !== 'http://localhost:3000') {
436
+ event.preventDefault();
437
+ }
438
+ });
439
+ });