feat: allow user to move jan folder (#1649)
* feat: allow user to move jan folder Signed-off-by: James <james@jan.ai> --------- Signed-off-by: James <james@jan.ai> Co-authored-by: James <james@jan.ai> Co-authored-by: Louis <louis@jan.ai>
This commit is contained in:
parent
a3f2a16cb4
commit
4cf47777e6
7
core/.prettierrc
Normal file
7
core/.prettierrc
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"quoteProps": "consistent",
|
||||
"trailingComma": "es5",
|
||||
"endOfLine": "auto"
|
||||
}
|
||||
@ -7,12 +7,16 @@ export enum AppRoute {
|
||||
openExternalUrl = 'openExternalUrl',
|
||||
openAppDirectory = 'openAppDirectory',
|
||||
openFileExplore = 'openFileExplorer',
|
||||
selectDirectory = 'selectDirectory',
|
||||
getAppConfigurations = 'getAppConfigurations',
|
||||
updateAppConfiguration = 'updateAppConfiguration',
|
||||
relaunch = 'relaunch',
|
||||
joinPath = 'joinPath',
|
||||
baseName = 'baseName',
|
||||
startServer = 'startServer',
|
||||
stopServer = 'stopServer',
|
||||
log = 'log'
|
||||
log = 'log',
|
||||
logServer = 'logServer',
|
||||
}
|
||||
|
||||
export enum AppEvent {
|
||||
@ -55,7 +59,7 @@ export enum FileSystemRoute {
|
||||
}
|
||||
export enum FileManagerRoute {
|
||||
syncFile = 'syncFile',
|
||||
getUserSpace = 'getUserSpace',
|
||||
getJanDataFolderPath = 'getJanDataFolderPath',
|
||||
getResourcePath = 'getResourcePath',
|
||||
fileStat = 'fileStat',
|
||||
}
|
||||
|
||||
@ -35,10 +35,11 @@ const abortDownload: (fileName: string) => Promise<any> = (fileName) =>
|
||||
global.core.api?.abortDownload(fileName)
|
||||
|
||||
/**
|
||||
* Gets the user space path.
|
||||
* @returns {Promise<any>} A Promise that resolves with the user space path.
|
||||
* Gets Jan's data folder path.
|
||||
*
|
||||
* @returns {Promise<string>} A Promise that resolves with Jan's data folder path.
|
||||
*/
|
||||
const getUserSpace = (): Promise<string> => global.core.api?.getUserSpace()
|
||||
const getJanDataFolderPath = (): Promise<string> => global.core.api?.getJanDataFolderPath()
|
||||
|
||||
/**
|
||||
* Opens the file explorer at a specific path.
|
||||
@ -103,12 +104,12 @@ export {
|
||||
executeOnMain,
|
||||
downloadFile,
|
||||
abortDownload,
|
||||
getUserSpace,
|
||||
getJanDataFolderPath,
|
||||
openFileExplorer,
|
||||
getResourcePath,
|
||||
joinPath,
|
||||
openExternalUrl,
|
||||
baseName,
|
||||
log,
|
||||
FileStat
|
||||
FileStat,
|
||||
}
|
||||
|
||||
@ -2,13 +2,10 @@ import fs from 'fs'
|
||||
import { JanApiRouteConfiguration, RouteConfiguration } from './configuration'
|
||||
import { join } from 'path'
|
||||
import { ContentType, MessageStatus, Model, ThreadMessage } from './../../../index'
|
||||
|
||||
const os = require('os')
|
||||
|
||||
const path = join(os.homedir(), 'jan')
|
||||
import { getJanDataFolderPath } from '../../utils'
|
||||
|
||||
export const getBuilder = async (configuration: RouteConfiguration) => {
|
||||
const directoryPath = join(path, configuration.dirName)
|
||||
const directoryPath = join(getJanDataFolderPath(), configuration.dirName)
|
||||
try {
|
||||
if (!fs.existsSync(directoryPath)) {
|
||||
console.debug('model folder not found')
|
||||
@ -72,7 +69,7 @@ export const deleteBuilder = async (configuration: RouteConfiguration, id: strin
|
||||
}
|
||||
}
|
||||
|
||||
const directoryPath = join(path, configuration.dirName)
|
||||
const directoryPath = join(getJanDataFolderPath(), configuration.dirName)
|
||||
try {
|
||||
const data = await retrieveBuilder(configuration, id)
|
||||
if (!data) {
|
||||
@ -94,7 +91,7 @@ export const deleteBuilder = async (configuration: RouteConfiguration, id: strin
|
||||
}
|
||||
|
||||
export const getMessages = async (threadId: string): Promise<ThreadMessage[]> => {
|
||||
const threadDirPath = join(path, 'threads', threadId)
|
||||
const threadDirPath = join(getJanDataFolderPath(), 'threads', threadId)
|
||||
const messageFile = 'messages.jsonl'
|
||||
try {
|
||||
const files: string[] = fs.readdirSync(threadDirPath)
|
||||
@ -155,7 +152,7 @@ export const createThread = async (thread: any) => {
|
||||
created: Date.now(),
|
||||
updated: Date.now(),
|
||||
}
|
||||
const threadDirPath = join(path, 'threads', updatedThread.id)
|
||||
const threadDirPath = join(getJanDataFolderPath(), 'threads', updatedThread.id)
|
||||
const threadJsonPath = join(threadDirPath, threadMetadataFileName)
|
||||
|
||||
if (!fs.existsSync(threadDirPath)) {
|
||||
@ -189,7 +186,7 @@ export const updateThread = async (threadId: string, thread: any) => {
|
||||
updated: Date.now(),
|
||||
}
|
||||
try {
|
||||
const threadDirPath = join(path, 'threads', updatedThread.id)
|
||||
const threadDirPath = join(getJanDataFolderPath(), 'threads', updatedThread.id)
|
||||
const threadJsonPath = join(threadDirPath, threadMetadataFileName)
|
||||
|
||||
await fs.writeFileSync(threadJsonPath, JSON.stringify(updatedThread, null, 2))
|
||||
@ -231,7 +228,7 @@ export const createMessage = async (threadId: string, message: any) => {
|
||||
],
|
||||
}
|
||||
|
||||
const threadDirPath = join(path, 'threads', threadId)
|
||||
const threadDirPath = join(getJanDataFolderPath(), 'threads', threadId)
|
||||
const threadMessagePath = join(threadDirPath, threadMessagesFileName)
|
||||
|
||||
if (!fs.existsSync(threadDirPath)) {
|
||||
@ -246,9 +243,12 @@ export const createMessage = async (threadId: string, message: any) => {
|
||||
}
|
||||
}
|
||||
|
||||
export const downloadModel = async (modelId: string, network?: { proxy?: string, ignoreSSL?: boolean }) => {
|
||||
const strictSSL = !network?.ignoreSSL;
|
||||
const proxy = network?.proxy?.startsWith('http') ? network.proxy : undefined;
|
||||
export const downloadModel = async (
|
||||
modelId: string,
|
||||
network?: { proxy?: string; ignoreSSL?: boolean }
|
||||
) => {
|
||||
const strictSSL = !network?.ignoreSSL
|
||||
const proxy = network?.proxy?.startsWith('http') ? network.proxy : undefined
|
||||
const model = await retrieveBuilder(JanApiRouteConfiguration.models, modelId)
|
||||
if (!model || model.object !== 'model') {
|
||||
return {
|
||||
@ -256,7 +256,7 @@ export const downloadModel = async (modelId: string, network?: { proxy?: string,
|
||||
}
|
||||
}
|
||||
|
||||
const directoryPath = join(path, 'models', modelId)
|
||||
const directoryPath = join(getJanDataFolderPath(), 'models', modelId)
|
||||
if (!fs.existsSync(directoryPath)) {
|
||||
fs.mkdirSync(directoryPath)
|
||||
}
|
||||
@ -265,7 +265,7 @@ export const downloadModel = async (modelId: string, network?: { proxy?: string,
|
||||
const modelBinaryPath = join(directoryPath, modelId)
|
||||
|
||||
const request = require('request')
|
||||
const rq = request({url: model.source_url, strictSSL, proxy })
|
||||
const rq = request({ url: model.source_url, strictSSL, proxy })
|
||||
const progress = require('request-progress')
|
||||
progress(rq, {})
|
||||
.on('progress', function (state: any) {
|
||||
@ -316,7 +316,7 @@ export const chatCompletions = async (request: any, reply: any) => {
|
||||
reply.raw.writeHead(200, {
|
||||
'Content-Type': 'text/event-stream',
|
||||
'Cache-Control': 'no-cache',
|
||||
Connection: 'keep-alive',
|
||||
'Connection': 'keep-alive',
|
||||
})
|
||||
|
||||
const headers: Record<string, any> = {
|
||||
@ -347,7 +347,7 @@ const getEngineConfiguration = async (engineId: string) => {
|
||||
if (engineId !== 'openai') {
|
||||
return undefined
|
||||
}
|
||||
const directoryPath = join(path, 'engines')
|
||||
const directoryPath = join(getJanDataFolderPath(), 'engines')
|
||||
const filePath = join(directoryPath, `${engineId}.json`)
|
||||
const data = await fs.readFileSync(filePath, 'utf-8')
|
||||
return JSON.parse(data)
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { DownloadRoute } from "../../../api";
|
||||
import { join } from "path";
|
||||
import { userSpacePath } from "../../extension/manager";
|
||||
import { DownloadManager } from "../../download";
|
||||
import { HttpServer } from "../HttpServer";
|
||||
import { createWriteStream } from "fs";
|
||||
import { DownloadRoute } from '../../../api'
|
||||
import { join } from 'path'
|
||||
import { DownloadManager } from '../../download'
|
||||
import { HttpServer } from '../HttpServer'
|
||||
import { createWriteStream } from 'fs'
|
||||
import { getJanDataFolderPath } from '../../utils'
|
||||
import { normalizeFilePath } from "../../path";
|
||||
|
||||
export const downloadRouter = async (app: HttpServer) => {
|
||||
@ -13,7 +13,7 @@ export const downloadRouter = async (app: HttpServer) => {
|
||||
const body = JSON.parse(req.body as any);
|
||||
const normalizedArgs = body.map((arg: any) => {
|
||||
if (typeof arg === "string") {
|
||||
return join(userSpacePath, normalizeFilePath(arg));
|
||||
return join(getJanDataFolderPath(), normalizeFilePath(arg));
|
||||
}
|
||||
return arg;
|
||||
});
|
||||
@ -44,7 +44,7 @@ export const downloadRouter = async (app: HttpServer) => {
|
||||
const body = JSON.parse(req.body as any);
|
||||
const normalizedArgs = body.map((arg: any) => {
|
||||
if (typeof arg === "string") {
|
||||
return join(userSpacePath, normalizeFilePath(arg));
|
||||
return join(getJanDataFolderPath(), normalizeFilePath(arg));
|
||||
}
|
||||
return arg;
|
||||
});
|
||||
|
||||
@ -1,20 +1,20 @@
|
||||
import { join, extname } from 'path'
|
||||
import { ExtensionRoute } from '../../../api/index'
|
||||
import { userSpacePath } from '../../extension/manager'
|
||||
import { ModuleManager } from '../../module'
|
||||
import { getActiveExtensions, installExtensions } from '../../extension/store'
|
||||
import { HttpServer } from '../HttpServer'
|
||||
|
||||
import { readdirSync } from 'fs'
|
||||
import { getJanExtensionsPath } from '../../utils'
|
||||
|
||||
export const extensionRouter = async (app: HttpServer) => {
|
||||
// TODO: Share code between node projects
|
||||
app.post(`/${ExtensionRoute.getActiveExtensions}`, async (req, res) => {
|
||||
app.post(`/${ExtensionRoute.getActiveExtensions}`, async (_req, res) => {
|
||||
const activeExtensions = await getActiveExtensions()
|
||||
res.status(200).send(activeExtensions)
|
||||
})
|
||||
|
||||
app.post(`/${ExtensionRoute.baseExtensions}`, async (req, res) => {
|
||||
app.post(`/${ExtensionRoute.baseExtensions}`, async (_req, res) => {
|
||||
const baseExtensionPath = join(__dirname, '..', '..', '..', 'pre-install')
|
||||
const extensions = readdirSync(baseExtensionPath)
|
||||
.filter((file) => extname(file) === '.tgz')
|
||||
@ -23,7 +23,7 @@ export const extensionRouter = async (app: HttpServer) => {
|
||||
res.status(200).send(extensions)
|
||||
})
|
||||
|
||||
app.post(`/${ExtensionRoute.installExtension}`, async (req, res) => {
|
||||
app.post(`/${ExtensionRoute.installExtension}`, async (req) => {
|
||||
const extensions = req.body as any
|
||||
const installed = await installExtensions(JSON.parse(extensions)[0])
|
||||
return JSON.parse(JSON.stringify(installed))
|
||||
@ -32,7 +32,7 @@ export const extensionRouter = async (app: HttpServer) => {
|
||||
app.post(`/${ExtensionRoute.invokeExtensionFunc}`, async (req, res) => {
|
||||
const args = JSON.parse(req.body as any)
|
||||
console.debug(args)
|
||||
const module = await import(join(userSpacePath, 'extensions', args[0]))
|
||||
const module = await import(join(getJanExtensionsPath(), args[0]))
|
||||
|
||||
ModuleManager.instance.setModule(args[0], module)
|
||||
const method = args[1]
|
||||
|
||||
@ -4,7 +4,7 @@ import { HttpServer } from '../../index'
|
||||
export const fsRouter = async (app: HttpServer) => {
|
||||
app.post(`/app/${FileManagerRoute.syncFile}`, async (request: any, reply: any) => {})
|
||||
|
||||
app.post(`/app/${FileManagerRoute.getUserSpace}`, async (request: any, reply: any) => {})
|
||||
app.post(`/app/${FileManagerRoute.getJanDataFolderPath}`, async (request: any, reply: any) => {})
|
||||
|
||||
app.post(`/app/${FileManagerRoute.getResourcePath}`, async (request: any, reply: any) => {})
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { FileSystemRoute } from '../../../api'
|
||||
import { join } from 'path'
|
||||
import { HttpServer } from '../HttpServer'
|
||||
import { userSpacePath } from '../../extension/manager'
|
||||
import { getJanDataFolderPath } from '../../utils'
|
||||
|
||||
export const fsRouter = async (app: HttpServer) => {
|
||||
const moduleName = 'fs'
|
||||
@ -14,7 +14,7 @@ export const fsRouter = async (app: HttpServer) => {
|
||||
return mdl[route](
|
||||
...body.map((arg: any) =>
|
||||
typeof arg === 'string' && arg.includes('file:/')
|
||||
? join(userSpacePath, arg.replace('file:/', ''))
|
||||
? join(getJanDataFolderPath(), arg.replace('file:/', ''))
|
||||
: arg,
|
||||
),
|
||||
)
|
||||
|
||||
@ -103,7 +103,7 @@ export default class Extension {
|
||||
const pacote = await import('pacote')
|
||||
await pacote.extract(
|
||||
this.specifier,
|
||||
join(ExtensionManager.instance.extensionsPath ?? '', this.name ?? ''),
|
||||
join(ExtensionManager.instance.getExtensionsPath() ?? '', this.name ?? ''),
|
||||
this.installOptions,
|
||||
)
|
||||
|
||||
@ -166,9 +166,9 @@ export default class Extension {
|
||||
* @returns the latest available version if a new version is available or false if not.
|
||||
*/
|
||||
async isUpdateAvailable() {
|
||||
return import('pacote').then((pacote) => {
|
||||
if (this.origin) {
|
||||
return pacote.manifest(this.origin).then((mnf) => {
|
||||
return import('pacote').then((pacote) => {
|
||||
if (this.origin) {
|
||||
return pacote.manifest(this.origin).then((mnf) => {
|
||||
return mnf.version !== this.version ? mnf.version : false
|
||||
})
|
||||
}
|
||||
@ -179,8 +179,9 @@ export default class Extension {
|
||||
* Remove extension and refresh renderers.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async uninstall() {
|
||||
const extPath = resolve(ExtensionManager.instance.extensionsPath ?? '', this.name ?? '')
|
||||
async uninstall(): Promise<void> {
|
||||
const path = ExtensionManager.instance.getExtensionsPath()
|
||||
const extPath = resolve(path ?? '', this.name ?? '')
|
||||
await rmdirSync(extPath, { recursive: true })
|
||||
|
||||
this.emitUpdate()
|
||||
|
||||
@ -35,17 +35,17 @@ async function registerExtensionProtocol() {
|
||||
let electron: any = undefined
|
||||
|
||||
try {
|
||||
const moduleName = "electron"
|
||||
const moduleName = 'electron'
|
||||
electron = await import(moduleName)
|
||||
} catch (err) {
|
||||
console.error('Electron is not available')
|
||||
}
|
||||
|
||||
const extensionPath = ExtensionManager.instance.getExtensionsPath()
|
||||
if (electron) {
|
||||
return electron.protocol.registerFileProtocol('extension', (request: any, callback: any) => {
|
||||
const entry = request.url.substr('extension://'.length - 1)
|
||||
|
||||
const url = normalize(ExtensionManager.instance.extensionsPath + entry)
|
||||
const url = normalize(extensionPath + entry)
|
||||
callback({ path: url })
|
||||
})
|
||||
}
|
||||
@ -120,7 +120,7 @@ function loadExtension(ext: any) {
|
||||
* @returns {extensionManager} A set of functions used to manage the extension lifecycle.
|
||||
*/
|
||||
export function getStore() {
|
||||
if (!ExtensionManager.instance.extensionsPath) {
|
||||
if (!ExtensionManager.instance.getExtensionsFile()) {
|
||||
throw new Error(
|
||||
'The extension path has not yet been set up. Please run useExtensions before accessing the store',
|
||||
)
|
||||
@ -133,4 +133,4 @@ export function getStore() {
|
||||
getActiveExtensions,
|
||||
removeExtension,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,44 +1,45 @@
|
||||
import { join, resolve } from "path";
|
||||
import { join, resolve } from 'path'
|
||||
|
||||
import { existsSync, mkdirSync, writeFileSync } from 'fs'
|
||||
|
||||
import { existsSync, mkdirSync, writeFileSync } from "fs";
|
||||
import { homedir } from "os"
|
||||
/**
|
||||
* Manages extension installation and migration.
|
||||
*/
|
||||
|
||||
export const userSpacePath = join(homedir(), "jan");
|
||||
|
||||
export class ExtensionManager {
|
||||
public static instance: ExtensionManager = new ExtensionManager();
|
||||
public static instance: ExtensionManager = new ExtensionManager()
|
||||
|
||||
extensionsPath: string | undefined = join(userSpacePath, "extensions");
|
||||
private extensionsPath: string | undefined
|
||||
|
||||
constructor() {
|
||||
if (ExtensionManager.instance) {
|
||||
return ExtensionManager.instance;
|
||||
return ExtensionManager.instance
|
||||
}
|
||||
}
|
||||
|
||||
getExtensionsPath(): string | undefined {
|
||||
return this.extensionsPath
|
||||
}
|
||||
|
||||
setExtensionsPath(extPath: string) {
|
||||
// Create folder if it does not exist
|
||||
let extDir;
|
||||
let extDir
|
||||
try {
|
||||
extDir = resolve(extPath);
|
||||
if (extDir.length < 2) throw new Error();
|
||||
extDir = resolve(extPath)
|
||||
if (extDir.length < 2) throw new Error()
|
||||
|
||||
if (!existsSync(extDir)) mkdirSync(extDir);
|
||||
if (!existsSync(extDir)) mkdirSync(extDir)
|
||||
|
||||
const extensionsJson = join(extDir, "extensions.json");
|
||||
if (!existsSync(extensionsJson))
|
||||
writeFileSync(extensionsJson, "{}");
|
||||
const extensionsJson = join(extDir, 'extensions.json')
|
||||
if (!existsSync(extensionsJson)) writeFileSync(extensionsJson, '{}')
|
||||
|
||||
this.extensionsPath = extDir;
|
||||
this.extensionsPath = extDir
|
||||
} catch (error) {
|
||||
throw new Error("Invalid path provided to the extensions folder");
|
||||
throw new Error('Invalid path provided to the extensions folder')
|
||||
}
|
||||
}
|
||||
|
||||
getExtensionsFile() {
|
||||
return join(this.extensionsPath ?? "", "extensions.json");
|
||||
return join(this.extensionsPath ?? '', 'extensions.json')
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,4 +6,5 @@ export * from './download'
|
||||
export * from './module'
|
||||
export * from './api'
|
||||
export * from './log'
|
||||
export * from './utils'
|
||||
export * from './path'
|
||||
|
||||
@ -1,22 +1,35 @@
|
||||
import fs from 'fs'
|
||||
import util from 'util'
|
||||
import path from 'path'
|
||||
import os from 'os'
|
||||
import { getAppLogPath, getServerLogPath } from './utils'
|
||||
|
||||
export const logDir = path.join(os.homedir(), 'jan', 'logs')
|
||||
|
||||
export const log = function (message: string, fileName: string = 'app.log') {
|
||||
if (!fs.existsSync(logDir)) {
|
||||
fs.mkdirSync(logDir, { recursive: true })
|
||||
}
|
||||
export const log = function (message: string) {
|
||||
const appLogPath = getAppLogPath()
|
||||
if (!message.startsWith('[')) {
|
||||
message = `[APP]::${message}`
|
||||
}
|
||||
|
||||
message = `${new Date().toISOString()} ${message}`
|
||||
|
||||
if (fs.existsSync(logDir)) {
|
||||
var log_file = fs.createWriteStream(path.join(logDir, fileName), {
|
||||
if (fs.existsSync(appLogPath)) {
|
||||
var log_file = fs.createWriteStream(appLogPath, {
|
||||
flags: 'a',
|
||||
})
|
||||
log_file.write(util.format(message) + '\n')
|
||||
log_file.close()
|
||||
console.debug(message)
|
||||
}
|
||||
}
|
||||
|
||||
export const logServer = function (message: string) {
|
||||
const serverLogPath = getServerLogPath()
|
||||
if (!message.startsWith('[')) {
|
||||
message = `[SERVER]::${message}`
|
||||
}
|
||||
|
||||
message = `${new Date().toISOString()} ${message}`
|
||||
|
||||
if (fs.existsSync(serverLogPath)) {
|
||||
var log_file = fs.createWriteStream(serverLogPath, {
|
||||
flags: 'a',
|
||||
})
|
||||
log_file.write(util.format(message) + '\n')
|
||||
|
||||
103
core/src/node/utils/index.ts
Normal file
103
core/src/node/utils/index.ts
Normal file
@ -0,0 +1,103 @@
|
||||
import { AppConfiguration } from "../../types";
|
||||
import { join } from "path";
|
||||
import fs from "fs";
|
||||
import os from "os";
|
||||
|
||||
// TODO: move this to core
|
||||
const configurationFileName = "settings.json";
|
||||
|
||||
// TODO: do no specify app name in framework module
|
||||
const defaultJanDataFolder = join(os.homedir(), "jan");
|
||||
const defaultAppConfig: AppConfiguration = {
|
||||
data_folder: defaultJanDataFolder,
|
||||
};
|
||||
|
||||
/**
|
||||
* Getting App Configurations.
|
||||
*
|
||||
* @returns {AppConfiguration} The app configurations.
|
||||
*/
|
||||
export const getAppConfigurations = (): AppConfiguration => {
|
||||
// Retrieve Application Support folder path
|
||||
// Fallback to user home directory if not found
|
||||
const configurationFile = getConfigurationFilePath();
|
||||
|
||||
if (!fs.existsSync(configurationFile)) {
|
||||
// create default app config if we don't have one
|
||||
console.debug(`App config not found, creating default config at ${configurationFile}`);
|
||||
fs.writeFileSync(configurationFile, JSON.stringify(defaultAppConfig));
|
||||
return defaultAppConfig;
|
||||
}
|
||||
|
||||
try {
|
||||
const appConfigurations: AppConfiguration = JSON.parse(
|
||||
fs.readFileSync(configurationFile, "utf-8"),
|
||||
);
|
||||
return appConfigurations;
|
||||
} catch (err) {
|
||||
console.error(`Failed to read app config, return default config instead! Err: ${err}`);
|
||||
return defaultAppConfig;
|
||||
}
|
||||
};
|
||||
|
||||
const getConfigurationFilePath = () =>
|
||||
join(
|
||||
global.core?.appPath() || process.env[process.platform == "win32" ? "USERPROFILE" : "HOME"],
|
||||
configurationFileName,
|
||||
);
|
||||
|
||||
export const updateAppConfiguration = (configuration: AppConfiguration): Promise<void> => {
|
||||
const configurationFile = getConfigurationFilePath();
|
||||
console.debug("updateAppConfiguration, configurationFile: ", configurationFile);
|
||||
|
||||
fs.writeFileSync(configurationFile, JSON.stringify(configuration));
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
/**
|
||||
* Utility function to get server log path
|
||||
*
|
||||
* @returns {string} The log path.
|
||||
*/
|
||||
export const getServerLogPath = (): string => {
|
||||
const appConfigurations = getAppConfigurations();
|
||||
const logFolderPath = join(appConfigurations.data_folder, "logs");
|
||||
if (!fs.existsSync(logFolderPath)) {
|
||||
fs.mkdirSync(logFolderPath, { recursive: true });
|
||||
}
|
||||
return join(logFolderPath, "server.log");
|
||||
};
|
||||
|
||||
/**
|
||||
* Utility function to get app log path
|
||||
*
|
||||
* @returns {string} The log path.
|
||||
*/
|
||||
export const getAppLogPath = (): string => {
|
||||
const appConfigurations = getAppConfigurations();
|
||||
const logFolderPath = join(appConfigurations.data_folder, "logs");
|
||||
if (!fs.existsSync(logFolderPath)) {
|
||||
fs.mkdirSync(logFolderPath, { recursive: true });
|
||||
}
|
||||
return join(logFolderPath, "app.log");
|
||||
};
|
||||
|
||||
/**
|
||||
* Utility function to get data folder path
|
||||
*
|
||||
* @returns {string} The data folder path.
|
||||
*/
|
||||
export const getJanDataFolderPath = (): string => {
|
||||
const appConfigurations = getAppConfigurations();
|
||||
return appConfigurations.data_folder;
|
||||
};
|
||||
|
||||
/**
|
||||
* Utility function to get extension path
|
||||
*
|
||||
* @returns {string} The extensions path.
|
||||
*/
|
||||
export const getJanExtensionsPath = (): string => {
|
||||
const appConfigurations = getAppConfigurations();
|
||||
return join(appConfigurations.data_folder, "extensions");
|
||||
};
|
||||
3
core/src/types/config/appConfigEntity.ts
Normal file
3
core/src/types/config/appConfigEntity.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export type AppConfiguration = {
|
||||
data_folder: string
|
||||
}
|
||||
1
core/src/types/config/index.ts
Normal file
1
core/src/types/config/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './appConfigEntity'
|
||||
@ -5,3 +5,4 @@ export * from './message'
|
||||
export * from './inference'
|
||||
export * from './monitoring'
|
||||
export * from './file'
|
||||
export * from './config'
|
||||
|
||||
@ -34,5 +34,5 @@ module.exports = {
|
||||
{ name: 'Link', linkAttribute: 'to' },
|
||||
],
|
||||
},
|
||||
ignorePatterns: ['build', 'renderer', 'node_modules'],
|
||||
ignorePatterns: ['build', 'renderer', 'node_modules', '@global'],
|
||||
}
|
||||
|
||||
10
electron/@global/index.ts
Normal file
10
electron/@global/index.ts
Normal file
@ -0,0 +1,10 @@
|
||||
export {}
|
||||
|
||||
declare global {
|
||||
namespace NodeJS {
|
||||
interface Global {
|
||||
core: any
|
||||
}
|
||||
}
|
||||
var core: any | undefined
|
||||
}
|
||||
@ -1,10 +1,19 @@
|
||||
import { app, ipcMain, shell } from 'electron'
|
||||
import { app, ipcMain, dialog, shell } from 'electron'
|
||||
import { join, basename } from 'path'
|
||||
import { WindowManager } from './../managers/window'
|
||||
import { getResourcePath, userSpacePath } from './../utils/path'
|
||||
import { AppRoute } from '@janhq/core'
|
||||
import { ModuleManager, init, log } from '@janhq/core/node'
|
||||
import { getResourcePath } from './../utils/path'
|
||||
import { AppRoute, AppConfiguration } from '@janhq/core'
|
||||
import { ServerConfig, startServer, stopServer } from '@janhq/server'
|
||||
import {
|
||||
ModuleManager,
|
||||
getJanDataFolderPath,
|
||||
getJanExtensionsPath,
|
||||
init,
|
||||
log,
|
||||
logServer,
|
||||
getAppConfigurations,
|
||||
updateAppConfiguration,
|
||||
} from '@janhq/core/node'
|
||||
|
||||
export function handleAppIPCs() {
|
||||
/**
|
||||
@ -13,7 +22,7 @@ export function handleAppIPCs() {
|
||||
* @param _event - The IPC event object.
|
||||
*/
|
||||
ipcMain.handle(AppRoute.openAppDirectory, async (_event) => {
|
||||
shell.openPath(userSpacePath)
|
||||
shell.openPath(getJanDataFolderPath())
|
||||
})
|
||||
|
||||
/**
|
||||
@ -76,7 +85,7 @@ export function handleAppIPCs() {
|
||||
* @param _event - The IPC event object.
|
||||
* @param url - The URL to reload.
|
||||
*/
|
||||
ipcMain.handle(AppRoute.relaunch, async (_event, url) => {
|
||||
ipcMain.handle(AppRoute.relaunch, async (_event) => {
|
||||
ModuleManager.instance.clearImportedModules()
|
||||
|
||||
if (app.isPackaged) {
|
||||
@ -85,7 +94,7 @@ export function handleAppIPCs() {
|
||||
} else {
|
||||
for (const modulePath in ModuleManager.instance.requiredModules) {
|
||||
delete require.cache[
|
||||
require.resolve(join(userSpacePath, 'extensions', modulePath))
|
||||
require.resolve(join(getJanExtensionsPath(), modulePath))
|
||||
]
|
||||
}
|
||||
init({
|
||||
@ -94,7 +103,7 @@ export function handleAppIPCs() {
|
||||
return true
|
||||
},
|
||||
// Path to install extension to
|
||||
extensionsPath: join(userSpacePath, 'extensions'),
|
||||
extensionsPath: getJanExtensionsPath(),
|
||||
})
|
||||
WindowManager.instance.currentWindow?.reload()
|
||||
}
|
||||
@ -103,7 +112,41 @@ export function handleAppIPCs() {
|
||||
/**
|
||||
* Log message to log file.
|
||||
*/
|
||||
ipcMain.handle(AppRoute.log, async (_event, message, fileName) =>
|
||||
log(message, fileName)
|
||||
ipcMain.handle(AppRoute.log, async (_event, message) => log(message))
|
||||
|
||||
/**
|
||||
* Log message to log file.
|
||||
*/
|
||||
ipcMain.handle(AppRoute.logServer, async (_event, message) =>
|
||||
logServer(message)
|
||||
)
|
||||
|
||||
ipcMain.handle(AppRoute.selectDirectory, async () => {
|
||||
const mainWindow = WindowManager.instance.currentWindow
|
||||
if (!mainWindow) {
|
||||
console.error('No main window found')
|
||||
return
|
||||
}
|
||||
const { canceled, filePaths } = await dialog.showOpenDialog(mainWindow, {
|
||||
title: 'Select a folder',
|
||||
buttonLabel: 'Select Folder',
|
||||
properties: ['openDirectory', 'createDirectory'],
|
||||
})
|
||||
if (canceled) {
|
||||
return
|
||||
} else {
|
||||
return filePaths[0]
|
||||
}
|
||||
})
|
||||
|
||||
ipcMain.handle(AppRoute.getAppConfigurations, async () =>
|
||||
getAppConfigurations()
|
||||
)
|
||||
|
||||
ipcMain.handle(
|
||||
AppRoute.updateAppConfiguration,
|
||||
async (_event, appConfiguration: AppConfiguration) => {
|
||||
await updateAppConfiguration(appConfiguration)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import { app, ipcMain } from 'electron'
|
||||
import { resolve, join } from 'path'
|
||||
import { ipcMain } from 'electron'
|
||||
import { resolve } from 'path'
|
||||
import { WindowManager } from './../managers/window'
|
||||
import request from 'request'
|
||||
import { createWriteStream, renameSync } from 'fs'
|
||||
import { DownloadEvent, DownloadRoute } from '@janhq/core'
|
||||
const progress = require('request-progress')
|
||||
import { DownloadManager, normalizeFilePath } from '@janhq/core/node'
|
||||
import { DownloadManager, getJanDataFolderPath, normalizeFilePath } from '@janhq/core/node'
|
||||
|
||||
export function handleDownloaderIPCs() {
|
||||
/**
|
||||
@ -61,11 +61,11 @@ export function handleDownloaderIPCs() {
|
||||
const proxy = network?.proxy?.startsWith('http')
|
||||
? network.proxy
|
||||
: undefined
|
||||
const userDataPath = join(app.getPath('home'), 'jan')
|
||||
|
||||
if (typeof fileName === 'string') {
|
||||
fileName = normalizeFilePath(fileName)
|
||||
}
|
||||
const destination = resolve(userDataPath, fileName)
|
||||
const destination = resolve(getJanDataFolderPath(), fileName)
|
||||
const rq = request({ url, strictSSL, proxy })
|
||||
|
||||
// Put request to download manager instance
|
||||
|
||||
@ -7,10 +7,11 @@ import {
|
||||
getExtension,
|
||||
removeExtension,
|
||||
getActiveExtensions,
|
||||
ModuleManager
|
||||
ModuleManager,
|
||||
getJanExtensionsPath,
|
||||
} from '@janhq/core/node'
|
||||
|
||||
import { getResourcePath, userSpacePath } from './../utils/path'
|
||||
import { getResourcePath } from './../utils/path'
|
||||
import { ExtensionRoute } from '@janhq/core'
|
||||
|
||||
export function handleExtensionIPCs() {
|
||||
@ -27,7 +28,7 @@ export function handleExtensionIPCs() {
|
||||
ExtensionRoute.invokeExtensionFunc,
|
||||
async (_event, modulePath, method, ...args) => {
|
||||
const module = require(
|
||||
/* webpackIgnore: true */ join(userSpacePath, 'extensions', modulePath)
|
||||
/* webpackIgnore: true */ join(getJanExtensionsPath(), modulePath)
|
||||
)
|
||||
ModuleManager.instance.setModule(modulePath, module)
|
||||
|
||||
|
||||
@ -2,12 +2,11 @@ import { ipcMain } from 'electron'
|
||||
// @ts-ignore
|
||||
import reflect from '@alumna/reflect'
|
||||
|
||||
import { FileManagerRoute } from '@janhq/core'
|
||||
import { userSpacePath, getResourcePath } from './../utils/path'
|
||||
import { FileManagerRoute, FileStat } from '@janhq/core'
|
||||
import { getResourcePath } from './../utils/path'
|
||||
import fs from 'fs'
|
||||
import { join } from 'path'
|
||||
import { FileStat } from '@janhq/core'
|
||||
import { normalizeFilePath } from '@janhq/core/node'
|
||||
import { getJanDataFolderPath, normalizeFilePath } from '@janhq/core/node'
|
||||
|
||||
/**
|
||||
* Handles file system extensions operations.
|
||||
@ -28,10 +27,10 @@ export function handleFileMangerIPCs() {
|
||||
}
|
||||
)
|
||||
|
||||
// Handles the 'getUserSpace' IPC event. This event is triggered to get the user space path.
|
||||
// Handles the 'getJanDataFolderPath' IPC event. This event is triggered to get the user space path.
|
||||
ipcMain.handle(
|
||||
FileManagerRoute.getUserSpace,
|
||||
(): Promise<string> => Promise.resolve(userSpacePath)
|
||||
FileManagerRoute.getJanDataFolderPath,
|
||||
(): Promise<string> => Promise.resolve(getJanDataFolderPath())
|
||||
)
|
||||
|
||||
// Handles the 'getResourcePath' IPC event. This event is triggered to get the resource path.
|
||||
@ -45,7 +44,7 @@ export function handleFileMangerIPCs() {
|
||||
async (_event, path: string): Promise<FileStat | undefined> => {
|
||||
const normalizedPath = normalizeFilePath(path)
|
||||
|
||||
const fullPath = join(userSpacePath, normalizedPath)
|
||||
const fullPath = join(getJanDataFolderPath(), normalizedPath)
|
||||
const isExist = fs.existsSync(fullPath)
|
||||
if (!isExist) return undefined
|
||||
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { ipcMain } from 'electron'
|
||||
|
||||
import { FileSystemRoute } from '@janhq/core'
|
||||
import { userSpacePath } from '../utils/path'
|
||||
import { join } from 'path'
|
||||
import { normalizeFilePath } from '@janhq/core/node'
|
||||
import { getJanDataFolderPath, normalizeFilePath } from '@janhq/core/node'
|
||||
|
||||
/**
|
||||
* Handles file system operations.
|
||||
*/
|
||||
@ -16,7 +16,7 @@ export function handleFsIPCs() {
|
||||
...args.map((arg) =>
|
||||
typeof arg === 'string' &&
|
||||
(arg.includes(`file:/`) || arg.includes(`file:\\`))
|
||||
? join(userSpacePath, normalizeFilePath(arg))
|
||||
? join(getJanDataFolderPath(), normalizeFilePath(arg))
|
||||
: arg
|
||||
)
|
||||
)
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
import { app, BrowserWindow } from 'electron'
|
||||
import { join } from 'path'
|
||||
import { setupMenu } from './utils/menu'
|
||||
import { createUserSpace } from './utils/path'
|
||||
/**
|
||||
* Managers
|
||||
**/
|
||||
@ -21,12 +19,16 @@ import { handleFsIPCs } from './handlers/fs'
|
||||
/**
|
||||
* Utils
|
||||
**/
|
||||
import { setupMenu } from './utils/menu'
|
||||
import { createUserSpace } from './utils/path'
|
||||
import { migrateExtensions } from './utils/migration'
|
||||
import { cleanUpAndQuit } from './utils/clean'
|
||||
import { setupExtensions } from './utils/extension'
|
||||
import { setupCore } from './utils/setup'
|
||||
|
||||
app
|
||||
.whenReady()
|
||||
.then(setupCore)
|
||||
.then(createUserSpace)
|
||||
.then(migrateExtensions)
|
||||
.then(setupExtensions)
|
||||
@ -94,9 +96,8 @@ function handleIPCs() {
|
||||
}
|
||||
|
||||
/*
|
||||
** Suppress Node error messages
|
||||
*/
|
||||
** Suppress Node error messages
|
||||
*/
|
||||
process.on('uncaughtException', function (err) {
|
||||
// TODO: Write error to log file in #1447
|
||||
log(`Error: ${err}`)
|
||||
})
|
||||
|
||||
@ -1,13 +1,12 @@
|
||||
import { init, userSpacePath } from '@janhq/core/node'
|
||||
import path from 'path'
|
||||
import { getJanExtensionsPath, init } from '@janhq/core/node'
|
||||
|
||||
export const setupExtensions = () => {
|
||||
export const setupExtensions = async () => {
|
||||
init({
|
||||
// Function to check from the main process that user wants to install a extension
|
||||
confirmInstall: async (_extensions: string[]) => {
|
||||
return true
|
||||
},
|
||||
// Path to install extension to
|
||||
extensionsPath: path.join(userSpacePath, 'extensions'),
|
||||
extensionsPath: getJanExtensionsPath(),
|
||||
})
|
||||
}
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { app } from 'electron'
|
||||
import { join } from 'path'
|
||||
|
||||
import { rmdir } from 'fs'
|
||||
import Store from 'electron-store'
|
||||
import { userSpacePath } from './path'
|
||||
import { getJanExtensionsPath } from '@janhq/core/node'
|
||||
|
||||
/**
|
||||
* Migrates the extensions by deleting the `extensions` directory in the user data path.
|
||||
* If the `migrated_version` key in the `Store` object does not match the current app version,
|
||||
@ -15,9 +15,8 @@ export function migrateExtensions() {
|
||||
const store = new Store()
|
||||
if (store.get('migrated_version') !== app.getVersion()) {
|
||||
console.debug('start migration:', store.get('migrated_version'))
|
||||
const fullPath = join(userSpacePath, 'extensions')
|
||||
|
||||
rmdir(fullPath, { recursive: true }, function (err) {
|
||||
rmdir(getJanExtensionsPath(), { recursive: true }, function (err) {
|
||||
if (err) console.error(err)
|
||||
store.set('migrated_version', app.getVersion())
|
||||
console.debug('migrate extensions done')
|
||||
|
||||
@ -1,13 +1,22 @@
|
||||
import { join } from 'path'
|
||||
import { app } from 'electron'
|
||||
import { mkdir } from 'fs-extra'
|
||||
import { existsSync } from 'fs'
|
||||
import { getJanDataFolderPath } from '@janhq/core/node'
|
||||
|
||||
export async function createUserSpace(): Promise<void> {
|
||||
return mkdir(userSpacePath).catch(() => {})
|
||||
const janDataFolderPath = getJanDataFolderPath()
|
||||
if (!existsSync(janDataFolderPath)) {
|
||||
try {
|
||||
await mkdir(janDataFolderPath)
|
||||
} catch (err) {
|
||||
console.error(
|
||||
`Unable to create Jan data folder at ${janDataFolderPath}: ${err}`
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const userSpacePath = join(app.getPath('home'), 'jan')
|
||||
|
||||
export function getResourcePath() {
|
||||
let appPath = join(app.getAppPath(), '..', 'app.asar.unpacked')
|
||||
|
||||
|
||||
9
electron/utils/setup.ts
Normal file
9
electron/utils/setup.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { app } from 'electron'
|
||||
|
||||
export const setupCore = async () => {
|
||||
// Setup core api for main process
|
||||
global.core = {
|
||||
// Define appPath function for app to retrieve app path globaly
|
||||
appPath: () => app.getPath('userData')
|
||||
}
|
||||
}
|
||||
@ -4,11 +4,10 @@ import { ChildProcessWithoutNullStreams, spawn } from "child_process";
|
||||
import tcpPortUsed from "tcp-port-used";
|
||||
import fetchRT from "fetch-retry";
|
||||
import osUtils from "os-utils";
|
||||
import { log } from "@janhq/core/node";
|
||||
import { log, getJanDataFolderPath } from "@janhq/core/node";
|
||||
import { getNitroProcessInfo, updateNvidiaInfo } from "./nvidia";
|
||||
import { Model, InferenceEngine, ModelSettingParams } from "@janhq/core";
|
||||
import { executableNitroFile } from "./execute";
|
||||
import { homedir } from "os";
|
||||
// Polyfill fetch with retry
|
||||
const fetchRetry = fetchRT(fetch);
|
||||
|
||||
@ -86,7 +85,7 @@ async function runModel(
|
||||
}
|
||||
|
||||
currentModelFile = wrapper.modelFullPath;
|
||||
const janRoot = path.join(homedir(), "jan");
|
||||
const janRoot = await getJanDataFolderPath();
|
||||
if (!currentModelFile.includes(janRoot)) {
|
||||
currentModelFile = path.join(janRoot, currentModelFile);
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { writeFileSync, existsSync, readFileSync } from "fs";
|
||||
import { exec } from "child_process";
|
||||
import path from "path";
|
||||
import { homedir } from "os";
|
||||
import { getJanDataFolderPath } from "@janhq/core/node";
|
||||
|
||||
/**
|
||||
* Default GPU settings
|
||||
@ -25,8 +25,7 @@ const DEFALT_SETTINGS = {
|
||||
* Path to the settings file
|
||||
**/
|
||||
export const NVIDIA_INFO_FILE = path.join(
|
||||
homedir(),
|
||||
"jan",
|
||||
getJanDataFolderPath(),
|
||||
"settings",
|
||||
"settings.json"
|
||||
);
|
||||
@ -40,7 +39,7 @@ let nitroProcessInfo: NitroProcessInfo | undefined = undefined;
|
||||
* Nitro process info
|
||||
*/
|
||||
export interface NitroProcessInfo {
|
||||
isRunning: boolean
|
||||
isRunning: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -3,11 +3,11 @@ import {
|
||||
downloadFile,
|
||||
abortDownload,
|
||||
getResourcePath,
|
||||
getUserSpace,
|
||||
InferenceEngine,
|
||||
joinPath,
|
||||
ModelExtension,
|
||||
Model,
|
||||
getJanDataFolderPath,
|
||||
} from '@janhq/core'
|
||||
|
||||
/**
|
||||
@ -39,7 +39,6 @@ export default class JanModelExtension extends ModelExtension {
|
||||
|
||||
private async copyModelsToHomeDir() {
|
||||
try {
|
||||
|
||||
// Check for migration conditions
|
||||
if (
|
||||
localStorage.getItem(`${EXTENSION_NAME}-version`) === VERSION &&
|
||||
@ -53,8 +52,8 @@ export default class JanModelExtension extends ModelExtension {
|
||||
const resourePath = await getResourcePath()
|
||||
const srcPath = await joinPath([resourePath, 'models'])
|
||||
|
||||
const userSpace = await getUserSpace()
|
||||
const destPath = await joinPath([userSpace, 'models'])
|
||||
const janDataFolderPath = await getJanDataFolderPath()
|
||||
const destPath = await joinPath([janDataFolderPath, 'models'])
|
||||
|
||||
await fs.syncFile(srcPath, destPath)
|
||||
|
||||
|
||||
@ -1,25 +1,23 @@
|
||||
const os = require("os");
|
||||
const nodeOsUtils = require("node-os-utils");
|
||||
|
||||
const getResourcesInfo = () =>
|
||||
new Promise((resolve) => {
|
||||
nodeOsUtils.mem.used()
|
||||
.then(ramUsedInfo => {
|
||||
const totalMemory = ramUsedInfo.totalMemMb * 1024 * 1024;
|
||||
const usedMemory = ramUsedInfo.usedMemMb * 1024 * 1024;
|
||||
const response = {
|
||||
mem: {
|
||||
totalMemory,
|
||||
usedMemory,
|
||||
},
|
||||
};
|
||||
resolve(response);
|
||||
})
|
||||
nodeOsUtils.mem.used().then((ramUsedInfo) => {
|
||||
const totalMemory = ramUsedInfo.totalMemMb * 1024 * 1024;
|
||||
const usedMemory = ramUsedInfo.usedMemMb * 1024 * 1024;
|
||||
const response = {
|
||||
mem: {
|
||||
totalMemory,
|
||||
usedMemory,
|
||||
},
|
||||
};
|
||||
resolve(response);
|
||||
});
|
||||
});
|
||||
|
||||
const getCurrentLoad = () =>
|
||||
new Promise((resolve) => {
|
||||
nodeOsUtils.cpu.usage().then(cpuPercentage =>{
|
||||
nodeOsUtils.cpu.usage().then((cpuPercentage) => {
|
||||
const response = {
|
||||
cpu: {
|
||||
usage: cpuPercentage,
|
||||
|
||||
@ -1,9 +1,12 @@
|
||||
import fastify from "fastify";
|
||||
import dotenv from "dotenv";
|
||||
import { log, v1Router } from "@janhq/core/node";
|
||||
import path from "path";
|
||||
|
||||
import os from "os";
|
||||
import {
|
||||
getServerLogPath,
|
||||
v1Router,
|
||||
logServer,
|
||||
getJanExtensionsPath,
|
||||
} from "@janhq/core/node";
|
||||
import { join } from "path";
|
||||
|
||||
// Load environment variables
|
||||
dotenv.config();
|
||||
@ -11,7 +14,6 @@ dotenv.config();
|
||||
// Define default settings
|
||||
const JAN_API_HOST = process.env.JAN_API_HOST || "127.0.0.1";
|
||||
const JAN_API_PORT = Number.parseInt(process.env.JAN_API_PORT || "1337");
|
||||
const serverLogPath = path.join(os.homedir(), "jan", "logs", "server.log");
|
||||
|
||||
// Initialize server settings
|
||||
let server: any | undefined = undefined;
|
||||
@ -40,7 +42,7 @@ export interface ServerConfig {
|
||||
|
||||
/**
|
||||
* Function to start the server
|
||||
* @param configs - Server configurations
|
||||
* @param configs - Server configurations
|
||||
*/
|
||||
export const startServer = async (configs?: ServerConfig) => {
|
||||
// Update server settings
|
||||
@ -48,12 +50,12 @@ export const startServer = async (configs?: ServerConfig) => {
|
||||
hostSetting = configs?.host ?? JAN_API_HOST;
|
||||
portSetting = configs?.port ?? JAN_API_PORT;
|
||||
corsEnbaled = configs?.isCorsEnabled ?? true;
|
||||
const serverLogPath = getServerLogPath();
|
||||
|
||||
// Start the server
|
||||
try {
|
||||
// Log server start
|
||||
if (isVerbose)
|
||||
log(`[API]::Debug: Starting JAN API server...`, "server.log");
|
||||
if (isVerbose) logServer(`Debug: Starting JAN API server...`);
|
||||
|
||||
// Initialize Fastify server with logging
|
||||
server = fastify({
|
||||
@ -78,7 +80,7 @@ export const startServer = async (configs?: ServerConfig) => {
|
||||
// Register Swagger UI
|
||||
await server.register(require("@fastify/swagger-ui"), {
|
||||
routePrefix: "/",
|
||||
baseDir: configs?.baseDir ?? path.join(__dirname, "../..", "./docs/openapi"),
|
||||
baseDir: configs?.baseDir ?? join(__dirname, "../..", "./docs/openapi"),
|
||||
uiConfig: {
|
||||
docExpansion: "full",
|
||||
deepLinking: false,
|
||||
@ -92,9 +94,7 @@ export const startServer = async (configs?: ServerConfig) => {
|
||||
await server.register(
|
||||
(childContext: any, _: any, done: any) => {
|
||||
childContext.register(require("@fastify/static"), {
|
||||
root:
|
||||
process.env.EXTENSION_ROOT ||
|
||||
path.join(require("os").homedir(), "jan", "extensions"),
|
||||
root: getJanExtensionsPath(),
|
||||
wildcard: false,
|
||||
});
|
||||
|
||||
@ -115,13 +115,13 @@ export const startServer = async (configs?: ServerConfig) => {
|
||||
.then(() => {
|
||||
// Log server listening
|
||||
if (isVerbose)
|
||||
log(
|
||||
`[API]::Debug: JAN API listening at: http://${JAN_API_HOST}:${JAN_API_PORT}`
|
||||
logServer(
|
||||
`Debug: JAN API listening at: http://${JAN_API_HOST}:${JAN_API_PORT}`
|
||||
);
|
||||
});
|
||||
} catch (e) {
|
||||
// Log any errors
|
||||
if (isVerbose) log(`[API]::Error: ${e}`);
|
||||
if (isVerbose) logServer(`Error: ${e}`);
|
||||
}
|
||||
};
|
||||
|
||||
@ -131,11 +131,11 @@ export const startServer = async (configs?: ServerConfig) => {
|
||||
export const stopServer = async () => {
|
||||
try {
|
||||
// Log server stop
|
||||
if (isVerbose) log(`[API]::Debug: Server stopped`, "server.log");
|
||||
if (isVerbose) logServer(`Debug: Server stopped`);
|
||||
// Stop the server
|
||||
await server.close();
|
||||
} catch (e) {
|
||||
// Log any errors
|
||||
if (isVerbose) log(`[API]::Error: ${e}`);
|
||||
if (isVerbose) logServer(`Error: ${e}`);
|
||||
}
|
||||
};
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
import { ExtensionTypeEnum } from '@janhq/core'
|
||||
import { MonitoringExtension } from '@janhq/core'
|
||||
import { ExtensionTypeEnum, MonitoringExtension } from '@janhq/core'
|
||||
|
||||
import { useSetAtom } from 'jotai'
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { getUserSpace, openFileExplorer, joinPath } from '@janhq/core'
|
||||
import { openFileExplorer, joinPath, getJanDataFolderPath } from '@janhq/core'
|
||||
import { useAtomValue } from 'jotai'
|
||||
|
||||
import { selectedModelAtom } from '@/containers/DropdownListSidebar'
|
||||
@ -18,7 +18,7 @@ export const usePath = () => {
|
||||
return
|
||||
}
|
||||
|
||||
const userSpace = await getUserSpace()
|
||||
const userSpace = await getJanDataFolderPath()
|
||||
let filePath = undefined
|
||||
const assistantId = activeThread.assistants[0]?.assistant_id
|
||||
switch (type) {
|
||||
@ -51,7 +51,7 @@ export const usePath = () => {
|
||||
return
|
||||
}
|
||||
|
||||
const userSpace = await getUserSpace()
|
||||
const userSpace = await getJanDataFolderPath()
|
||||
let filePath = undefined
|
||||
const assistantId = activeThread.assistants[0]?.assistant_id
|
||||
switch (type) {
|
||||
|
||||
@ -1,4 +1,9 @@
|
||||
import { fs, joinPath, openFileExplorer, getUserSpace } from '@janhq/core'
|
||||
import {
|
||||
fs,
|
||||
joinPath,
|
||||
openFileExplorer,
|
||||
getJanDataFolderPath,
|
||||
} from '@janhq/core'
|
||||
|
||||
export const useServerLog = () => {
|
||||
const getServerLog = async () => {
|
||||
@ -12,8 +17,8 @@ export const useServerLog = () => {
|
||||
return logs
|
||||
}
|
||||
const openServerLog = async () => {
|
||||
const userSpace = await getUserSpace()
|
||||
const fullPath = await joinPath([userSpace, 'logs', 'server.log'])
|
||||
const janDataFolderPath = await getJanDataFolderPath()
|
||||
const fullPath = await joinPath([janDataFolderPath, 'logs', 'server.log'])
|
||||
return openFileExplorer(fullPath)
|
||||
}
|
||||
|
||||
|
||||
@ -9,7 +9,7 @@ import {
|
||||
ChangeEvent,
|
||||
} from 'react'
|
||||
|
||||
import { fs } from '@janhq/core'
|
||||
import { fs, AppConfiguration } from '@janhq/core'
|
||||
import { Switch, Button, Input } from '@janhq/uikit'
|
||||
|
||||
import ShortcutModal from '@/containers/ShortcutModal'
|
||||
@ -46,6 +46,17 @@ const Advanced = () => {
|
||||
[setPartialProxy, setProxy]
|
||||
)
|
||||
|
||||
// TODO: remove me later.
|
||||
const [currentPath, setCurrentPath] = useState('')
|
||||
|
||||
useEffect(() => {
|
||||
window.core?.api
|
||||
?.getAppConfigurations()
|
||||
?.then((appConfig: AppConfiguration) => {
|
||||
setCurrentPath(appConfig.data_folder)
|
||||
})
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
readSettings().then((settings) => {
|
||||
setGpuEnabled(settings.run_mode === 'gpu')
|
||||
@ -62,6 +73,35 @@ const Advanced = () => {
|
||||
})
|
||||
}
|
||||
|
||||
const onJanVaultDirectoryClick = async () => {
|
||||
const destFolder = await window.core?.api?.selectDirectory()
|
||||
if (destFolder) {
|
||||
console.debug(`Destination folder selected: ${destFolder}`)
|
||||
|
||||
try {
|
||||
const appConfiguration: AppConfiguration =
|
||||
await window.core?.api?.getAppConfigurations()
|
||||
const currentJanDataFolder = appConfiguration.data_folder
|
||||
if (currentJanDataFolder === destFolder) {
|
||||
console.debug(
|
||||
`Destination folder is the same as current folder. Ignore..`
|
||||
)
|
||||
return
|
||||
}
|
||||
appConfiguration.data_folder = destFolder
|
||||
|
||||
await fs.syncFile(currentJanDataFolder, destFolder)
|
||||
await window.core?.api?.updateAppConfiguration(appConfiguration)
|
||||
console.debug(
|
||||
`File sync finished from ${currentJanDataFolder} to ${destFolder}`
|
||||
)
|
||||
await window.core?.api?.relaunch()
|
||||
} catch (e) {
|
||||
console.error(`Error: ${e}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="block w-full">
|
||||
{/* CPU / GPU switching */}
|
||||
@ -192,6 +232,31 @@ const Advanced = () => {
|
||||
Clear
|
||||
</Button>
|
||||
</div>
|
||||
{experimentalFeature && (
|
||||
<div className="flex w-full items-start justify-between border-b border-border py-4 first:pt-0 last:border-none">
|
||||
<div className="w-4/5 flex-shrink-0 space-y-1.5">
|
||||
<div className="flex gap-x-2">
|
||||
<h6 className="text-sm font-semibold capitalize">
|
||||
Jan Data Folder
|
||||
</h6>
|
||||
</div>
|
||||
<p className="whitespace-pre-wrap leading-relaxed">
|
||||
Where messages, model configurations, and other user data is
|
||||
placed.
|
||||
</p>
|
||||
<p className="whitespace-pre-wrap leading-relaxed text-gray-500">
|
||||
{`${currentPath}`}
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
size="sm"
|
||||
themes="secondary"
|
||||
onClick={onJanVaultDirectoryClick}
|
||||
>
|
||||
Select
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex w-full items-start justify-between border-b border-border py-4 first:pt-0 last:border-none">
|
||||
<div className="w-4/5 flex-shrink-0 space-y-1.5">
|
||||
<div className="flex gap-x-2">
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user