diff --git a/electron/main.cjs b/electron/main.cjs deleted file mode 100644 index c53dfa5..0000000 --- a/electron/main.cjs +++ /dev/null @@ -1,740 +0,0 @@ -const { app, BrowserWindow, Menu, dialog, ipcMain, shell } = require('electron') -const path = require('path') -const { is } = require('@electron-toolkit/utils') -const fs = require('fs') -const { execFile } = require('child_process') -const { promisify } = require('util') - -let mainWindow -let settingsWindow = null -const execFileAsync = promisify(execFile) - -const DEFAULT_BACKEND_API_URL = 'http://127.0.0.1:8000' -const DEFAULT_OLLAMA_API_URL = 'http://127.0.0.1:11434' -const DEFAULT_EMBED_MODEL = 'nomic-embed-text:latest' -const DEFAULT_RERANK_MODEL = DEFAULT_EMBED_MODEL -const DEFAULT_TRANSCRIPTION_MODEL = 'base' -const BGE_EMBED_MODEL = 'bge-m3:latest' -const REPO_ROOT = path.resolve(__dirname, '..') -const UPDATE_REMOTE_URL = 'https://giers10.uber.space/giers10/Heimgeist.git' -const UPDATE_BRANCH = 'master' -const GIT_ENV = { ...process.env, GIT_TERMINAL_PROMPT: '0' } -const DEV_WRAPPER_RELAUNCH_CODE = Number(process.env.HEIMGEIST_DEV_RELAUNCH_CODE || 75) -const settingsFilePath = process.env.HEIMGEIST_SETTINGS_FILE || path.join(app.getPath('userData'), 'settings.json') -let appSettings = {} -let lastUpdateCheckResult = null -let activeUpdateCheck = null -const DEFAULT_UI_SCALE = 1 -const MIN_UI_SCALE = 0.7 -const MAX_UI_SCALE = 1.3 -const DEFAULT_OPEN_DEVTOOLS_ON_STARTUP = false -const DEFAULT_AUDIO_INPUT_ENABLED = true -const DEFAULT_AUDIO_INPUT_DEVICE_ID = '' -const DEFAULT_AUDIO_INPUT_LANGUAGE = '' -const CHANGELOG_PAGE_SIZE = 50 -const CHANGELOG_MESSAGE_MAX_LENGTH = 500 - -const defaultSettings = { - backendApiUrl: DEFAULT_BACKEND_API_URL, - ollamaApiUrl: DEFAULT_OLLAMA_API_URL, - chatModel: 'llama3', - visionModel: '', - embedModel: DEFAULT_EMBED_MODEL, - rerankModel: DEFAULT_RERANK_MODEL, - transcriptionModel: DEFAULT_TRANSCRIPTION_MODEL, - colorScheme: 'Default', - uiScale: DEFAULT_UI_SCALE, - openDevToolsOnStartup: DEFAULT_OPEN_DEVTOOLS_ON_STARTUP, - audioInputEnabled: DEFAULT_AUDIO_INPUT_ENABLED, - audioInputDeviceId: DEFAULT_AUDIO_INPUT_DEVICE_ID, - audioInputLanguage: DEFAULT_AUDIO_INPUT_LANGUAGE, -} - -function normalizeEmbedModel(value) { - const trimmed = String(value || '').trim() - if (!trimmed) { - return DEFAULT_EMBED_MODEL - } - - const lowered = trimmed.toLowerCase() - if (lowered === 'bge' || lowered === 'bge-m3' || lowered === BGE_EMBED_MODEL) { - return BGE_EMBED_MODEL - } - if (lowered === 'nomic' || lowered === 'nomic-embed-text' || lowered === DEFAULT_EMBED_MODEL) { - return DEFAULT_EMBED_MODEL - } - return trimmed -} - -function normalizeRerankModel(value) { - return normalizeEmbedModel(value) -} - -function normalizeModelName(value, fallback = '') { - const trimmed = String(value || '').trim() - return trimmed || fallback -} - -function normalizeTranscriptionModel(value) { - return normalizeModelName(value, DEFAULT_TRANSCRIPTION_MODEL) -} - -function looksLikeOllamaUrl(value) { - if (typeof value !== 'string') { - return false - } - - try { - const parsed = new URL(value) - if (parsed.port === '11434') { - return true - } - - return /^\/api\/?$/i.test(parsed.pathname || '') - } catch (_error) { - return false - } -} - -function migrateSettings(rawSettings) { - const source = rawSettings && typeof rawSettings === 'object' ? rawSettings : {} - const nextSettings = { ...defaultSettings, ...source } - let migrated = false - - if (!Object.prototype.hasOwnProperty.call(source, 'backendApiUrl') && typeof source.ollamaApiUrl === 'string') { - if (looksLikeOllamaUrl(source.ollamaApiUrl)) { - nextSettings.backendApiUrl = DEFAULT_BACKEND_API_URL - nextSettings.ollamaApiUrl = source.ollamaApiUrl - } else { - nextSettings.backendApiUrl = source.ollamaApiUrl - nextSettings.ollamaApiUrl = DEFAULT_OLLAMA_API_URL - } - migrated = true - } - - if (!Object.prototype.hasOwnProperty.call(source, 'openDevToolsOnStartup')) { - migrated = true - } - - if (!Object.prototype.hasOwnProperty.call(source, 'rerankModel')) { - nextSettings.rerankModel = nextSettings.embedModel - migrated = true - } - - if (!Object.prototype.hasOwnProperty.call(source, 'visionModel')) { - nextSettings.visionModel = nextSettings.chatModel || '' - migrated = true - } - - if (!Object.prototype.hasOwnProperty.call(source, 'transcriptionModel')) { - nextSettings.transcriptionModel = DEFAULT_TRANSCRIPTION_MODEL - migrated = true - } - - nextSettings.backendApiUrl = String(nextSettings.backendApiUrl || '').trim() - nextSettings.ollamaApiUrl = String(nextSettings.ollamaApiUrl || '').trim() - nextSettings.chatModel = normalizeModelName(nextSettings.chatModel) - nextSettings.visionModel = normalizeModelName(nextSettings.visionModel) - nextSettings.embedModel = normalizeEmbedModel(nextSettings.embedModel) - nextSettings.rerankModel = normalizeRerankModel(nextSettings.rerankModel) - nextSettings.transcriptionModel = normalizeTranscriptionModel(nextSettings.transcriptionModel) - nextSettings.openDevToolsOnStartup = normalizeOpenDevToolsOnStartup(nextSettings.openDevToolsOnStartup) - nextSettings.audioInputEnabled = normalizeBooleanSetting(nextSettings.audioInputEnabled) - nextSettings.audioInputDeviceId = String(nextSettings.audioInputDeviceId || '').trim() - nextSettings.audioInputLanguage = String(nextSettings.audioInputLanguage || '').trim().toLowerCase() - - return { nextSettings, migrated } -} - -function normalizeUiScale(value) { - const numericValue = Number(value) - if (!Number.isFinite(numericValue)) { - return DEFAULT_UI_SCALE - } - - return Math.min(MAX_UI_SCALE, Math.max(MIN_UI_SCALE, Math.round(numericValue * 100) / 100)) -} - -function normalizeBooleanSetting(value) { - if (typeof value === 'string') { - const trimmed = value.trim().toLowerCase() - if (trimmed === 'true' || trimmed === '1' || trimmed === 'yes' || trimmed === 'on') { - return true - } - - if (trimmed === 'false' || trimmed === '0' || trimmed === 'no' || trimmed === 'off' || trimmed === '') { - return false - } - } - - return value === true -} - -function normalizeOpenDevToolsOnStartup(value) { - return normalizeBooleanSetting(value) -} - -function applyUiScaleToWindow(window) { - if (!window || window.isDestroyed()) { - return - } - - window.webContents.setZoomFactor(normalizeUiScale(appSettings.uiScale)) -} - -function applyUiScaleToAllWindows() { - BrowserWindow.getAllWindows().forEach(applyUiScaleToWindow) -} - -function shouldAutoOpenDevTools() { - return is.dev && appSettings.openDevToolsOnStartup === true -} - -function applyDevToolsPreferenceToWindow(window) { - if (!is.dev || !window || window.isDestroyed()) { - return - } - - const webContents = window.webContents - if (!webContents) { - return - } - - if (shouldAutoOpenDevTools()) { - if (!webContents.isDevToolsOpened()) { - webContents.openDevTools({ mode: 'detach' }) - } - return - } - - if (webContents.isDevToolsOpened()) { - webContents.closeDevTools() - } -} - -function applyDevToolsPreferenceToAllWindows() { - BrowserWindow.getAllWindows().forEach(applyDevToolsPreferenceToWindow) -} - -function loadSettings() { - try { - if (fs.existsSync(settingsFilePath)) { - const data = fs.readFileSync(settingsFilePath, 'utf8') - const { nextSettings, migrated } = migrateSettings(JSON.parse(data)) - appSettings = nextSettings - if (migrated) { - saveSettings() - } - } else { - appSettings = { ...defaultSettings } - saveSettings() - } - appSettings.uiScale = normalizeUiScale(appSettings.uiScale) - } catch (error) { - console.error('Failed to load settings:', error) - appSettings = { ...defaultSettings } - } -} - -function saveSettings() { - try { - fs.mkdirSync(path.dirname(settingsFilePath), { recursive: true }) - fs.writeFileSync(settingsFilePath, JSON.stringify(appSettings, null, 2), 'utf8') - } catch (error) { - console.error('Failed to save settings:', error) - } -} - -function setUpdateStatus(status) { - lastUpdateCheckResult = { - state: 'idle', - message: '', - checkedAt: new Date().toISOString(), - localCommit: null, - remoteCommit: null, - branch: null, - restartScheduled: false, - ...status, - } - - return lastUpdateCheckResult -} - -async function runGitCommand(args, options = {}) { - return execFileAsync('git', ['-C', REPO_ROOT, ...args], { - env: GIT_ENV, - maxBuffer: 1024 * 1024, - timeout: options.timeout ?? 15000, - }) -} - -function normalizeChangelogPage(value) { - const parsed = Number.parseInt(value, 10) - if (!Number.isFinite(parsed) || parsed < 1) { - return 1 - } - - return parsed -} - -function truncateChangelogMessage(message) { - const normalized = String(message || '') - .replace(/\s+/g, ' ') - .trim() - - if (!normalized) { - return 'No commit message.' - } - - if (normalized.length <= CHANGELOG_MESSAGE_MAX_LENGTH) { - return normalized - } - - return `${normalized.slice(0, CHANGELOG_MESSAGE_MAX_LENGTH - 3).trimEnd()}...` -} - -function parseChangelogOutput(output) { - return String(output || '') - .split('\u0000\u0000') - .map((record) => record.trim()) - .filter(Boolean) - .map((record) => { - const [hash = '', committedAt = '', ...messageParts] = record.split('\u0000') - return { - hash: hash.trim(), - committedAt: committedAt.trim(), - message: truncateChangelogMessage(messageParts.join('\u0000')), - } - }) - .filter((entry) => entry.hash && entry.message) -} - -async function getChangelogPage(page = 1) { - const normalizedPage = normalizeChangelogPage(page) - const pageSize = CHANGELOG_PAGE_SIZE - const skip = (normalizedPage - 1) * pageSize - const { stdout } = await runGitCommand([ - 'log', - `--max-count=${pageSize + 1}`, - `--skip=${skip}`, - '--format=%H%x00%cI%x00%B%x00%x00', - ], { timeout: 30000 }) - - const entries = parseChangelogOutput(stdout) - - return { - page: normalizedPage, - pageSize, - hasMore: entries.length > pageSize, - entries: entries.slice(0, pageSize), - } -} - -function scheduleAppRestart() { - setTimeout(() => { - if (process.env.HEIMGEIST_DEV_WRAPPER === '1') { - app.exit(DEV_WRAPPER_RELAUNCH_CODE) - return - } - - app.relaunch() - app.exit(0) - }, 300) -} - -function parseGitStatusPaths(statusOutput) { - return statusOutput - .split('\n') - .map((line) => line.trimEnd()) - .filter(Boolean) - .map((line) => line.slice(3).trim()) -} - -function formatChangedPaths(paths) { - if (!Array.isArray(paths) || paths.length === 0) { - return '' - } - - const preview = paths.slice(0, 3).join(', ') - return paths.length > 3 ? `${preview}, ...` : preview -} - -async function performUpdateCheck(trigger = 'manual') { - if (!fs.existsSync(path.join(REPO_ROOT, '.git'))) { - return setUpdateStatus({ - state: 'unavailable', - trigger, - message: 'Update check unavailable: no Git checkout found.', - }) - } - - setUpdateStatus({ - state: 'checking', - trigger, - message: 'Checking remote repository for updates...', - }) - - try { - const [ - { stdout: localStdout }, - { stdout: branchStdout }, - { stdout: remoteStdout }, - { stdout: worktreeStdout }, - ] = await Promise.all([ - runGitCommand(['rev-parse', 'HEAD']), - runGitCommand(['branch', '--show-current']), - runGitCommand(['ls-remote', UPDATE_REMOTE_URL, `refs/heads/${UPDATE_BRANCH}`]), - runGitCommand(['status', '--porcelain', '--untracked-files=no']), - ]) - - const localCommit = localStdout.trim() - const branch = branchStdout.trim() || null - const remoteCommit = remoteStdout.trim().split(/\s+/)[0] || null - const changedPaths = parseGitStatusPaths(worktreeStdout) - const worktreeDirty = changedPaths.length > 0 - - if (!localCommit) { - throw new Error('Could not resolve the current local commit hash.') - } - - if (!remoteCommit) { - throw new Error(`Could not resolve the remote ${UPDATE_BRANCH} commit hash.`) - } - - if (localCommit === remoteCommit) { - return setUpdateStatus({ - state: 'up-to-date', - trigger, - branch, - localCommit, - remoteCommit, - message: 'Heimgeist is already up to date.', - }) - } - - if (branch && branch !== UPDATE_BRANCH) { - return setUpdateStatus({ - state: 'skipped', - trigger, - branch, - localCommit, - remoteCommit, - message: `Update skipped: current branch is "${branch}", expected "${UPDATE_BRANCH}".`, - }) - } - - if (worktreeDirty) { - const changedPathSummary = formatChangedPaths(changedPaths) - return setUpdateStatus({ - state: 'skipped', - trigger, - branch: branch || UPDATE_BRANCH, - localCommit, - remoteCommit, - message: changedPathSummary - ? `Update skipped: tracked local changes detected in ${changedPathSummary}.` - : 'Update skipped: tracked local changes detected.', - }) - } - - setUpdateStatus({ - state: 'updating', - trigger, - branch: branch || UPDATE_BRANCH, - localCommit, - remoteCommit, - message: 'Update found. Pulling latest changes and restarting Heimgeist...', - }) - - await runGitCommand(['pull', '--ff-only', UPDATE_REMOTE_URL, UPDATE_BRANCH], { timeout: 120000 }) - - const { stdout: updatedStdout } = await runGitCommand(['rev-parse', 'HEAD']) - const updatedLocalCommit = updatedStdout.trim() - - if (!updatedLocalCommit || updatedLocalCommit === localCommit) { - return setUpdateStatus({ - state: 'up-to-date', - trigger, - branch: branch || UPDATE_BRANCH, - localCommit, - remoteCommit, - message: 'No newer remote update was applied. Local checkout already contains the latest pulled state.', - }) - } - - const result = setUpdateStatus({ - state: 'updated', - trigger, - branch: branch || UPDATE_BRANCH, - localCommit: updatedLocalCommit || localCommit, - remoteCommit, - message: 'Update installed. Heimgeist restarts now.', - restartScheduled: true, - }) - - scheduleAppRestart() - return result - } catch (error) { - console.error('Failed to check for updates:', error) - - return setUpdateStatus({ - state: 'error', - trigger, - message: `Update check failed: ${error.message || String(error)}`, - }) - } -} - -function checkForUpdates(trigger = 'manual') { - if (!activeUpdateCheck) { - activeUpdateCheck = performUpdateCheck(trigger).finally(() => { - activeUpdateCheck = null - }) - } - - return activeUpdateCheck -} - -async function createMainWindow() { - console.log('Electron: creating main window') - mainWindow = new BrowserWindow({ - width: 1000, - height: 720, - minWidth: 680, - minHeight: 300, - show: false, - webPreferences: { - preload: path.join(__dirname, 'preload.cjs'), - contextIsolation: true, - nodeIntegration: false, - }, - }) - - applyUiScaleToWindow(mainWindow) - - mainWindow.on('ready-to-show', () => { - mainWindow.show() - }) - - mainWindow.webContents.on('did-finish-load', () => { - applyUiScaleToWindow(mainWindow) - }) - - mainWindow.webContents.on('did-fail-load', (_event, errorCode, errorDescription, validatedURL) => { - console.error('Main window failed to load:', { errorCode, errorDescription, validatedURL }) - }) - - mainWindow.on('focus', () => { - mainWindow.webContents.send('window-focused') - }) - - if (is.dev && process.env.VITE_DEV_SERVER_URL) { - console.log(`Electron: loading renderer ${process.env.VITE_DEV_SERVER_URL}`) - await mainWindow.loadURL(process.env.VITE_DEV_SERVER_URL) - applyDevToolsPreferenceToWindow(mainWindow) - } else { - console.log('Electron: loading bundled renderer') - await mainWindow.loadFile(path.join(__dirname, '../dist/index.html')) - } - - mainWindow.webContents.setWindowOpenHandler(({ url }) => { - shell.openExternal(url) - return { action: 'deny' } - }) -} - -async function createSettingsWindow() { - if (settingsWindow) { - settingsWindow.focus() - return - } - - settingsWindow = new BrowserWindow({ - width: 800, - height: 600, - title: 'Settings', - parent: mainWindow, - modal: true, - show: false, - webPreferences: { - preload: path.join(__dirname, 'preload.cjs'), - contextIsolation: true, - nodeIntegration: false, - }, - }) - - applyUiScaleToWindow(settingsWindow) - - settingsWindow.on('ready-to-show', () => { - settingsWindow.show() - }) - - settingsWindow.webContents.on('did-finish-load', () => { - applyUiScaleToWindow(settingsWindow) - }) - - settingsWindow.webContents.on('did-fail-load', (_event, errorCode, errorDescription, validatedURL) => { - console.error('Settings window failed to load:', { errorCode, errorDescription, validatedURL }) - }) - - settingsWindow.on('closed', () => { - settingsWindow = null - }) - - if (is.dev && process.env.VITE_DEV_SERVER_URL) { - await settingsWindow.loadURL(`${process.env.VITE_DEV_SERVER_URL}#/settings`) - applyDevToolsPreferenceToWindow(settingsWindow) - } else { - await settingsWindow.loadFile(path.join(__dirname, '../dist/index.html'), { hash: '/settings' }) - } -} - -app.whenReady().then(async () => { - console.log('Electron: app ready') - loadSettings() - const startupUpdateResult = await checkForUpdates('startup') - if (startupUpdateResult?.restartScheduled) { - return - } - - await createMainWindow() - - const menuTemplate = [ - { - label: 'File', - submenu: [ - { - label: 'Settings', - accelerator: 'CmdOrCtrl+,', - click: createSettingsWindow, - }, - { type: 'separator' }, - { role: 'quit' }, - ], - }, - { - label: 'Edit', - submenu: [ - { role: 'undo' }, - { role: 'redo' }, - { type: 'separator' }, - { role: 'cut' }, - { role: 'copy' }, - { role: 'paste' }, - { role: 'delete' }, - { type: 'separator' }, - { role: 'selectAll' }, - ], - }, - { - label: 'View', - submenu: [ - { role: 'reload' }, - { role: 'forcereload' }, - { role: 'toggledevtools' }, - { type: 'separator' }, - { role: 'resetzoom' }, - { role: 'zoomin' }, - { role: 'zoomout' }, - { type: 'separator' }, - { role: 'togglefullscreen' }, - ], - }, - ] - - const menu = Menu.buildFromTemplate(menuTemplate) - Menu.setApplicationMenu(menu) - - app.on('activate', () => { - if (BrowserWindow.getAllWindows().length === 0) createMainWindow() - }) -}) - -ipcMain.handle('get-settings', () => appSettings) -ipcMain.handle('get-update-status', () => lastUpdateCheckResult) -ipcMain.handle('check-for-updates', () => checkForUpdates('manual')) -ipcMain.handle('get-changelog-page', (event, page) => getChangelogPage(page)) - -ipcMain.handle('set-setting', (event, key, value) => { - if (key === 'uiScale') { - appSettings[key] = normalizeUiScale(value) - } else if (key === 'embedModel') { - appSettings[key] = normalizeEmbedModel(value) - } else if (key === 'rerankModel') { - appSettings[key] = normalizeRerankModel(value) - } else if (key === 'chatModel' || key === 'visionModel') { - appSettings[key] = normalizeModelName(value) - } else if (key === 'transcriptionModel') { - appSettings[key] = normalizeTranscriptionModel(value) - } else if (key === 'openDevToolsOnStartup') { - appSettings[key] = normalizeOpenDevToolsOnStartup(value) - } else if (key === 'audioInputEnabled') { - appSettings[key] = normalizeBooleanSetting(value) - } else if (key === 'audioInputDeviceId') { - appSettings[key] = String(value || '').trim() - } else if (key === 'audioInputLanguage') { - appSettings[key] = String(value || '').trim().toLowerCase() - } else { - appSettings[key] = value - } - saveSettings() - if (key === 'uiScale') { - applyUiScaleToAllWindows() - } else if (key === 'openDevToolsOnStartup') { - applyDevToolsPreferenceToAllWindows() - } - return true -}) - -ipcMain.handle('update-settings', (event, settings) => { - appSettings = { ...appSettings, ...settings } - appSettings.uiScale = normalizeUiScale(appSettings.uiScale) - appSettings.chatModel = normalizeModelName(appSettings.chatModel) - appSettings.visionModel = normalizeModelName(appSettings.visionModel) - appSettings.embedModel = normalizeEmbedModel(appSettings.embedModel) - appSettings.rerankModel = normalizeRerankModel(appSettings.rerankModel) - appSettings.transcriptionModel = normalizeTranscriptionModel(appSettings.transcriptionModel) - appSettings.openDevToolsOnStartup = normalizeOpenDevToolsOnStartup(appSettings.openDevToolsOnStartup) - appSettings.audioInputEnabled = normalizeBooleanSetting(appSettings.audioInputEnabled) - appSettings.audioInputDeviceId = String(appSettings.audioInputDeviceId || '').trim() - appSettings.audioInputLanguage = String(appSettings.audioInputLanguage || '').trim().toLowerCase() - saveSettings() - if (Object.prototype.hasOwnProperty.call(settings, 'uiScale')) { - applyUiScaleToAllWindows() - } - if (Object.prototype.hasOwnProperty.call(settings, 'openDevToolsOnStartup')) { - applyDevToolsPreferenceToAllWindows() - } - return true -}) - -ipcMain.handle('pick-paths', async (event, options = {}) => { - const dialogOptions = { - properties: ['openFile', 'multiSelections'], - } - if (Array.isArray(options?.filters) && options.filters.length > 0) { - dialogOptions.filters = options.filters - } - if (typeof options?.title === 'string' && options.title.trim()) { - dialogOptions.title = options.title.trim() - } - - const result = await dialog.showOpenDialog(mainWindow, { - ...dialogOptions, - }) - return result.canceled ? [] : result.filePaths -}) - -ipcMain.handle('open-path', async (event, filePath) => { - if (!filePath) return false - const err = await shell.openPath(filePath) - return err === '' -}) - -ipcMain.on('open-external-link', (event, url) => { - shell.openExternal(url) -}) - -app.on('window-all-closed', () => { - if (process.platform !== 'darwin') app.quit() -}) diff --git a/electron/preload.cjs b/electron/preload.cjs deleted file mode 100644 index fe1981f..0000000 --- a/electron/preload.cjs +++ /dev/null @@ -1,21 +0,0 @@ - -const { contextBridge, ipcRenderer } = require('electron') - -// Expose a secure API to the renderer process -contextBridge.exposeInMainWorld('electronAPI', { - getSettings: () => ipcRenderer.invoke('get-settings'), - getUpdateStatus: () => ipcRenderer.invoke('get-update-status'), - checkForUpdates: () => ipcRenderer.invoke('check-for-updates'), - getChangelogPage: (page) => ipcRenderer.invoke('get-changelog-page', page), - setSetting: (key, value) => ipcRenderer.invoke('set-setting', key, value), - updateSettings: (settings) => ipcRenderer.invoke('update-settings', settings), - pickPaths: (options) => ipcRenderer.invoke('pick-paths', options), - openPath: (filePath) => ipcRenderer.invoke('open-path', filePath), - openExternalLink: (event) => { - event.preventDefault(); - const url = event.currentTarget.href; - ipcRenderer.send('open-external-link', url); - }, - onWindowFocus: (callback) => ipcRenderer.on('window-focused', callback), - offWindowFocus: (callback) => ipcRenderer.removeListener('window-focused', callback), -}) diff --git a/package.json b/package.json index 37171f0..ec51762 100644 --- a/package.json +++ b/package.json @@ -2,23 +2,18 @@ "name": "Heimgeist", "version": "0.1.0", "private": true, - "main": "electron/main.cjs", "type": "module", "scripts": { - "dev": "concurrently -k \"npm:dev:backend\" \"npm:dev:renderer\" \"npm:dev:electron\"", - "dev:backend": "node scripts/run-backend.cjs", - "dev:renderer": "vite --port 5173 --strictPort", - "dev:renderer:tauri": "vite --host 127.0.0.1 --port 5174 --strictPort", - "dev:electron": "node scripts/run-electron-dev.cjs", + "dev": "node scripts/run-tauri-dev.cjs", "dev:tauri": "node scripts/run-tauri-dev.cjs", - "dev:tauri:full": "node scripts/run-tauri-dev.cjs", + "dev:backend": "node scripts/run-backend.cjs", + "dev:renderer": "vite --host 127.0.0.1 --port 5174 --strictPort", "dev:tauri:shell": "tauri dev", "tauri": "tauri", "build": "vite build", - "start": "electron ." + "start": "npm run dev" }, "dependencies": { - "@electron-toolkit/utils": "^4.0.0", "ffmpeg-static": "^5.3.0", "ffprobe-static": "^3.1.0", "react": "^18.3.1", @@ -29,9 +24,6 @@ "devDependencies": { "@tauri-apps/cli": "^2.11.0", "@vitejs/plugin-react": "^4.3.1", - "concurrently": "^9.0.1", - "cross-env": "^7.0.3", - "electron": "^31.2.0", "vite": "^5.4.2", "wait-on": "^7.2.0" } diff --git a/run.sh b/run.sh index 07aefe5..3792766 100755 --- a/run.sh +++ b/run.sh @@ -64,12 +64,15 @@ PY node_deps_usable() { node - <<'NODE' >/dev/null 2>&1 -require('concurrently') -require('electron') require('react') require('vite') require('wait-on') NODE + if [ "$?" -ne 0 ]; then + return 1 + fi + + [ -x node_modules/.bin/tauri ] || [ -x node_modules/.bin/tauri.cmd ] } is_linux_x86_64() { @@ -169,21 +172,6 @@ fi TORCH_FLAVOR="$(resolve_torch_flavor)" RECREATE_VENV=0 -if [ -z "${HEIMGEIST_SETTINGS_FILE:-}" ]; then - case "$(uname -s)" in - Darwin) - HEIMGEIST_SETTINGS_FILE="${HOME}/Library/Application Support/Heimgeist/settings.json" - ;; - Linux) - HEIMGEIST_SETTINGS_FILE="${XDG_CONFIG_HOME:-$HOME/.config}/Heimgeist/settings.json" - ;; - esac - if [ -n "${HEIMGEIST_SETTINGS_FILE:-}" ]; then - export HEIMGEIST_SETTINGS_FILE - mkdir -p "$(dirname "$HEIMGEIST_SETTINGS_FILE")" - fi -fi - if [ ! -x "$VENV_DIR/bin/python" ] || ! "$VENV_DIR/bin/python" -c 'import sys; raise SystemExit(0 if sys.version_info[:2] == (3, 13) else 1)'; then RECREATE_VENV=1 fi diff --git a/scripts/run-electron-dev.cjs b/scripts/run-electron-dev.cjs deleted file mode 100644 index c049a0e..0000000 --- a/scripts/run-electron-dev.cjs +++ /dev/null @@ -1,70 +0,0 @@ -const { spawn } = require('child_process') -const path = require('path') -const waitOn = require('wait-on') - -const projectRoot = path.resolve(__dirname, '..') -const electronBinary = require('electron') -const relaunchExitCode = 75 -const devServerUrl = process.env.VITE_DEV_SERVER_URL || 'http://127.0.0.1:5173' -const startupResources = ['http-get://127.0.0.1:5173', 'http-get://127.0.0.1:8000/health'] -const env = { - ...process.env, - VITE_DEV_SERVER_URL: devServerUrl, - HEIMGEIST_DEV_WRAPPER: '1', - HEIMGEIST_DEV_RELAUNCH_CODE: String(relaunchExitCode), -} - -async function waitForDependencies() { - await waitOn({ - resources: startupResources, - proxy: false, - timeout: 120000, - }) -} - -function sleep(ms) { - return new Promise((resolve) => { - setTimeout(resolve, ms) - }) -} - -function runElectronOnce() { - return new Promise((resolve) => { - const child = spawn(electronBinary, ['.'], { - cwd: projectRoot, - env, - stdio: 'inherit', - }) - - child.on('exit', (code, signal) => { - resolve({ code, signal }) - }) - }) -} - -async function main() { - await waitForDependencies() - console.log(`Electron dev wrapper: dependencies ready, launching ${env.VITE_DEV_SERVER_URL}`) - - while (true) { - const { code, signal } = await runElectronOnce() - - if (signal) { - process.kill(process.pid, signal) - return - } - - if (code === relaunchExitCode) { - await sleep(750) - await waitForDependencies() - continue - } - - process.exit(code ?? 0) - } -} - -main().catch((error) => { - console.error('Failed to launch Electron dev wrapper:', error) - process.exit(1) -}) diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 4a800bd..5849228 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -4,7 +4,7 @@ "version": "0.1.0", "identifier": "com.giers.heimgeist", "build": { - "beforeDevCommand": "npm run dev:renderer:tauri", + "beforeDevCommand": "npm run dev:renderer", "beforeBuildCommand": "npm run build", "devUrl": "http://127.0.0.1:5174", "frontendDist": "../dist"