Skip to content

Commit 4759779

Browse files
Merge pull request #17 from TensorBlock/image-generation-support
Image generation support
2 parents f866613 + 8b446b7 commit 4759779

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+3926
-742
lines changed

app/main.ts

Lines changed: 228 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* eslint-disable @typescript-eslint/no-unused-vars */
2-
import {app, BrowserWindow, ipcMain, shell, dialog} from 'electron';
2+
import {app, BrowserWindow, ipcMain, shell, dialog, Tray, Menu, nativeImage} from 'electron';
33
import * as path from 'path';
44
import * as fs from 'fs';
55
import * as os from 'os';
@@ -8,11 +8,151 @@ import { execSync } from 'child_process';
88

99
// Main window reference
1010
let win: BrowserWindow | null = null;
11+
// Tray reference
12+
let tray: Tray | null = null;
13+
14+
// Settings
15+
let closeToTray = true;
16+
let forceQuit = false; // Flag to indicate we're trying to actually quit
1117

1218
// Check if app is running in development mode
1319
const args = process.argv.slice(1);
1420
const serve = args.some(val => val === '--serve');
1521

22+
/**
23+
* Create the system tray
24+
*/
25+
function createTray() {
26+
if (tray) {
27+
return;
28+
}
29+
30+
// Get appropriate icon based on platform
31+
const iconPath = path.join(__dirname, '..', 'dist', 'logos', 'favicon.256x256.png');
32+
const icon = nativeImage.createFromPath(iconPath);
33+
34+
tray = new Tray(icon);
35+
tray.setToolTip('TensorBlock Desktop');
36+
37+
const contextMenu = Menu.buildFromTemplate([
38+
{ label: 'Open TensorBlock', click: () => {
39+
win?.show();
40+
win?.setSkipTaskbar(false); // Show in taskbar
41+
}},
42+
{ type: 'separator' },
43+
{ label: 'Quit', click: () => {
44+
forceQuit = true; // Set flag to bypass close-to-tray
45+
app.quit();
46+
}}
47+
]);
48+
49+
tray.setContextMenu(contextMenu);
50+
51+
tray.on('click', () => {
52+
if (win) {
53+
if (win.isVisible()) {
54+
win.hide();
55+
win.setSkipTaskbar(true); // Hide from taskbar
56+
} else {
57+
win.show();
58+
win.setSkipTaskbar(false); // Show in taskbar
59+
}
60+
}
61+
});
62+
}
63+
64+
/**
65+
* Set or remove auto launch on system startup
66+
*/
67+
function setAutoLaunch(enable: boolean): boolean {
68+
try {
69+
if (process.platform === 'win32') {
70+
const appPath = app.getPath('exe');
71+
const regKey = 'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run';
72+
const appName = app.getName();
73+
74+
if (enable) {
75+
// Add to registry to enable auto launch
76+
execSync(`reg add ${regKey} /v ${appName} /t REG_SZ /d "${appPath}" /f`);
77+
} else {
78+
// Remove from registry to disable auto launch
79+
execSync(`reg delete ${regKey} /v ${appName} /f`);
80+
}
81+
return true;
82+
} else if (process.platform === 'darwin') {
83+
const appPath = app.getPath('exe');
84+
const loginItemSettings = app.getLoginItemSettings();
85+
86+
// Set login item settings for macOS
87+
app.setLoginItemSettings({
88+
openAtLogin: enable,
89+
path: appPath
90+
});
91+
92+
return app.getLoginItemSettings().openAtLogin === enable;
93+
} else if (process.platform === 'linux') {
94+
// For Linux, create or remove a .desktop file in autostart directory
95+
const desktopFilePath = path.join(os.homedir(), '.config', 'autostart', `${app.getName()}.desktop`);
96+
97+
if (enable) {
98+
// Create directory if it doesn't exist
99+
const autoStartDir = path.dirname(desktopFilePath);
100+
if (!fs.existsSync(autoStartDir)) {
101+
fs.mkdirSync(autoStartDir, { recursive: true });
102+
}
103+
104+
// Create .desktop file
105+
const desktopFileContent = `
106+
[Desktop Entry]
107+
Type=Application
108+
Exec=${app.getPath('exe')}
109+
Hidden=false
110+
NoDisplay=false
111+
X-GNOME-Autostart-enabled=true
112+
Name=${app.getName()}
113+
Comment=${app.getName()} startup script
114+
`;
115+
fs.writeFileSync(desktopFilePath, desktopFileContent);
116+
} else if (fs.existsSync(desktopFilePath)) {
117+
// Remove .desktop file
118+
fs.unlinkSync(desktopFilePath);
119+
}
120+
121+
return true;
122+
}
123+
124+
return false;
125+
} catch (error) {
126+
console.error('Error setting auto launch:', error);
127+
return false;
128+
}
129+
}
130+
131+
/**
132+
* Check if app is set to auto launch on system startup
133+
*/
134+
function getAutoLaunchEnabled(): boolean {
135+
try {
136+
if (process.platform === 'win32') {
137+
const regKey = 'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run';
138+
const appName = app.getName();
139+
140+
const output = execSync(`reg query ${regKey} /v ${appName} 2>nul`).toString();
141+
return output.includes(appName);
142+
} else if (process.platform === 'darwin') {
143+
return app.getLoginItemSettings().openAtLogin;
144+
} else if (process.platform === 'linux') {
145+
const desktopFilePath = path.join(os.homedir(), '.config', 'autostart', `${app.getName()}.desktop`);
146+
return fs.existsSync(desktopFilePath);
147+
}
148+
149+
return false;
150+
} catch (error) {
151+
// If command fails (e.g., key doesn't exist), auto launch is not enabled
152+
return false;
153+
}
154+
}
155+
16156
/**
17157
* Creates the main application window
18158
*/
@@ -29,8 +169,8 @@ function createWindow(): BrowserWindow {
29169
frame: false,
30170
fullscreenable: false,
31171
autoHideMenuBar: true,
32-
minWidth: 600,
33-
minHeight: 600,
172+
minWidth: 800,
173+
minHeight: 700,
34174
webPreferences: {
35175
preload: path.join(__dirname, 'preload.js'),
36176
contextIsolation: true,
@@ -180,14 +320,67 @@ function createWindow(): BrowserWindow {
180320

181321
// Close application
182322
ipcMain.on('close-app', () => {
183-
app.quit();
323+
if (closeToTray && !forceQuit) {
324+
win?.hide();
325+
win?.setSkipTaskbar(true); // Hide from taskbar
326+
} else {
327+
forceQuit = true; // Ensure we're really quitting
328+
app.quit();
329+
}
184330
});
185331

186332
// Open URL in default browser
187333
ipcMain.on('open-url', (event, url) => {
188334
shell.openExternal(url);
189335
});
190336

337+
// Auto-startup handlers
338+
ipcMain.handle('get-auto-launch', () => {
339+
return getAutoLaunchEnabled();
340+
});
341+
342+
ipcMain.handle('set-auto-launch', (event, enable) => {
343+
return setAutoLaunch(enable);
344+
});
345+
346+
// Tray handlers
347+
ipcMain.handle('set-close-to-tray', (event, enable) => {
348+
closeToTray = enable;
349+
return true;
350+
});
351+
352+
ipcMain.handle('get-close-to-tray', () => {
353+
return closeToTray;
354+
});
355+
356+
ipcMain.handle('set-startup-to-tray', (event, enable) => {
357+
// Store this preference for the next app start
358+
try {
359+
const configPath = path.join(app.getPath('userData'), 'config.json');
360+
let config = {};
361+
362+
if (fs.existsSync(configPath)) {
363+
config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
364+
}
365+
366+
config = { ...config, startupToTray: enable };
367+
fs.writeFileSync(configPath, JSON.stringify(config));
368+
return true;
369+
} catch (error) {
370+
console.error('Error saving startup to tray setting:', error);
371+
return false;
372+
}
373+
});
374+
375+
// Listen for window close event
376+
win.on('close', (e) => {
377+
if (closeToTray && !forceQuit) {
378+
e.preventDefault();
379+
win?.hide();
380+
win?.setSkipTaskbar(true); // Hide from taskbar
381+
}
382+
});
383+
191384
// Disable page refresh in production
192385
if (process.env.NODE_ENV === 'production') {
193386
win.webContents.on('before-input-event', (event, input) => {
@@ -290,10 +483,36 @@ function getCPUName() {
290483
try {
291484
app.commandLine.appendSwitch('class', 'tensorblock-desktop');
292485

486+
// Set force quit flag when app is about to quit
487+
app.on('before-quit', () => {
488+
forceQuit = true;
489+
});
490+
293491
// Initialize app when Electron is ready
294492
// Added delay to fix black background issue with transparent windows
295493
// See: https://github.com/electron/electron/issues/15947
296-
app.on('ready', () => setTimeout(createWindow, 400));
494+
app.on('ready', () => {
495+
setTimeout(() => {
496+
// Create window
497+
win = createWindow();
498+
499+
// Create tray
500+
createTray();
501+
502+
// Check if we should start minimized to tray
503+
try {
504+
const configPath = path.join(app.getPath('userData'), 'config.json');
505+
if (fs.existsSync(configPath)) {
506+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
507+
if (config.startupToTray) {
508+
win.hide();
509+
}
510+
}
511+
} catch (error) {
512+
console.error('Error reading config file:', error);
513+
}
514+
}, 400);
515+
});
297516

298517
// Quit when all windows are closed
299518
app.on('window-all-closed', () => {
@@ -303,7 +522,10 @@ try {
303522
// Re-create window if activated and no windows exist
304523
app.on('activate', () => {
305524
if (win === null) {
306-
createWindow();
525+
win = createWindow();
526+
} else {
527+
win.show();
528+
win.setSkipTaskbar(false); // Show in taskbar
307529
}
308530
});
309531
} catch (_e) {

app/preload.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,11 @@ contextBridge.exposeInMainWorld('electron', {
2929
saveFile: (fileBuffer: ArrayBuffer | string, fileName: string, fileType: string) =>
3030
ipcRenderer.invoke('save-file', { fileBuffer, fileName, fileType }),
3131
openFile: (filePath: string) => ipcRenderer.invoke('open-file', filePath),
32+
33+
// Auto-startup and tray functions
34+
getAutoLaunch: () => ipcRenderer.invoke('get-auto-launch'),
35+
setAutoLaunch: (enable: boolean) => ipcRenderer.invoke('set-auto-launch', enable),
36+
setCloseToTray: (enable: boolean) => ipcRenderer.invoke('set-close-to-tray', enable),
37+
getCloseToTray: () => ipcRenderer.invoke('get-close-to-tray'),
38+
setStartupToTray: (enable: boolean) => ipcRenderer.invoke('set-startup-to-tray', enable),
3239
});

src/App.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { ChatPage } from './components/pages/ChatPage';
33
import { ImageGenerationPage } from './components/pages/ImageGenerationPage';
44
import { TranslationPage } from './components/pages/TranslationPage';
55
import { FileManagementPage } from './components/pages/FileManagementPage';
6+
import { MCPServerPage } from './components/pages/MCPServerPage';
67
import MainLayout from './components/layout/MainLayout';
78
import DatabaseInitializer from './components/core/DatabaseInitializer';
89

@@ -38,6 +39,7 @@ function App() {
3839
{activePage === 'image' && <ImageGenerationPage />}
3940
{activePage === 'translation' && <TranslationPage />}
4041
{activePage === 'files' && <FileManagementPage />}
42+
{activePage === 'mcpserver' && <MCPServerPage />}
4143
</MainLayout>
4244
</DatabaseInitializer>
4345
);

0 commit comments

Comments
 (0)