jan/electron/main.ts
NamH ec9b5bf682
fix: update new api from cortex to support 0.5.0 (#3221)
* fix: update new api from cortex to support 0.5.0

Signed-off-by: James <namnh0122@gmail.com>

* fix stop button for streaming

Signed-off-by: James <namnh0122@gmail.com>

* fix stop inference for nonstreaming

Signed-off-by: James <namnh0122@gmail.com>

* chore: remove umami prevent tracking call to vercel

Signed-off-by: James <namnh0122@gmail.com>

* add warning modal when running more than 2 model concurrently

Signed-off-by: James <namnh0122@gmail.com>

* fix: skip summarize if abort

Signed-off-by: James <namnh0122@gmail.com>

* 0.5.0-3

* add inference error popup

Signed-off-by: James <namnh0122@gmail.com>

* add back import local model

Signed-off-by: James <namnh0122@gmail.com>

* fix: max token issue (#3225)

Signed-off-by: James <namnh0122@gmail.com>

* format status

Signed-off-by: James <namnh0122@gmail.com>

* fix migration missing instructions

Signed-off-by: James <namnh0122@gmail.com>

* fix: wait for cortex process overlay should be on top (#3224)

* fix: wait for cortex process overlay should be on top

* chore: update cortex.js

* Cortex 0.5.0-5

* add import model to my model screen

Signed-off-by: James <namnh0122@gmail.com>

* fix: should migrate symlink models (#3226)

* fix import on windows (#3229)

Signed-off-by: James <namnh0122@gmail.com>

* fix yarn lint

Signed-off-by: James <namnh0122@gmail.com>

* fix: clean up port before start jan (#3232)

Signed-off-by: James <namnh0122@gmail.com>

---------

Signed-off-by: James <namnh0122@gmail.com>
Co-authored-by: Van Pham <64197333+Van-QA@users.noreply.github.com>
Co-authored-by: Louis <louis@jan.ai>
2024-08-02 09:37:04 +07:00

262 lines
6.3 KiB
TypeScript

import { app, BrowserWindow } from 'electron'
import { join, resolve } from 'path'
import { exec, execSync, ChildProcess } from 'child_process'
import { cortexPath } from './cortex-runner'
/**
* Managers
**/
import { windowManager } from './managers/window'
/**
* IPC Handlers
**/
import { handleAppUpdates } from './handlers/update'
import { handleAppIPCs } from './handlers/native'
/**
* Utils
**/
import { setupMenu } from './utils/menu'
import { createUserSpace } from './utils/path'
import { migrate } from './utils/migration'
import { cleanUpAndQuit } from './utils/clean'
import { setupCore } from './utils/setup'
import { setupReactDevTool } from './utils/dev'
import log from 'electron-log'
const preloadPath = join(__dirname, 'preload.js')
const rendererPath = join(__dirname, '..', 'renderer')
const mainPath = join(rendererPath, 'index.html')
const mainUrl = 'http://localhost:3000'
const gotTheLock = app.requestSingleInstanceLock()
if (process.defaultApp) {
if (process.argv.length >= 2) {
app.setAsDefaultProtocolClient('jan', process.execPath, [
resolve(process.argv[1]),
])
}
} else {
app.setAsDefaultProtocolClient('jan')
}
const createMainWindow = () => {
const startUrl = app.isPackaged ? `file://${mainPath}` : mainUrl
windowManager.createMainWindow(preloadPath, startUrl)
}
log.initialize()
log.info('Log from the main process')
// replace all console.log to log
Object.assign(console, log.functions)
let cortexService: ChildProcess | undefined = undefined
app
.whenReady()
.then(() => killProcessesOnPort(3929))
.then(() => killProcessesOnPort(1337))
.then(() => {
const command = `${cortexPath} -a 127.0.0.1 -p 1337`
log.info('Starting cortex with command:', command)
// init cortex
cortexService = exec(`${command}`, (error, stdout, stderr) => {
if (error) {
log.error(`error: ${error.message}`)
return
}
if (stderr) {
log.error(`stderr: ${stderr}`)
return
}
log.info(`stdout: ${stdout}`)
})
})
.then(() => {
if (!gotTheLock) {
app.quit()
throw new Error('Another instance of the app is already running')
} else {
app.on(
'second-instance',
(_event, commandLine, _workingDirectory): void => {
if (process.platform === 'win32' || process.platform === 'linux') {
// this is for handling deeplink on windows and linux
// since those OS will emit second-instance instead of open-url
const url = commandLine.pop()
if (url) {
windowManager.sendMainAppDeepLink(url)
}
}
windowManager.showMainWindow()
}
)
}
})
.then(setupCore)
.then(createUserSpace)
.then(migrate)
.then(setupMenu)
.then(handleIPCs)
.then(handleAppUpdates)
.then(createMainWindow)
.then(() => {
if (!app.isPackaged) {
setupReactDevTool()
windowManager.mainWindow?.webContents.openDevTools()
}
})
.then(() => {
app.on('activate', () => {
if (!BrowserWindow.getAllWindows().length) {
createMainWindow()
} else {
windowManager.showMainWindow()
}
})
})
app.on('open-url', (_event, url) => {
windowManager.sendMainAppDeepLink(url)
})
app.once('quit', async () => {
cleanUpAndQuit()
})
app.once('window-all-closed', async () => {
await stopApiServer()
await stopCortexService()
cleanUpAndQuit()
})
async function stopCortexService() {
try {
const pid = cortexService?.pid
if (!pid) {
console.log('No cortex service to stop.')
return
}
process.kill(pid)
console.log(`Service with PID ${pid} has been terminated.`)
} catch (error) {
console.error('Error killing service:', error)
}
}
async function stopApiServer() {
// this function is not meant to be success. It will throw an error.
try {
await fetch('http://localhost:1337/v1/system', {
method: 'DELETE',
})
} catch (error) {
// do nothing
}
}
/**
* Handles various IPC messages from the renderer process.
*/
function handleIPCs() {
// Inject core handlers for IPCs
// Handle native IPCs
handleAppIPCs()
}
function killProcessesOnPort(port: number): void {
try {
console.log(`Killing processes on port ${port}...`)
if (process.platform === 'win32') {
killProcessesOnWindowsPort(port)
} else {
killProcessesOnUnixPort(port)
}
} catch (error) {
console.error(
`Failed to kill process(es) on port ${port}: ${(error as Error).message}`
)
}
}
function killProcessesOnWindowsPort(port: number): void {
let result: string
try {
result = execSync(`netstat -ano | findstr :${port}`).toString()
} catch (error) {
console.log(`No processes found on port ${port}.`)
return
}
const lines = result.split('\n').filter(Boolean)
if (lines.length === 0) {
console.log(`No processes found on port ${port}.`)
return
}
const pids = lines
.map((line) => {
const parts = line.trim().split(/\s+/)
return parts[parts.length - 1]
})
.filter((pid): pid is string => Boolean(pid) && !isNaN(Number(pid)))
if (pids.length === 0) {
console.log(`No valid PIDs found for port ${port}.`)
return
}
const uniquePids = Array.from(new Set(pids))
console.log('uniquePids', uniquePids)
uniquePids.forEach((pid) => {
try {
execSync(`taskkill /PID ${pid} /F`)
console.log(
`Process with PID ${pid} on port ${port} has been terminated.`
)
} catch (error) {
console.error(
`Failed to kill process with PID ${pid}: ${(error as Error).message}`
)
}
})
}
function killProcessesOnUnixPort(port: number): void {
let pids: string[]
try {
pids = execSync(`lsof -ti tcp:${port}`)
.toString()
.trim()
.split('\n')
.filter(Boolean)
} catch (error) {
if ((error as { status?: number }).status === 1) {
console.log(`No processes found on port ${port}.`)
return
}
throw error // Re-throw if it's not the "no processes found" error
}
pids.forEach((pid) => {
process.kill(parseInt(pid), 'SIGTERM')
console.log(`Process with PID ${pid} on port ${port} has been terminated.`)
})
}
/**
* Suppress Node error messages
*/
process.on('uncaughtException', function (err) {
log.error(`Error: ${err}`)
})