Files
Heimgeist/electron/main.cjs

246 lines
5.9 KiB
JavaScript
Raw Normal View History

const { app, BrowserWindow, Menu, dialog, ipcMain, shell } = require('electron')
2025-08-22 23:42:34 +02:00
const path = require('path')
const { is } = require('@electron-toolkit/utils')
const fs = require('fs')
let mainWindow
let settingsWindow = null
const settingsFilePath = path.join(app.getPath('userData'), 'settings.json')
let appSettings = {}
const DEFAULT_UI_SCALE = 1
const MIN_UI_SCALE = 0.7
const MAX_UI_SCALE = 1.3
2025-08-22 23:42:34 +02:00
const defaultSettings = {
ollamaApiUrl: 'http://127.0.0.1:8000',
colorScheme: 'Default',
uiScale: DEFAULT_UI_SCALE,
chatModel: 'llama3',
}
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 applyUiScaleToWindow(window) {
if (!window || window.isDestroyed()) {
return
}
window.webContents.setZoomFactor(normalizeUiScale(appSettings.uiScale))
}
function applyUiScaleToAllWindows() {
BrowserWindow.getAllWindows().forEach(applyUiScaleToWindow)
2025-08-22 23:42:34 +02:00
}
function loadSettings() {
try {
if (fs.existsSync(settingsFilePath)) {
const data = fs.readFileSync(settingsFilePath, 'utf8')
appSettings = { ...defaultSettings, ...JSON.parse(data) }
} else {
appSettings = { ...defaultSettings }
saveSettings()
2025-08-22 23:42:34 +02:00
}
appSettings.uiScale = normalizeUiScale(appSettings.uiScale)
2025-08-22 23:42:34 +02:00
} catch (error) {
console.error('Failed to load settings:', error)
appSettings = { ...defaultSettings }
}
}
function saveSettings() {
try {
fs.writeFileSync(settingsFilePath, JSON.stringify(appSettings, null, 2), 'utf8')
} catch (error) {
console.error('Failed to save settings:', error)
}
}
async function createMainWindow() {
2025-08-22 23:42:34 +02:00
mainWindow = new BrowserWindow({
width: 1000,
height: 720,
feat: Add streaming chat + scroll persistence; improve markdown & links Backend - /chat: support streaming via StreamingResponse; save full reply after stream ends. Non-stream path unchanged. - ChatRequest: add stream flag (default false). - GenerateTitleRequest: add model and use it instead of hardcoded llama3. - ollama_client.chat_stream(): new async generator parsing Ollama streaming JSON (both formats). - Remove response_model from /chat to allow streaming; non-stream still returns { reply }. Electron - Open external links in system browser (setWindowOpenHandler, shell.openExternal). - New IPC: update-settings, open-external-link. - Set minimum window size; preload exposes updateSettings and openExternalLink. Frontend (React) - Streaming UI with live chunking; sticky-bottom only when user at bottom. - Per-session scroll persistence and robust restore. - New message tip to jump to latest reply when scrolled up. - Disable Send while sending; spinner. - General Settings: stream output toggle; propagate model/stream changes. - Apply color scheme at boot; extract colorSchemes helper. - Sidebar UX tweaks and unread badges. Markdown/rendering - Code blocks: language title bar and wrapper. - Tables: GitHub-style parsing, per-cell borders, rounded wrapper, spacing, alignment. - Headings: remove blank line after h1-h4. - <hr>: handle after tables; strip following whitespace. - Links: target=_blank with icon and URL tooltip. Styles - Add styles for code/table wrappers, new-message tip, toggle, spinner; hover/active vars; narrower sidebar. API notes / breaking changes - /chat accepts stream=true and returns text/plain streamed chunks. - generate-title now requires a model. - Non-stream /chat response shape unchanged.
2025-08-23 16:45:46 +02:00
minWidth: 680,
minHeight: 300,
2025-08-22 23:42:34 +02:00
show: false,
webPreferences: {
preload: path.join(__dirname, 'preload.cjs'),
contextIsolation: true,
nodeIntegration: false,
},
2025-08-22 23:42:34 +02:00
})
applyUiScaleToWindow(mainWindow)
2025-08-22 23:42:34 +02:00
mainWindow.on('ready-to-show', () => {
mainWindow.show()
})
mainWindow.webContents.on('did-finish-load', () => {
applyUiScaleToWindow(mainWindow)
})
mainWindow.on('focus', () => {
mainWindow.webContents.send('window-focused')
})
2025-08-22 23:42:34 +02:00
if (is.dev && process.env.VITE_DEV_SERVER_URL) {
await mainWindow.loadURL(process.env.VITE_DEV_SERVER_URL)
mainWindow.webContents.openDevTools({ mode: 'detach' })
} else {
await mainWindow.loadFile(path.join(__dirname, '../dist/index.html'))
}
feat: Add streaming chat + scroll persistence; improve markdown & links Backend - /chat: support streaming via StreamingResponse; save full reply after stream ends. Non-stream path unchanged. - ChatRequest: add stream flag (default false). - GenerateTitleRequest: add model and use it instead of hardcoded llama3. - ollama_client.chat_stream(): new async generator parsing Ollama streaming JSON (both formats). - Remove response_model from /chat to allow streaming; non-stream still returns { reply }. Electron - Open external links in system browser (setWindowOpenHandler, shell.openExternal). - New IPC: update-settings, open-external-link. - Set minimum window size; preload exposes updateSettings and openExternalLink. Frontend (React) - Streaming UI with live chunking; sticky-bottom only when user at bottom. - Per-session scroll persistence and robust restore. - New message tip to jump to latest reply when scrolled up. - Disable Send while sending; spinner. - General Settings: stream output toggle; propagate model/stream changes. - Apply color scheme at boot; extract colorSchemes helper. - Sidebar UX tweaks and unread badges. Markdown/rendering - Code blocks: language title bar and wrapper. - Tables: GitHub-style parsing, per-cell borders, rounded wrapper, spacing, alignment. - Headings: remove blank line after h1-h4. - <hr>: handle after tables; strip following whitespace. - Links: target=_blank with icon and URL tooltip. Styles - Add styles for code/table wrappers, new-message tip, toggle, spinner; hover/active vars; narrower sidebar. API notes / breaking changes - /chat accepts stream=true and returns text/plain streamed chunks. - generate-title now requires a model. - Non-stream /chat response shape unchanged.
2025-08-23 16:45:46 +02:00
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
shell.openExternal(url)
return { action: 'deny' }
})
2025-08-22 23:42:34 +02:00
}
async function createSettingsWindow() {
2025-08-22 23:42:34 +02:00
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,
},
2025-08-22 23:42:34 +02:00
})
applyUiScaleToWindow(settingsWindow)
2025-08-22 23:42:34 +02:00
settingsWindow.on('ready-to-show', () => {
settingsWindow.show()
})
settingsWindow.webContents.on('did-finish-load', () => {
applyUiScaleToWindow(settingsWindow)
})
2025-08-22 23:42:34 +02:00
settingsWindow.on('closed', () => {
settingsWindow = null
})
if (is.dev && process.env.VITE_DEV_SERVER_URL) {
await settingsWindow.loadURL(`${process.env.VITE_DEV_SERVER_URL}#/settings`)
settingsWindow.webContents.openDevTools({ mode: 'detach' })
} else {
await settingsWindow.loadFile(path.join(__dirname, '../dist/index.html'), { hash: '/settings' })
}
}
app.whenReady().then(() => {
loadSettings()
2025-08-22 23:42:34 +02:00
createMainWindow()
const menuTemplate = [
{
label: 'File',
submenu: [
{
label: 'Settings',
accelerator: 'CmdOrCtrl+,',
click: createSettingsWindow,
2025-08-22 23:42:34 +02:00
},
{ type: 'separator' },
{ role: 'quit' },
],
2025-08-22 23:42:34 +02:00
},
{
label: 'Edit',
submenu: [
{ role: 'undo' },
{ role: 'redo' },
{ type: 'separator' },
{ role: 'cut' },
{ role: 'copy' },
{ role: 'paste' },
{ role: 'delete' },
{ type: 'separator' },
{ role: 'selectAll' },
],
2025-08-22 23:42:34 +02:00
},
{
label: 'View',
submenu: [
{ role: 'reload' },
{ role: 'forcereload' },
{ role: 'toggledevtools' },
{ type: 'separator' },
{ role: 'resetzoom' },
{ role: 'zoomin' },
{ role: 'zoomout' },
{ type: 'separator' },
{ role: 'togglefullscreen' },
],
},
2025-08-22 23:42:34 +02:00
]
const menu = Menu.buildFromTemplate(menuTemplate)
Menu.setApplicationMenu(menu)
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createMainWindow()
})
})
ipcMain.handle('get-settings', () => appSettings)
2025-08-22 23:42:34 +02:00
ipcMain.handle('set-setting', (event, key, value) => {
appSettings[key] = key === 'uiScale' ? normalizeUiScale(value) : value
2025-08-22 23:42:34 +02:00
saveSettings()
if (key === 'uiScale') {
applyUiScaleToAllWindows()
}
2025-08-22 23:42:34 +02:00
return true
})
feat: Add streaming chat + scroll persistence; improve markdown & links Backend - /chat: support streaming via StreamingResponse; save full reply after stream ends. Non-stream path unchanged. - ChatRequest: add stream flag (default false). - GenerateTitleRequest: add model and use it instead of hardcoded llama3. - ollama_client.chat_stream(): new async generator parsing Ollama streaming JSON (both formats). - Remove response_model from /chat to allow streaming; non-stream still returns { reply }. Electron - Open external links in system browser (setWindowOpenHandler, shell.openExternal). - New IPC: update-settings, open-external-link. - Set minimum window size; preload exposes updateSettings and openExternalLink. Frontend (React) - Streaming UI with live chunking; sticky-bottom only when user at bottom. - Per-session scroll persistence and robust restore. - New message tip to jump to latest reply when scrolled up. - Disable Send while sending; spinner. - General Settings: stream output toggle; propagate model/stream changes. - Apply color scheme at boot; extract colorSchemes helper. - Sidebar UX tweaks and unread badges. Markdown/rendering - Code blocks: language title bar and wrapper. - Tables: GitHub-style parsing, per-cell borders, rounded wrapper, spacing, alignment. - Headings: remove blank line after h1-h4. - <hr>: handle after tables; strip following whitespace. - Links: target=_blank with icon and URL tooltip. Styles - Add styles for code/table wrappers, new-message tip, toggle, spinner; hover/active vars; narrower sidebar. API notes / breaking changes - /chat accepts stream=true and returns text/plain streamed chunks. - generate-title now requires a model. - Non-stream /chat response shape unchanged.
2025-08-23 16:45:46 +02:00
ipcMain.handle('update-settings', (event, settings) => {
appSettings = { ...appSettings, ...settings }
appSettings.uiScale = normalizeUiScale(appSettings.uiScale)
feat: Add streaming chat + scroll persistence; improve markdown & links Backend - /chat: support streaming via StreamingResponse; save full reply after stream ends. Non-stream path unchanged. - ChatRequest: add stream flag (default false). - GenerateTitleRequest: add model and use it instead of hardcoded llama3. - ollama_client.chat_stream(): new async generator parsing Ollama streaming JSON (both formats). - Remove response_model from /chat to allow streaming; non-stream still returns { reply }. Electron - Open external links in system browser (setWindowOpenHandler, shell.openExternal). - New IPC: update-settings, open-external-link. - Set minimum window size; preload exposes updateSettings and openExternalLink. Frontend (React) - Streaming UI with live chunking; sticky-bottom only when user at bottom. - Per-session scroll persistence and robust restore. - New message tip to jump to latest reply when scrolled up. - Disable Send while sending; spinner. - General Settings: stream output toggle; propagate model/stream changes. - Apply color scheme at boot; extract colorSchemes helper. - Sidebar UX tweaks and unread badges. Markdown/rendering - Code blocks: language title bar and wrapper. - Tables: GitHub-style parsing, per-cell borders, rounded wrapper, spacing, alignment. - Headings: remove blank line after h1-h4. - <hr>: handle after tables; strip following whitespace. - Links: target=_blank with icon and URL tooltip. Styles - Add styles for code/table wrappers, new-message tip, toggle, spinner; hover/active vars; narrower sidebar. API notes / breaking changes - /chat accepts stream=true and returns text/plain streamed chunks. - generate-title now requires a model. - Non-stream /chat response shape unchanged.
2025-08-23 16:45:46 +02:00
saveSettings()
if (Object.prototype.hasOwnProperty.call(settings, 'uiScale')) {
applyUiScaleToAllWindows()
}
feat: Add streaming chat + scroll persistence; improve markdown & links Backend - /chat: support streaming via StreamingResponse; save full reply after stream ends. Non-stream path unchanged. - ChatRequest: add stream flag (default false). - GenerateTitleRequest: add model and use it instead of hardcoded llama3. - ollama_client.chat_stream(): new async generator parsing Ollama streaming JSON (both formats). - Remove response_model from /chat to allow streaming; non-stream still returns { reply }. Electron - Open external links in system browser (setWindowOpenHandler, shell.openExternal). - New IPC: update-settings, open-external-link. - Set minimum window size; preload exposes updateSettings and openExternalLink. Frontend (React) - Streaming UI with live chunking; sticky-bottom only when user at bottom. - Per-session scroll persistence and robust restore. - New message tip to jump to latest reply when scrolled up. - Disable Send while sending; spinner. - General Settings: stream output toggle; propagate model/stream changes. - Apply color scheme at boot; extract colorSchemes helper. - Sidebar UX tweaks and unread badges. Markdown/rendering - Code blocks: language title bar and wrapper. - Tables: GitHub-style parsing, per-cell borders, rounded wrapper, spacing, alignment. - Headings: remove blank line after h1-h4. - <hr>: handle after tables; strip following whitespace. - Links: target=_blank with icon and URL tooltip. Styles - Add styles for code/table wrappers, new-message tip, toggle, spinner; hover/active vars; narrower sidebar. API notes / breaking changes - /chat accepts stream=true and returns text/plain streamed chunks. - generate-title now requires a model. - Non-stream /chat response shape unchanged.
2025-08-23 16:45:46 +02:00
return true
})
ipcMain.handle('pick-paths', async () => {
const result = await dialog.showOpenDialog(mainWindow, {
properties: ['openFile', 'openDirectory', 'multiSelections'],
})
return result.canceled ? [] : result.filePaths
})
ipcMain.handle('open-path', async (event, filePath) => {
if (!filePath) return false
const err = await shell.openPath(filePath)
return err === ''
})
feat: Add streaming chat + scroll persistence; improve markdown & links Backend - /chat: support streaming via StreamingResponse; save full reply after stream ends. Non-stream path unchanged. - ChatRequest: add stream flag (default false). - GenerateTitleRequest: add model and use it instead of hardcoded llama3. - ollama_client.chat_stream(): new async generator parsing Ollama streaming JSON (both formats). - Remove response_model from /chat to allow streaming; non-stream still returns { reply }. Electron - Open external links in system browser (setWindowOpenHandler, shell.openExternal). - New IPC: update-settings, open-external-link. - Set minimum window size; preload exposes updateSettings and openExternalLink. Frontend (React) - Streaming UI with live chunking; sticky-bottom only when user at bottom. - Per-session scroll persistence and robust restore. - New message tip to jump to latest reply when scrolled up. - Disable Send while sending; spinner. - General Settings: stream output toggle; propagate model/stream changes. - Apply color scheme at boot; extract colorSchemes helper. - Sidebar UX tweaks and unread badges. Markdown/rendering - Code blocks: language title bar and wrapper. - Tables: GitHub-style parsing, per-cell borders, rounded wrapper, spacing, alignment. - Headings: remove blank line after h1-h4. - <hr>: handle after tables; strip following whitespace. - Links: target=_blank with icon and URL tooltip. Styles - Add styles for code/table wrappers, new-message tip, toggle, spinner; hover/active vars; narrower sidebar. API notes / breaking changes - /chat accepts stream=true and returns text/plain streamed chunks. - generate-title now requires a model. - Non-stream /chat response shape unchanged.
2025-08-23 16:45:46 +02:00
ipcMain.on('open-external-link', (event, url) => {
shell.openExternal(url)
})
2025-08-22 23:42:34 +02:00
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit()
})