diff --git a/package.json b/package.json index 8bf660c..752e16c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { - "name": "imposter", + "name": "system-utility", "version": "1.0.0", - "description": "Imposter: Beating a Broken System", + "description": "System Utility: System performance evaluation tool", "author": "Puskar Roy", "repository": { "type": "git", @@ -37,8 +37,8 @@ "prettier": "^3.2.5" }, "build": { - "appId": "com.imposter.app", - "productName": "Imposter", + "appId": "com.system-utility.app", + "productName": "System Utility", "directories": { "output": "dist" }, diff --git a/src/main/index.js b/src/main/index.js index 2151e71..3d28b00 100644 --- a/src/main/index.js +++ b/src/main/index.js @@ -2,6 +2,14 @@ const { app, session, BrowserWindow } = require('electron'); const path = require('path'); require('dotenv').config(); +if (app.isPackaged) { + console.log = () => {}; + console.debug = () => {}; + console.info = () => {}; + console.warn = () => {}; + console.error = () => {}; +} + const { createMainWindow, getMainWindow } = require('./window-manager'); const { registerShortcuts, unregisterShortcuts } = require('./shortcuts'); const { registerIpcHandlers } = require('./ipc-handlers'); diff --git a/src/main/ipc-handlers.js b/src/main/ipc-handlers.js index 567c8bc..c6cc31a 100644 --- a/src/main/ipc-handlers.js +++ b/src/main/ipc-handlers.js @@ -165,6 +165,21 @@ function registerIpcHandlers() { console.error('[IPC] minimize error:', err); } }); + + ipcMain.on('maximize-app', () => { + try { + const mainWindow = getMainWindow(); + if (mainWindow && !mainWindow.isDestroyed()) { + if (mainWindow.isMaximized()) { + mainWindow.unmaximize(); + } else { + mainWindow.maximize(); + } + } + } catch (err) { + console.error('[IPC] maximize error:', err); + } + }); ipcMain.on('close-app', () => { try { app.quit(); } catch (err) { @@ -197,6 +212,14 @@ function registerIpcHandlers() { } }); + ipcMain.on('register-window-listeners', (event) => { + const win = getMainWindow(); + if (win && !win.isDestroyed()) { + win.on('maximize', () => safeSendToWindow(win, 'window-state', 'maximized')); + win.on('unmaximize', () => safeSendToWindow(win, 'window-state', 'normal')); + } + }); + ipcMain.on('send-ai-to-island', (event, text) => { try { const { getIslandWindow } = require('./window-manager'); diff --git a/src/main/preload.js b/src/main/preload.js index 7551e0c..1659137 100644 --- a/src/main/preload.js +++ b/src/main/preload.js @@ -3,6 +3,7 @@ const { contextBridge, ipcRenderer } = require('electron'); contextBridge.exposeInMainWorld('electronAPI', { // Basic App Controls minimizeApp: () => ipcRenderer.send('minimize-app'), + maximizeApp: () => ipcRenderer.send('maximize-app'), closeApp: () => ipcRenderer.send('close-app'), restartApp: () => ipcRenderer.send('restart-app'), setAppMode: (mode) => ipcRenderer.send('set-app-mode', mode), @@ -38,5 +39,7 @@ contextBridge.exposeInMainWorld('electronAPI', { // Window Management openIslandWindow: () => ipcRenderer.send('open-island-window'), - closeIslandWindow: () => ipcRenderer.send('close-island-window') + closeIslandWindow: () => ipcRenderer.send('close-island-window'), + registerWindowListeners: () => ipcRenderer.send('register-window-listeners'), + onWindowState: (callback) => ipcRenderer.on('window-state', (event, state) => callback(state)) }); diff --git a/src/main/shortcuts.js b/src/main/shortcuts.js index 635a578..10adedb 100644 --- a/src/main/shortcuts.js +++ b/src/main/shortcuts.js @@ -1,5 +1,5 @@ const { globalShortcut, app, screen, desktopCapturer } = require('electron'); -const { getMainWindow, createSnipperWindow } = require('./window-manager'); +const { getMainWindow, createSnipperWindow, getIslandWindow, getSnipperWindow } = require('./window-manager'); const path = require('path'); function safeRegister(accelerator, callback) { @@ -21,8 +21,30 @@ function safeSend(channel, ...args) { } } +let isAppHidden = false; + +function toggleVisibility() { + isAppHidden = !isAppHidden; + const mainWin = getMainWindow(); + const islandWin = getIslandWindow(); + const snipperWin = getSnipperWindow(); + + const windows = [mainWin, islandWin, snipperWin]; + + windows.forEach(win => { + if (win && !win.isDestroyed()) { + if (isAppHidden) { + win.hide(); + } else { + win.show(); + if (win === mainWin) win.focus(); + } + } + }); +} + function registerShortcuts() { - safeRegister('CommandOrControl+Shift+Q', () => app.quit()); + safeRegister('CommandOrControl+Shift+Q', () => toggleVisibility()); const moveAmount = 15; const move = (dx, dy) => { @@ -78,11 +100,6 @@ function registerShortcuts() { console.error('[SHORTCUT] Snipping error:', err); } }); - - safeRegister('CommandOrControl+Shift+D', () => { - const win = getMainWindow(); - if (win && !win.isDestroyed()) win.webContents.toggleDevTools(); - }); } function unregisterShortcuts() { diff --git a/src/main/window-manager.js b/src/main/window-manager.js index 98af70e..a717ff6 100644 --- a/src/main/window-manager.js +++ b/src/main/window-manager.js @@ -48,11 +48,13 @@ function createMainWindow(preloadPath) { skipTaskbar: true, alwaysOnTop: true, hasShadow: false, - resizable: false, + resizable: true, + maximizable: true, webPreferences: { nodeIntegration: false, contextIsolation: true, - preload: preloadPath + preload: preloadPath, + devTools: false } }); @@ -96,7 +98,8 @@ function createIslandWindow(preloadPath) { webPreferences: { nodeIntegration: false, contextIsolation: true, - preload: preloadPath + preload: preloadPath, + devTools: false } }); @@ -137,7 +140,8 @@ function createSnipperWindow(preloadPath, screenSource) { enableLargerThanScreen: true, webPreferences: { preload: preloadPath, - contextIsolation: true + contextIsolation: true, + devTools: false } }); diff --git a/src/renderer/css/base.css b/src/renderer/css/base.css index 01504f7..a1c93d5 100644 --- a/src/renderer/css/base.css +++ b/src/renderer/css/base.css @@ -46,3 +46,72 @@ body { ::-webkit-scrollbar-thumb:hover { background: #666; } + +/* Custom HUD Notifications */ +.notification-container { + position: fixed; + top: 20px; + left: 50%; + transform: translateX(-50%); + z-index: 10000; + display: flex; + flex-direction: column; + align-items: center; + gap: 10px; + pointer-events: none; +} + +.notification { + background: rgba(30, 30, 30, 0.95); + color: var(--text-primary); + padding: 12px 20px; + border-radius: 12px; + font-size: 13px; + font-weight: 500; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4); + border: 1px solid var(--border-color); + backdrop-filter: blur(8px); + display: flex; + align-items: center; + gap: 12px; + animation: slideDownIn 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards; + pointer-events: auto; + max-width: 400px; + width: max-content; +} + +.notification.error { + border-left: 4px solid #ff4d4d; +} + +.notification.success { + border-left: 4px solid var(--accent-color); +} + +.notification-icon { + display: flex; + align-items: center; + justify-content: center; +} + +@keyframes slideDownIn { + from { + opacity: 0; + transform: translateY(-20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.notification.fade-out { + animation: fadeOut 0.3s forwards; +} + +@keyframes fadeOut { + to { + opacity: 0; + transform: translateY(-10px); + } +} diff --git a/src/renderer/css/components/split-view.css b/src/renderer/css/components/split-view.css new file mode 100644 index 0000000..7be13ec --- /dev/null +++ b/src/renderer/css/components/split-view.css @@ -0,0 +1,223 @@ +.split-thread { + display: none; + flex-direction: row; + height: 100%; + width: 100%; + overflow: hidden; + gap: 0; +} + +.split-thread.active { + display: flex; +} + +.split-column { + flex: 1; + display: flex; + flex-direction: column; + min-width: 0; + height: 100%; + background: rgba(255, 255, 255, 0.02); +} + +.split-column.left { + border-right: none; +} + +.column-header { + padding: 12px 16px; + border-bottom: 1px solid var(--border-color); + background: rgba(0, 0, 0, 0.2); + display: flex; + align-items: center; + justify-content: center; + z-index: 10; +} + +.split-messages { + flex: 1; + overflow-y: auto; + padding: 20px; + display: flex; + flex-direction: column; + gap: 16px; +} + +.split-divider { + width: 1px; + background: var(--border-color); + position: relative; + display: flex; + align-items: center; + justify-content: center; + z-index: 5; +} + +.v-pill { + position: absolute; + top: 10px; + background: var(--bg-color); + border: 1px solid var(--border-color); + color: var(--text-secondary); + font-weight: 800; + font-size: 10px; + width: 24px; + height: 24px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5); + transform: translateY(12px); +} + +/* Response Label Style from Mockup */ +.model-response-label { + font-size: 11px; + font-weight: 700; + color: var(--accent-color); + text-transform: uppercase; + letter-spacing: 0.1em; + margin-bottom: 8px; + opacity: 0.8; +} + +/* Adjustments for chat bubbles in split view */ +.split-messages .chat-message-ai, +.split-messages .chat-message-user { + max-width: 100%; + margin-left: 0; + margin-right: 0; + font-size: 13px; + line-height: 1.5; + padding: 12px 16px; + border-radius: 12px; +} + +.split-messages .chat-message-ai { + background: rgba(255, 255, 255, 0.04); +} + +/* Fix for Thinking box (loading state) */ +.split-messages .chat-message-ai:has(.loading) { + background: transparent; + padding: 0 12px; + margin-bottom: 12px; +} + +.split-messages .loading { + margin-top: 0; + background: rgba(255, 255, 255, 0.06); + padding: 10px 16px; + border-radius: 20px; + display: inline-flex; + align-items: center; + border: 1px solid rgba(255, 255, 255, 0.08); + box-shadow: 0 4px 15px rgba(0,0,0,0.2); +} + +.split-messages .loading span { + font-size: 13px; + letter-spacing: 0.02em; +} + +/* Scrollbar styling for split view - Sleeker */ +.split-messages::-webkit-scrollbar { + width: 3px; +} + +.split-messages::-webkit-scrollbar-track { + background: transparent; +} + +.split-messages::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.12); + border-radius: 10px; +} + +.split-messages::-webkit-scrollbar-thumb:hover { + background: var(--accent-color); +} + +/* Dropdown Overrides for Split Headers */ +.column-header .custom-dropdown { + max-width: 280px; +} + +.column-header .dropdown-trigger { + background: rgba(255, 255, 255, 0.04); + border: 1px solid rgba(255, 255, 255, 0.06); + height: 36px; + padding: 0 14px; + border-radius: 10px; +} + +.column-header .dropdown-trigger:hover { + background: rgba(255, 255, 255, 0.08); + border-color: rgba(255, 255, 255, 0.1); +} + +.column-header .dropdown-value { + font-weight: 600; + font-size: 12px; + color: var(--text-primary); +} + +/* Speed and Performance Badges */ +.speed-badge { + font-size: 10px; + font-weight: 600; + color: var(--text-secondary); + background: rgba(255, 255, 255, 0.05); + padding: 2px 6px; + border-radius: 4px; + margin-left: 8px; + vertical-align: middle; +} + +.faster-badge { + color: #ff9d00; + background: rgba(255, 157, 0, 0.1); + border: 1px solid rgba(255, 157, 0, 0.2); + font-size: 9px; + padding: 1px 5px; + text-transform: uppercase; + animation: pulse 2s infinite; +} + +@keyframes pulse { + 0% { opacity: 0.8; } + 50% { opacity: 1; transform: scale(1.05); } + 100% { opacity: 0.8; } +} + +.split-title-header { + font-family: 'Outfit', sans-serif; + font-size: 14px; + font-weight: 700; + color: var(--text-primary); + letter-spacing: 0.03em; + text-transform: uppercase; + display: flex; + align-items: center; + gap: 8px; +} + +.split-title-header::before { + content: ''; + display: block; + width: 3px; + height: 14px; + background: var(--accent-color); + border-radius: 10px; +} + +/* Animations */ +.split-thread.active { + animation: fadeIn 0.3s ease-out; +} + +@keyframes fadeIn { + from { opacity: 0; transform: translateY(10px); } + to { opacity: 1; transform: translateY(0); } +} diff --git a/src/renderer/css/layout.css b/src/renderer/css/layout.css index 9005398..40069f8 100644 --- a/src/renderer/css/layout.css +++ b/src/renderer/css/layout.css @@ -4,10 +4,24 @@ height: 100%; width: 100%; max-width: 900px; + width: 95vw; background-color: var(--bg-color); border-radius: 12px; overflow: hidden; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4); + transition: max-width 0.4s cubic-bezier(0.4, 0, 0.2, 1); +} + +body.split-mode-active .app-container { + max-width: 1400px; +} + +body.is-maximized .app-container { + max-width: 100% !important; + width: 100% !important; + height: 100vh !important; + border-radius: 0; + box-shadow: none; } .app-header { @@ -78,7 +92,8 @@ transition: all 0.2s; } -.win-btn.win-min:hover { +.win-btn.win-min:hover, +.win-btn.win-max:hover { background-color: rgba(255, 255, 255, 0.1); color: var(--text-primary); } diff --git a/src/renderer/index.html b/src/renderer/index.html index ea1f623..832eb66 100644 --- a/src/renderer/index.html +++ b/src/renderer/index.html @@ -5,7 +5,7 @@ -