feat: move log into monitoring extension (#2662)
This commit is contained in:
parent
3be0b3af65
commit
3f23de6c28
@ -1,9 +1,12 @@
|
|||||||
import { basename, isAbsolute, join, relative } from 'path'
|
import { basename, isAbsolute, join, relative } from 'path'
|
||||||
|
|
||||||
import { Processor } from './Processor'
|
import { Processor } from './Processor'
|
||||||
import { getAppConfigurations as appConfiguration, updateAppConfiguration } from '../../helper'
|
import {
|
||||||
import { log as writeLog, logServer as writeServerLog } from '../../helper/log'
|
log as writeLog,
|
||||||
import { appResourcePath } from '../../helper/path'
|
appResourcePath,
|
||||||
|
getAppConfigurations as appConfiguration,
|
||||||
|
updateAppConfiguration,
|
||||||
|
} from '../../helper'
|
||||||
|
|
||||||
export class App implements Processor {
|
export class App implements Processor {
|
||||||
observer?: Function
|
observer?: Function
|
||||||
@ -56,13 +59,6 @@ export class App implements Processor {
|
|||||||
writeLog(args)
|
writeLog(args)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Log message to log file.
|
|
||||||
*/
|
|
||||||
logServer(args: any) {
|
|
||||||
writeServerLog(args)
|
|
||||||
}
|
|
||||||
|
|
||||||
getAppConfigurations() {
|
getAppConfigurations() {
|
||||||
return appConfiguration()
|
return appConfiguration()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,11 @@
|
|||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import { getJanDataFolderPath, getJanExtensionsPath, getSystemResourceInfo } from '../../../helper'
|
import {
|
||||||
import { logServer } from '../../../helper/log'
|
getJanDataFolderPath,
|
||||||
|
getJanExtensionsPath,
|
||||||
|
getSystemResourceInfo,
|
||||||
|
log,
|
||||||
|
} from '../../../helper'
|
||||||
import { ChildProcessWithoutNullStreams, spawn } from 'child_process'
|
import { ChildProcessWithoutNullStreams, spawn } from 'child_process'
|
||||||
import { Model, ModelSettingParams, PromptTemplate } from '../../../../types'
|
import { Model, ModelSettingParams, PromptTemplate } from '../../../../types'
|
||||||
import {
|
import {
|
||||||
@ -69,7 +73,7 @@ const runModel = async (modelId: string, settingParams?: ModelSettingParams): Pr
|
|||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
|
||||||
logServer(`[NITRO]::Debug: Nitro model settings: ${JSON.stringify(nitroModelSettings)}`)
|
log(`[SERVER]::Debug: Nitro model settings: ${JSON.stringify(nitroModelSettings)}`)
|
||||||
|
|
||||||
// Convert settings.prompt_template to system_prompt, user_prompt, ai_prompt
|
// Convert settings.prompt_template to system_prompt, user_prompt, ai_prompt
|
||||||
if (modelMetadata.settings.prompt_template) {
|
if (modelMetadata.settings.prompt_template) {
|
||||||
@ -140,7 +144,7 @@ const runNitroAndLoadModel = async (modelId: string, modelSettings: NitroModelSe
|
|||||||
}
|
}
|
||||||
|
|
||||||
const spawnNitroProcess = async (): Promise<void> => {
|
const spawnNitroProcess = async (): Promise<void> => {
|
||||||
logServer(`[NITRO]::Debug: Spawning Nitro subprocess...`)
|
log(`[SERVER]::Debug: Spawning Nitro subprocess...`)
|
||||||
|
|
||||||
let binaryFolder = join(
|
let binaryFolder = join(
|
||||||
getJanExtensionsPath(),
|
getJanExtensionsPath(),
|
||||||
@ -155,8 +159,8 @@ const spawnNitroProcess = async (): Promise<void> => {
|
|||||||
|
|
||||||
const args: string[] = ['1', LOCAL_HOST, NITRO_DEFAULT_PORT.toString()]
|
const args: string[] = ['1', LOCAL_HOST, NITRO_DEFAULT_PORT.toString()]
|
||||||
// Execute the binary
|
// Execute the binary
|
||||||
logServer(
|
log(
|
||||||
`[NITRO]::Debug: Spawn nitro at path: ${executableOptions.executablePath}, and args: ${args}`
|
`[SERVER]::Debug: Spawn nitro at path: ${executableOptions.executablePath}, and args: ${args}`
|
||||||
)
|
)
|
||||||
subprocess = spawn(
|
subprocess = spawn(
|
||||||
executableOptions.executablePath,
|
executableOptions.executablePath,
|
||||||
@ -172,20 +176,20 @@ const spawnNitroProcess = async (): Promise<void> => {
|
|||||||
|
|
||||||
// Handle subprocess output
|
// Handle subprocess output
|
||||||
subprocess.stdout.on('data', (data: any) => {
|
subprocess.stdout.on('data', (data: any) => {
|
||||||
logServer(`[NITRO]::Debug: ${data}`)
|
log(`[SERVER]::Debug: ${data}`)
|
||||||
})
|
})
|
||||||
|
|
||||||
subprocess.stderr.on('data', (data: any) => {
|
subprocess.stderr.on('data', (data: any) => {
|
||||||
logServer(`[NITRO]::Error: ${data}`)
|
log(`[SERVER]::Error: ${data}`)
|
||||||
})
|
})
|
||||||
|
|
||||||
subprocess.on('close', (code: any) => {
|
subprocess.on('close', (code: any) => {
|
||||||
logServer(`[NITRO]::Debug: Nitro exited with code: ${code}`)
|
log(`[SERVER]::Debug: Nitro exited with code: ${code}`)
|
||||||
subprocess = undefined
|
subprocess = undefined
|
||||||
})
|
})
|
||||||
|
|
||||||
tcpPortUsed.waitUntilUsed(NITRO_DEFAULT_PORT, 300, 30000).then(() => {
|
tcpPortUsed.waitUntilUsed(NITRO_DEFAULT_PORT, 300, 30000).then(() => {
|
||||||
logServer(`[NITRO]::Debug: Nitro is ready`)
|
log(`[SERVER]::Debug: Nitro is ready`)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -267,7 +271,7 @@ const validateModelStatus = async (): Promise<void> => {
|
|||||||
retries: 5,
|
retries: 5,
|
||||||
retryDelay: 500,
|
retryDelay: 500,
|
||||||
}).then(async (res: Response) => {
|
}).then(async (res: Response) => {
|
||||||
logServer(`[NITRO]::Debug: Validate model state success with response ${JSON.stringify(res)}`)
|
log(`[SERVER]::Debug: Validate model state success with response ${JSON.stringify(res)}`)
|
||||||
// If the response is OK, check model_loaded status.
|
// If the response is OK, check model_loaded status.
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const body = await res.json()
|
const body = await res.json()
|
||||||
@ -282,7 +286,7 @@ const validateModelStatus = async (): Promise<void> => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const loadLLMModel = async (settings: NitroModelSettings): Promise<Response> => {
|
const loadLLMModel = async (settings: NitroModelSettings): Promise<Response> => {
|
||||||
logServer(`[NITRO]::Debug: Loading model with params ${JSON.stringify(settings)}`)
|
log(`[SERVER]::Debug: Loading model with params ${JSON.stringify(settings)}`)
|
||||||
const fetchRT = require('fetch-retry')
|
const fetchRT = require('fetch-retry')
|
||||||
const fetchRetry = fetchRT(fetch)
|
const fetchRetry = fetchRT(fetch)
|
||||||
|
|
||||||
@ -296,11 +300,11 @@ const loadLLMModel = async (settings: NitroModelSettings): Promise<Response> =>
|
|||||||
retryDelay: 500,
|
retryDelay: 500,
|
||||||
})
|
})
|
||||||
.then((res: any) => {
|
.then((res: any) => {
|
||||||
logServer(`[NITRO]::Debug: Load model success with response ${JSON.stringify(res)}`)
|
log(`[SERVER]::Debug: Load model success with response ${JSON.stringify(res)}`)
|
||||||
return Promise.resolve(res)
|
return Promise.resolve(res)
|
||||||
})
|
})
|
||||||
.catch((err: any) => {
|
.catch((err: any) => {
|
||||||
logServer(`[NITRO]::Error: Load model failed with error ${err}`)
|
log(`[SERVER]::Error: Load model failed with error ${err}`)
|
||||||
return Promise.reject(err)
|
return Promise.reject(err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -323,7 +327,7 @@ export const stopModel = async (_modelId: string) => {
|
|||||||
})
|
})
|
||||||
}, 5000)
|
}, 5000)
|
||||||
const tcpPortUsed = require('tcp-port-used')
|
const tcpPortUsed = require('tcp-port-used')
|
||||||
logServer(`[NITRO]::Debug: Request to kill Nitro`)
|
log(`[SERVER]::Debug: Request to kill Nitro`)
|
||||||
|
|
||||||
fetch(NITRO_HTTP_KILL_URL, {
|
fetch(NITRO_HTTP_KILL_URL, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
@ -337,7 +341,7 @@ export const stopModel = async (_modelId: string) => {
|
|||||||
// don't need to do anything, we still kill the subprocess
|
// don't need to do anything, we still kill the subprocess
|
||||||
})
|
})
|
||||||
.then(() => tcpPortUsed.waitUntilFree(NITRO_DEFAULT_PORT, 300, 5000))
|
.then(() => tcpPortUsed.waitUntilFree(NITRO_DEFAULT_PORT, 300, 5000))
|
||||||
.then(() => logServer(`[NITRO]::Debug: Nitro process is terminated`))
|
.then(() => log(`[SERVER]::Debug: Nitro process is terminated`))
|
||||||
.then(() =>
|
.then(() =>
|
||||||
resolve({
|
resolve({
|
||||||
message: 'Model stopped',
|
message: 'Model stopped',
|
||||||
|
|||||||
@ -150,31 +150,3 @@ export const getEngineConfiguration = async (engineId: string) => {
|
|||||||
full_url: undefined,
|
full_url: undefined,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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')
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
export * from './config'
|
export * from './config'
|
||||||
export * from './download'
|
export * from './download'
|
||||||
export * from './log'
|
export * from './logger'
|
||||||
export * from './module'
|
export * from './module'
|
||||||
export * from './path'
|
export * from './path'
|
||||||
export * from './resource'
|
export * from './resource'
|
||||||
|
|||||||
@ -1,37 +0,0 @@
|
|||||||
import fs from 'fs'
|
|
||||||
import util from 'util'
|
|
||||||
import { getAppLogPath, getServerLogPath } from './config'
|
|
||||||
|
|
||||||
export const log = (message: string) => {
|
|
||||||
const path = getAppLogPath()
|
|
||||||
if (!message.startsWith('[')) {
|
|
||||||
message = `[APP]::${message}`
|
|
||||||
}
|
|
||||||
|
|
||||||
message = `${new Date().toISOString()} ${message}`
|
|
||||||
|
|
||||||
writeLog(message, path)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const logServer = (message: string) => {
|
|
||||||
const path = getServerLogPath()
|
|
||||||
if (!message.startsWith('[')) {
|
|
||||||
message = `[SERVER]::${message}`
|
|
||||||
}
|
|
||||||
|
|
||||||
message = `${new Date().toISOString()} ${message}`
|
|
||||||
writeLog(message, path)
|
|
||||||
}
|
|
||||||
|
|
||||||
const writeLog = (message: string, logPath: string) => {
|
|
||||||
if (!fs.existsSync(logPath)) {
|
|
||||||
fs.writeFileSync(logPath, message)
|
|
||||||
} else {
|
|
||||||
const logFile = fs.createWriteStream(logPath, {
|
|
||||||
flags: 'a',
|
|
||||||
})
|
|
||||||
logFile.write(util.format(message) + '\n')
|
|
||||||
logFile.close()
|
|
||||||
console.debug(message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
81
core/src/node/helper/logger.ts
Normal file
81
core/src/node/helper/logger.ts
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
// Abstract Logger class that all loggers should extend.
|
||||||
|
export abstract class Logger {
|
||||||
|
// Each logger must have a unique name.
|
||||||
|
abstract name: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log message to log file.
|
||||||
|
* This method should be overridden by subclasses to provide specific logging behavior.
|
||||||
|
*/
|
||||||
|
abstract log(args: any): void
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoggerManager is a singleton class that manages all registered loggers.
|
||||||
|
export class LoggerManager {
|
||||||
|
// Map of registered loggers, keyed by their names.
|
||||||
|
public loggers = new Map<string, Logger>()
|
||||||
|
|
||||||
|
// Array to store logs that are queued before the loggers are registered.
|
||||||
|
queuedLogs: any[] = []
|
||||||
|
|
||||||
|
// Flag to indicate whether flushLogs is currently running.
|
||||||
|
private isFlushing = false
|
||||||
|
|
||||||
|
// Register a new logger. If a logger with the same name already exists, it will be replaced.
|
||||||
|
register(logger: Logger) {
|
||||||
|
this.loggers.set(logger.name, logger)
|
||||||
|
}
|
||||||
|
// Unregister a logger by its name.
|
||||||
|
unregister(name: string) {
|
||||||
|
this.loggers.delete(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
get(name: string) {
|
||||||
|
return this.loggers.get(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush queued logs to all registered loggers.
|
||||||
|
flushLogs() {
|
||||||
|
// If flushLogs is already running, do nothing.
|
||||||
|
if (this.isFlushing) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isFlushing = true
|
||||||
|
|
||||||
|
while (this.queuedLogs.length > 0 && this.loggers.size > 0) {
|
||||||
|
const log = this.queuedLogs.shift()
|
||||||
|
this.loggers.forEach((logger) => {
|
||||||
|
logger.log(log)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isFlushing = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log message using all registered loggers.
|
||||||
|
log(args: any) {
|
||||||
|
this.queuedLogs.push(args)
|
||||||
|
|
||||||
|
this.flushLogs()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The instance of the logger.
|
||||||
|
* If an instance doesn't exist, it creates a new one.
|
||||||
|
* This ensures that there is only one LoggerManager instance at any time.
|
||||||
|
*/
|
||||||
|
static instance(): LoggerManager {
|
||||||
|
let instance: LoggerManager | undefined = global.core?.logger
|
||||||
|
if (!instance) {
|
||||||
|
instance = new LoggerManager()
|
||||||
|
if (!global.core) global.core = {}
|
||||||
|
global.core.logger = instance
|
||||||
|
}
|
||||||
|
return instance
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const log = (...args: any) => {
|
||||||
|
LoggerManager.instance().log(args)
|
||||||
|
}
|
||||||
@ -1,11 +1,10 @@
|
|||||||
import { SystemResourceInfo } from '../../types'
|
import { SystemResourceInfo } from '../../types'
|
||||||
import { physicalCpuCount } from './config'
|
import { physicalCpuCount } from './config'
|
||||||
import { log } from './log'
|
import { log } from './logger'
|
||||||
|
|
||||||
export const getSystemResourceInfo = async (): Promise<SystemResourceInfo> => {
|
export const getSystemResourceInfo = async (): Promise<SystemResourceInfo> => {
|
||||||
const cpu = await physicalCpuCount()
|
const cpu = await physicalCpuCount()
|
||||||
const message = `[NITRO]::CPU informations - ${cpu}`
|
log(`[NITRO]::CPU informations - ${cpu}`)
|
||||||
log(message)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
numCpuPhysicalCore: cpu,
|
numCpuPhysicalCore: cpu,
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import { GpuSetting, OperatingSystemInfo } from '../miscellaneous'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Monitoring extension for system monitoring.
|
* Monitoring extension for system monitoring.
|
||||||
* @extends BaseExtension
|
* @extends BaseExtension
|
||||||
@ -14,4 +16,14 @@ export interface MonitoringInterface {
|
|||||||
* @returns {Promise<any>} A promise that resolves with the current system load.
|
* @returns {Promise<any>} A promise that resolves with the current system load.
|
||||||
*/
|
*/
|
||||||
getCurrentLoad(): Promise<any>
|
getCurrentLoad(): Promise<any>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the GPU configuration.
|
||||||
|
*/
|
||||||
|
getGpuSetting(): Promise<GpuSetting>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns information about the operating system.
|
||||||
|
*/
|
||||||
|
getOsInfo(): Promise<OperatingSystemInfo>
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,7 +24,6 @@ import { cleanUpAndQuit } from './utils/clean'
|
|||||||
import { setupExtensions } from './utils/extension'
|
import { setupExtensions } from './utils/extension'
|
||||||
import { setupCore } from './utils/setup'
|
import { setupCore } from './utils/setup'
|
||||||
import { setupReactDevTool } from './utils/dev'
|
import { setupReactDevTool } from './utils/dev'
|
||||||
import { cleanLogs } from './utils/log'
|
|
||||||
|
|
||||||
import { trayManager } from './managers/tray'
|
import { trayManager } from './managers/tray'
|
||||||
import { logSystemInfo } from './utils/system'
|
import { logSystemInfo } from './utils/system'
|
||||||
@ -75,7 +74,6 @@ app
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.then(() => cleanLogs())
|
|
||||||
|
|
||||||
app.on('second-instance', (_event, _commandLine, _workingDirectory) => {
|
app.on('second-instance', (_event, _commandLine, _workingDirectory) => {
|
||||||
windowManager.showMainWindow()
|
windowManager.showMainWindow()
|
||||||
@ -111,7 +109,6 @@ function createMainWindow() {
|
|||||||
windowManager.createMainWindow(preloadPath, startUrl)
|
windowManager.createMainWindow(preloadPath, startUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles various IPC messages from the renderer process.
|
* Handles various IPC messages from the renderer process.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -1,67 +0,0 @@
|
|||||||
import { getJanDataFolderPath } from '@janhq/core/node'
|
|
||||||
import * as fs from 'fs'
|
|
||||||
import * as path from 'path'
|
|
||||||
|
|
||||||
export function cleanLogs(
|
|
||||||
maxFileSizeBytes?: number | undefined,
|
|
||||||
daysToKeep?: number | undefined,
|
|
||||||
delayMs?: number | undefined
|
|
||||||
): void {
|
|
||||||
const size = maxFileSizeBytes ?? 1 * 1024 * 1024 // 1 MB
|
|
||||||
const days = daysToKeep ?? 7 // 7 days
|
|
||||||
const delays = delayMs ?? 10000 // 10 seconds
|
|
||||||
const logDirectory = path.join(getJanDataFolderPath(), 'logs')
|
|
||||||
|
|
||||||
// Perform log cleaning
|
|
||||||
const currentDate = new Date()
|
|
||||||
fs.readdir(logDirectory, (err, files) => {
|
|
||||||
if (err) {
|
|
||||||
console.error('Error reading log directory:', err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
files.forEach((file) => {
|
|
||||||
const filePath = path.join(logDirectory, file)
|
|
||||||
fs.stat(filePath, (err, stats) => {
|
|
||||||
if (err) {
|
|
||||||
console.error('Error getting file stats:', err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check size
|
|
||||||
if (stats.size > size) {
|
|
||||||
fs.unlink(filePath, (err) => {
|
|
||||||
if (err) {
|
|
||||||
console.error('Error deleting log file:', err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
console.debug(
|
|
||||||
`Deleted log file due to exceeding size limit: ${filePath}`
|
|
||||||
)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
// Check age
|
|
||||||
const creationDate = new Date(stats.ctime)
|
|
||||||
const daysDifference = Math.floor(
|
|
||||||
(currentDate.getTime() - creationDate.getTime()) /
|
|
||||||
(1000 * 3600 * 24)
|
|
||||||
)
|
|
||||||
if (daysDifference > days) {
|
|
||||||
fs.unlink(filePath, (err) => {
|
|
||||||
if (err) {
|
|
||||||
console.error('Error deleting log file:', err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
console.debug(`Deleted old log file: ${filePath}`)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// Schedule the next execution with doubled delays
|
|
||||||
setTimeout(() => {
|
|
||||||
cleanLogs(maxFileSizeBytes, daysToKeep, delays * 2)
|
|
||||||
}, delays)
|
|
||||||
}
|
|
||||||
@ -1,14 +1,10 @@
|
|||||||
# Jan Assistant plugin
|
# Create a Jan Extension using Typescript
|
||||||
|
|
||||||
Created using Jan app example
|
Use this template to bootstrap the creation of a TypeScript Jan extension. 🚀
|
||||||
|
|
||||||
# Create a Jan Plugin using Typescript
|
## Create Your Own Extension
|
||||||
|
|
||||||
Use this template to bootstrap the creation of a TypeScript Jan plugin. 🚀
|
To create your own extension, you can use this repository as a template! Just follow the below instructions:
|
||||||
|
|
||||||
## Create Your Own Plugin
|
|
||||||
|
|
||||||
To create your own plugin, you can use this repository as a template! Just follow the below instructions:
|
|
||||||
|
|
||||||
1. Click the Use this template button at the top of the repository
|
1. Click the Use this template button at the top of the repository
|
||||||
2. Select Create a new repository
|
2. Select Create a new repository
|
||||||
@ -18,7 +14,7 @@ To create your own plugin, you can use this repository as a template! Just follo
|
|||||||
|
|
||||||
## Initial Setup
|
## Initial Setup
|
||||||
|
|
||||||
After you've cloned the repository to your local machine or codespace, you'll need to perform some initial setup steps before you can develop your plugin.
|
After you've cloned the repository to your local machine or codespace, you'll need to perform some initial setup steps before you can develop your extension.
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
>
|
>
|
||||||
@ -43,35 +39,37 @@ After you've cloned the repository to your local machine or codespace, you'll ne
|
|||||||
|
|
||||||
1. :white_check_mark: Check your artifact
|
1. :white_check_mark: Check your artifact
|
||||||
|
|
||||||
There will be a tgz file in your plugin directory now
|
There will be a tgz file in your extension directory now
|
||||||
|
|
||||||
## Update the Plugin Metadata
|
## Update the Extension Metadata
|
||||||
|
|
||||||
The [`package.json`](package.json) file defines metadata about your plugin, such as
|
The [`package.json`](package.json) file defines metadata about your extension, such as
|
||||||
plugin name, main entry, description and version.
|
extension name, main entry, description and version.
|
||||||
|
|
||||||
When you copy this repository, update `package.json` with the name, description for your plugin.
|
When you copy this repository, update `package.json` with the name, description for your extension.
|
||||||
|
|
||||||
## Update the Plugin Code
|
## Update the Extension Code
|
||||||
|
|
||||||
The [`src/`](./src/) directory is the heart of your plugin! This contains the
|
The [`src/`](./src/) directory is the heart of your extension! This contains the
|
||||||
source code that will be run when your plugin extension functions are invoked. You can replace the
|
source code that will be run when your extension functions are invoked. You can replace the
|
||||||
contents of this directory with your own code.
|
contents of this directory with your own code.
|
||||||
|
|
||||||
There are a few things to keep in mind when writing your plugin code:
|
There are a few things to keep in mind when writing your extension code:
|
||||||
|
|
||||||
- Most Jan Plugin Extension functions are processed asynchronously.
|
- Most Jan Extension functions are processed asynchronously.
|
||||||
In `index.ts`, you will see that the extension function will return a `Promise<any>`.
|
In `index.ts`, you will see that the extension function will return a `Promise<any>`.
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { core } from "@janhq/core";
|
import { events, MessageEvent, MessageRequest } from '@janhq/core'
|
||||||
|
|
||||||
function onStart(): Promise<any> {
|
function onStart(): Promise<any> {
|
||||||
return core.invokePluginFunc(MODULE_PATH, "run", 0);
|
return events.on(MessageEvent.OnMessageSent, (data: MessageRequest) =>
|
||||||
|
this.inference(data)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
For more information about the Jan Plugin Core module, see the
|
For more information about the Jan Extension Core module, see the
|
||||||
[documentation](https://github.com/janhq/jan/blob/main/core/README.md).
|
[documentation](https://github.com/janhq/jan/blob/main/core/README.md).
|
||||||
|
|
||||||
So, what are you waiting for? Go ahead and start customizing your plugin!
|
So, what are you waiting for? Go ahead and start customizing your extension!
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
# Create a Jan Plugin using Typescript
|
# Create a Jan Extension using Typescript
|
||||||
|
|
||||||
Use this template to bootstrap the creation of a TypeScript Jan plugin. 🚀
|
Use this template to bootstrap the creation of a TypeScript Jan extension. 🚀
|
||||||
|
|
||||||
## Create Your Own Plugin
|
## Create Your Own Extension
|
||||||
|
|
||||||
To create your own plugin, you can use this repository as a template! Just follow the below instructions:
|
To create your own extension, you can use this repository as a template! Just follow the below instructions:
|
||||||
|
|
||||||
1. Click the Use this template button at the top of the repository
|
1. Click the Use this template button at the top of the repository
|
||||||
2. Select Create a new repository
|
2. Select Create a new repository
|
||||||
@ -14,7 +14,7 @@ To create your own plugin, you can use this repository as a template! Just follo
|
|||||||
|
|
||||||
## Initial Setup
|
## Initial Setup
|
||||||
|
|
||||||
After you've cloned the repository to your local machine or codespace, you'll need to perform some initial setup steps before you can develop your plugin.
|
After you've cloned the repository to your local machine or codespace, you'll need to perform some initial setup steps before you can develop your extension.
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
>
|
>
|
||||||
@ -39,35 +39,37 @@ After you've cloned the repository to your local machine or codespace, you'll ne
|
|||||||
|
|
||||||
1. :white_check_mark: Check your artifact
|
1. :white_check_mark: Check your artifact
|
||||||
|
|
||||||
There will be a tgz file in your plugin directory now
|
There will be a tgz file in your extension directory now
|
||||||
|
|
||||||
## Update the Plugin Metadata
|
## Update the Extension Metadata
|
||||||
|
|
||||||
The [`package.json`](package.json) file defines metadata about your plugin, such as
|
The [`package.json`](package.json) file defines metadata about your extension, such as
|
||||||
plugin name, main entry, description and version.
|
extension name, main entry, description and version.
|
||||||
|
|
||||||
When you copy this repository, update `package.json` with the name, description for your plugin.
|
When you copy this repository, update `package.json` with the name, description for your extension.
|
||||||
|
|
||||||
## Update the Plugin Code
|
## Update the Extension Code
|
||||||
|
|
||||||
The [`src/`](./src/) directory is the heart of your plugin! This contains the
|
The [`src/`](./src/) directory is the heart of your extension! This contains the
|
||||||
source code that will be run when your plugin extension functions are invoked. You can replace the
|
source code that will be run when your extension functions are invoked. You can replace the
|
||||||
contents of this directory with your own code.
|
contents of this directory with your own code.
|
||||||
|
|
||||||
There are a few things to keep in mind when writing your plugin code:
|
There are a few things to keep in mind when writing your extension code:
|
||||||
|
|
||||||
- Most Jan Plugin Extension functions are processed asynchronously.
|
- Most Jan Extension functions are processed asynchronously.
|
||||||
In `index.ts`, you will see that the extension function will return a `Promise<any>`.
|
In `index.ts`, you will see that the extension function will return a `Promise<any>`.
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { core } from "@janhq/core";
|
import { events, MessageEvent, MessageRequest } from '@janhq/core'
|
||||||
|
|
||||||
function onStart(): Promise<any> {
|
function onStart(): Promise<any> {
|
||||||
return core.invokePluginFunc(MODULE_PATH, "run", 0);
|
return events.on(MessageEvent.OnMessageSent, (data: MessageRequest) =>
|
||||||
|
this.inference(data)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
For more information about the Jan Plugin Core module, see the
|
For more information about the Jan Extension Core module, see the
|
||||||
[documentation](https://github.com/janhq/jan/blob/main/core/README.md).
|
[documentation](https://github.com/janhq/jan/blob/main/core/README.md).
|
||||||
|
|
||||||
So, what are you waiting for? Go ahead and start customizing your plugin!
|
So, what are you waiting for? Go ahead and start customizing your extension!
|
||||||
|
|||||||
@ -1,14 +1,10 @@
|
|||||||
# Jan inference plugin
|
# Create a Jan Extension using Typescript
|
||||||
|
|
||||||
Created using Jan app example
|
Use this template to bootstrap the creation of a TypeScript Jan extension. 🚀
|
||||||
|
|
||||||
# Create a Jan Plugin using Typescript
|
## Create Your Own Extension
|
||||||
|
|
||||||
Use this template to bootstrap the creation of a TypeScript Jan plugin. 🚀
|
To create your own extension, you can use this repository as a template! Just follow the below instructions:
|
||||||
|
|
||||||
## Create Your Own Plugin
|
|
||||||
|
|
||||||
To create your own plugin, you can use this repository as a template! Just follow the below instructions:
|
|
||||||
|
|
||||||
1. Click the Use this template button at the top of the repository
|
1. Click the Use this template button at the top of the repository
|
||||||
2. Select Create a new repository
|
2. Select Create a new repository
|
||||||
@ -18,7 +14,7 @@ To create your own plugin, you can use this repository as a template! Just follo
|
|||||||
|
|
||||||
## Initial Setup
|
## Initial Setup
|
||||||
|
|
||||||
After you've cloned the repository to your local machine or codespace, you'll need to perform some initial setup steps before you can develop your plugin.
|
After you've cloned the repository to your local machine or codespace, you'll need to perform some initial setup steps before you can develop your extension.
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
>
|
>
|
||||||
@ -43,36 +39,37 @@ After you've cloned the repository to your local machine or codespace, you'll ne
|
|||||||
|
|
||||||
1. :white_check_mark: Check your artifact
|
1. :white_check_mark: Check your artifact
|
||||||
|
|
||||||
There will be a tgz file in your plugin directory now
|
There will be a tgz file in your extension directory now
|
||||||
|
|
||||||
## Update the Plugin Metadata
|
## Update the Extension Metadata
|
||||||
|
|
||||||
The [`package.json`](package.json) file defines metadata about your plugin, such as
|
The [`package.json`](package.json) file defines metadata about your extension, such as
|
||||||
plugin name, main entry, description and version.
|
extension name, main entry, description and version.
|
||||||
|
|
||||||
When you copy this repository, update `package.json` with the name, description for your plugin.
|
When you copy this repository, update `package.json` with the name, description for your extension.
|
||||||
|
|
||||||
## Update the Plugin Code
|
## Update the Extension Code
|
||||||
|
|
||||||
The [`src/`](./src/) directory is the heart of your plugin! This contains the
|
The [`src/`](./src/) directory is the heart of your extension! This contains the
|
||||||
source code that will be run when your plugin extension functions are invoked. You can replace the
|
source code that will be run when your extension functions are invoked. You can replace the
|
||||||
contents of this directory with your own code.
|
contents of this directory with your own code.
|
||||||
|
|
||||||
There are a few things to keep in mind when writing your plugin code:
|
There are a few things to keep in mind when writing your extension code:
|
||||||
|
|
||||||
- Most Jan Plugin Extension functions are processed asynchronously.
|
- Most Jan Extension functions are processed asynchronously.
|
||||||
In `index.ts`, you will see that the extension function will return a `Promise<any>`.
|
In `index.ts`, you will see that the extension function will return a `Promise<any>`.
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { core } from "@janhq/core";
|
import { events, MessageEvent, MessageRequest } from '@janhq/core'
|
||||||
|
|
||||||
function onStart(): Promise<any> {
|
function onStart(): Promise<any> {
|
||||||
return core.invokePluginFunc(MODULE_PATH, "run", 0);
|
return events.on(MessageEvent.OnMessageSent, (data: MessageRequest) =>
|
||||||
|
this.inference(data)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
For more information about the Jan Plugin Core module, see the
|
For more information about the Jan Extension Core module, see the
|
||||||
[documentation](https://github.com/janhq/jan/blob/main/core/README.md).
|
[documentation](https://github.com/janhq/jan/blob/main/core/README.md).
|
||||||
|
|
||||||
So, what are you waiting for? Go ahead and start customizing your plugin!
|
So, what are you waiting for? Go ahead and start customizing your extension!
|
||||||
|
|
||||||
|
|||||||
@ -1,14 +1,10 @@
|
|||||||
# Jan inference plugin
|
# Create a Jan Extension using Typescript
|
||||||
|
|
||||||
Created using Jan app example
|
Use this template to bootstrap the creation of a TypeScript Jan extension. 🚀
|
||||||
|
|
||||||
# Create a Jan Plugin using Typescript
|
## Create Your Own Extension
|
||||||
|
|
||||||
Use this template to bootstrap the creation of a TypeScript Jan plugin. 🚀
|
To create your own extension, you can use this repository as a template! Just follow the below instructions:
|
||||||
|
|
||||||
## Create Your Own Plugin
|
|
||||||
|
|
||||||
To create your own plugin, you can use this repository as a template! Just follow the below instructions:
|
|
||||||
|
|
||||||
1. Click the Use this template button at the top of the repository
|
1. Click the Use this template button at the top of the repository
|
||||||
2. Select Create a new repository
|
2. Select Create a new repository
|
||||||
@ -18,7 +14,7 @@ To create your own plugin, you can use this repository as a template! Just follo
|
|||||||
|
|
||||||
## Initial Setup
|
## Initial Setup
|
||||||
|
|
||||||
After you've cloned the repository to your local machine or codespace, you'll need to perform some initial setup steps before you can develop your plugin.
|
After you've cloned the repository to your local machine or codespace, you'll need to perform some initial setup steps before you can develop your extension.
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
>
|
>
|
||||||
@ -43,35 +39,37 @@ After you've cloned the repository to your local machine or codespace, you'll ne
|
|||||||
|
|
||||||
1. :white_check_mark: Check your artifact
|
1. :white_check_mark: Check your artifact
|
||||||
|
|
||||||
There will be a tgz file in your plugin directory now
|
There will be a tgz file in your extension directory now
|
||||||
|
|
||||||
## Update the Plugin Metadata
|
## Update the Extension Metadata
|
||||||
|
|
||||||
The [`package.json`](package.json) file defines metadata about your plugin, such as
|
The [`package.json`](package.json) file defines metadata about your extension, such as
|
||||||
plugin name, main entry, description and version.
|
extension name, main entry, description and version.
|
||||||
|
|
||||||
When you copy this repository, update `package.json` with the name, description for your plugin.
|
When you copy this repository, update `package.json` with the name, description for your extension.
|
||||||
|
|
||||||
## Update the Plugin Code
|
## Update the Extension Code
|
||||||
|
|
||||||
The [`src/`](./src/) directory is the heart of your plugin! This contains the
|
The [`src/`](./src/) directory is the heart of your extension! This contains the
|
||||||
source code that will be run when your plugin extension functions are invoked. You can replace the
|
source code that will be run when your extension functions are invoked. You can replace the
|
||||||
contents of this directory with your own code.
|
contents of this directory with your own code.
|
||||||
|
|
||||||
There are a few things to keep in mind when writing your plugin code:
|
There are a few things to keep in mind when writing your extension code:
|
||||||
|
|
||||||
- Most Jan Plugin Extension functions are processed asynchronously.
|
- Most Jan Extension functions are processed asynchronously.
|
||||||
In `index.ts`, you will see that the extension function will return a `Promise<any>`.
|
In `index.ts`, you will see that the extension function will return a `Promise<any>`.
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { core } from '@janhq/core'
|
import { events, MessageEvent, MessageRequest } from '@janhq/core'
|
||||||
|
|
||||||
function onStart(): Promise<any> {
|
function onStart(): Promise<any> {
|
||||||
return core.invokePluginFunc(MODULE_PATH, 'run', 0)
|
return events.on(MessageEvent.OnMessageSent, (data: MessageRequest) =>
|
||||||
|
this.inference(data)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
For more information about the Jan Plugin Core module, see the
|
For more information about the Jan Extension Core module, see the
|
||||||
[documentation](https://github.com/janhq/jan/blob/main/core/README.md).
|
[documentation](https://github.com/janhq/jan/blob/main/core/README.md).
|
||||||
|
|
||||||
So, what are you waiting for? Go ahead and start customizing your plugin!
|
So, what are you waiting for? Go ahead and start customizing your extension!
|
||||||
|
|||||||
@ -1,14 +1,10 @@
|
|||||||
# Jan inference plugin
|
# Create a Jan Extension using Typescript
|
||||||
|
|
||||||
Created using Jan app example
|
Use this template to bootstrap the creation of a TypeScript Jan extension. 🚀
|
||||||
|
|
||||||
# Create a Jan Plugin using Typescript
|
## Create Your Own Extension
|
||||||
|
|
||||||
Use this template to bootstrap the creation of a TypeScript Jan plugin. 🚀
|
To create your own extension, you can use this repository as a template! Just follow the below instructions:
|
||||||
|
|
||||||
## Create Your Own Plugin
|
|
||||||
|
|
||||||
To create your own plugin, you can use this repository as a template! Just follow the below instructions:
|
|
||||||
|
|
||||||
1. Click the Use this template button at the top of the repository
|
1. Click the Use this template button at the top of the repository
|
||||||
2. Select Create a new repository
|
2. Select Create a new repository
|
||||||
@ -18,7 +14,7 @@ To create your own plugin, you can use this repository as a template! Just follo
|
|||||||
|
|
||||||
## Initial Setup
|
## Initial Setup
|
||||||
|
|
||||||
After you've cloned the repository to your local machine or codespace, you'll need to perform some initial setup steps before you can develop your plugin.
|
After you've cloned the repository to your local machine or codespace, you'll need to perform some initial setup steps before you can develop your extension.
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
>
|
>
|
||||||
@ -43,36 +39,37 @@ After you've cloned the repository to your local machine or codespace, you'll ne
|
|||||||
|
|
||||||
1. :white_check_mark: Check your artifact
|
1. :white_check_mark: Check your artifact
|
||||||
|
|
||||||
There will be a tgz file in your plugin directory now
|
There will be a tgz file in your extension directory now
|
||||||
|
|
||||||
## Update the Plugin Metadata
|
## Update the Extension Metadata
|
||||||
|
|
||||||
The [`package.json`](package.json) file defines metadata about your plugin, such as
|
The [`package.json`](package.json) file defines metadata about your extension, such as
|
||||||
plugin name, main entry, description and version.
|
extension name, main entry, description and version.
|
||||||
|
|
||||||
When you copy this repository, update `package.json` with the name, description for your plugin.
|
When you copy this repository, update `package.json` with the name, description for your extension.
|
||||||
|
|
||||||
## Update the Plugin Code
|
## Update the Extension Code
|
||||||
|
|
||||||
The [`src/`](./src/) directory is the heart of your plugin! This contains the
|
The [`src/`](./src/) directory is the heart of your extension! This contains the
|
||||||
source code that will be run when your plugin extension functions are invoked. You can replace the
|
source code that will be run when your extension functions are invoked. You can replace the
|
||||||
contents of this directory with your own code.
|
contents of this directory with your own code.
|
||||||
|
|
||||||
There are a few things to keep in mind when writing your plugin code:
|
There are a few things to keep in mind when writing your extension code:
|
||||||
|
|
||||||
- Most Jan Plugin Extension functions are processed asynchronously.
|
- Most Jan Extension functions are processed asynchronously.
|
||||||
In `index.ts`, you will see that the extension function will return a `Promise<any>`.
|
In `index.ts`, you will see that the extension function will return a `Promise<any>`.
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { core } from "@janhq/core";
|
import { events, MessageEvent, MessageRequest } from '@janhq/core'
|
||||||
|
|
||||||
function onStart(): Promise<any> {
|
function onStart(): Promise<any> {
|
||||||
return core.invokePluginFunc(MODULE_PATH, "run", 0);
|
return events.on(MessageEvent.OnMessageSent, (data: MessageRequest) =>
|
||||||
|
this.inference(data)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
For more information about the Jan Plugin Core module, see the
|
For more information about the Jan Extension Core module, see the
|
||||||
[documentation](https://github.com/janhq/jan/blob/main/core/README.md).
|
[documentation](https://github.com/janhq/jan/blob/main/core/README.md).
|
||||||
|
|
||||||
So, what are you waiting for? Go ahead and start customizing your plugin!
|
So, what are you waiting for? Go ahead and start customizing your extension!
|
||||||
|
|
||||||
|
|||||||
@ -1,14 +1,10 @@
|
|||||||
# Jan Model Management plugin
|
# Create a Jan Extension using Typescript
|
||||||
|
|
||||||
Created using Jan app example
|
Use this template to bootstrap the creation of a TypeScript Jan extension. 🚀
|
||||||
|
|
||||||
# Create a Jan Plugin using Typescript
|
## Create Your Own Extension
|
||||||
|
|
||||||
Use this template to bootstrap the creation of a TypeScript Jan plugin. 🚀
|
To create your own extension, you can use this repository as a template! Just follow the below instructions:
|
||||||
|
|
||||||
## Create Your Own Plugin
|
|
||||||
|
|
||||||
To create your own plugin, you can use this repository as a template! Just follow the below instructions:
|
|
||||||
|
|
||||||
1. Click the Use this template button at the top of the repository
|
1. Click the Use this template button at the top of the repository
|
||||||
2. Select Create a new repository
|
2. Select Create a new repository
|
||||||
@ -18,7 +14,7 @@ To create your own plugin, you can use this repository as a template! Just follo
|
|||||||
|
|
||||||
## Initial Setup
|
## Initial Setup
|
||||||
|
|
||||||
After you've cloned the repository to your local machine or codespace, you'll need to perform some initial setup steps before you can develop your plugin.
|
After you've cloned the repository to your local machine or codespace, you'll need to perform some initial setup steps before you can develop your extension.
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
>
|
>
|
||||||
@ -43,36 +39,37 @@ After you've cloned the repository to your local machine or codespace, you'll ne
|
|||||||
|
|
||||||
1. :white_check_mark: Check your artifact
|
1. :white_check_mark: Check your artifact
|
||||||
|
|
||||||
There will be a tgz file in your plugin directory now
|
There will be a tgz file in your extension directory now
|
||||||
|
|
||||||
## Update the Plugin Metadata
|
## Update the Extension Metadata
|
||||||
|
|
||||||
The [`package.json`](package.json) file defines metadata about your plugin, such as
|
The [`package.json`](package.json) file defines metadata about your extension, such as
|
||||||
plugin name, main entry, description and version.
|
extension name, main entry, description and version.
|
||||||
|
|
||||||
When you copy this repository, update `package.json` with the name, description for your plugin.
|
When you copy this repository, update `package.json` with the name, description for your extension.
|
||||||
|
|
||||||
## Update the Plugin Code
|
## Update the Extension Code
|
||||||
|
|
||||||
The [`src/`](./src/) directory is the heart of your plugin! This contains the
|
The [`src/`](./src/) directory is the heart of your extension! This contains the
|
||||||
source code that will be run when your plugin extension functions are invoked. You can replace the
|
source code that will be run when your extension functions are invoked. You can replace the
|
||||||
contents of this directory with your own code.
|
contents of this directory with your own code.
|
||||||
|
|
||||||
There are a few things to keep in mind when writing your plugin code:
|
There are a few things to keep in mind when writing your extension code:
|
||||||
|
|
||||||
- Most Jan Plugin Extension functions are processed asynchronously.
|
- Most Jan Extension functions are processed asynchronously.
|
||||||
In `index.ts`, you will see that the extension function will return a `Promise<any>`.
|
In `index.ts`, you will see that the extension function will return a `Promise<any>`.
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { core } from "@janhq/core";
|
import { events, MessageEvent, MessageRequest } from '@janhq/core'
|
||||||
|
|
||||||
function onStart(): Promise<any> {
|
function onStart(): Promise<any> {
|
||||||
return core.invokePluginFunc(MODULE_PATH, "run", 0);
|
return events.on(MessageEvent.OnMessageSent, (data: MessageRequest) =>
|
||||||
|
this.inference(data)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
For more information about the Jan Plugin Core module, see the
|
For more information about the Jan Extension Core module, see the
|
||||||
[documentation](https://github.com/janhq/jan/blob/main/core/README.md).
|
[documentation](https://github.com/janhq/jan/blob/main/core/README.md).
|
||||||
|
|
||||||
So, what are you waiting for? Go ahead and start customizing your plugin!
|
So, what are you waiting for? Go ahead and start customizing your extension!
|
||||||
|
|
||||||
|
|||||||
@ -1,14 +1,10 @@
|
|||||||
# Jan Monitoring plugin
|
# Create a Jan Extension using Typescript
|
||||||
|
|
||||||
Created using Jan app example
|
Use this template to bootstrap the creation of a TypeScript Jan extension. 🚀
|
||||||
|
|
||||||
# Create a Jan Plugin using Typescript
|
## Create Your Own Extension
|
||||||
|
|
||||||
Use this template to bootstrap the creation of a TypeScript Jan plugin. 🚀
|
To create your own extension, you can use this repository as a template! Just follow the below instructions:
|
||||||
|
|
||||||
## Create Your Own Plugin
|
|
||||||
|
|
||||||
To create your own plugin, you can use this repository as a template! Just follow the below instructions:
|
|
||||||
|
|
||||||
1. Click the Use this template button at the top of the repository
|
1. Click the Use this template button at the top of the repository
|
||||||
2. Select Create a new repository
|
2. Select Create a new repository
|
||||||
@ -18,7 +14,7 @@ To create your own plugin, you can use this repository as a template! Just follo
|
|||||||
|
|
||||||
## Initial Setup
|
## Initial Setup
|
||||||
|
|
||||||
After you've cloned the repository to your local machine or codespace, you'll need to perform some initial setup steps before you can develop your plugin.
|
After you've cloned the repository to your local machine or codespace, you'll need to perform some initial setup steps before you can develop your extension.
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
>
|
>
|
||||||
@ -43,36 +39,37 @@ After you've cloned the repository to your local machine or codespace, you'll ne
|
|||||||
|
|
||||||
1. :white_check_mark: Check your artifact
|
1. :white_check_mark: Check your artifact
|
||||||
|
|
||||||
There will be a tgz file in your plugin directory now
|
There will be a tgz file in your extension directory now
|
||||||
|
|
||||||
## Update the Plugin Metadata
|
## Update the Extension Metadata
|
||||||
|
|
||||||
The [`package.json`](package.json) file defines metadata about your plugin, such as
|
The [`package.json`](package.json) file defines metadata about your extension, such as
|
||||||
plugin name, main entry, description and version.
|
extension name, main entry, description and version.
|
||||||
|
|
||||||
When you copy this repository, update `package.json` with the name, description for your plugin.
|
When you copy this repository, update `package.json` with the name, description for your extension.
|
||||||
|
|
||||||
## Update the Plugin Code
|
## Update the Extension Code
|
||||||
|
|
||||||
The [`src/`](./src/) directory is the heart of your plugin! This contains the
|
The [`src/`](./src/) directory is the heart of your extension! This contains the
|
||||||
source code that will be run when your plugin extension functions are invoked. You can replace the
|
source code that will be run when your extension functions are invoked. You can replace the
|
||||||
contents of this directory with your own code.
|
contents of this directory with your own code.
|
||||||
|
|
||||||
There are a few things to keep in mind when writing your plugin code:
|
There are a few things to keep in mind when writing your extension code:
|
||||||
|
|
||||||
- Most Jan Plugin Extension functions are processed asynchronously.
|
- Most Jan Extension functions are processed asynchronously.
|
||||||
In `index.ts`, you will see that the extension function will return a `Promise<any>`.
|
In `index.ts`, you will see that the extension function will return a `Promise<any>`.
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { core } from "@janhq/core";
|
import { events, MessageEvent, MessageRequest } from '@janhq/core'
|
||||||
|
|
||||||
function onStart(): Promise<any> {
|
function onStart(): Promise<any> {
|
||||||
return core.invokePluginFunc(MODULE_PATH, "run", 0);
|
return events.on(MessageEvent.OnMessageSent, (data: MessageRequest) =>
|
||||||
|
this.inference(data)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
For more information about the Jan Plugin Core module, see the
|
For more information about the Jan Extension Core module, see the
|
||||||
[documentation](https://github.com/janhq/jan/blob/main/core/README.md).
|
[documentation](https://github.com/janhq/jan/blob/main/core/README.md).
|
||||||
|
|
||||||
So, what are you waiting for? Go ahead and start customizing your plugin!
|
So, what are you waiting for? Go ahead and start customizing your extension!
|
||||||
|
|
||||||
|
|||||||
21
extensions/monitoring-extension/resources/settings.json
Normal file
21
extensions/monitoring-extension/resources/settings.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"key": "log-enabled",
|
||||||
|
"title": "App Logging Enabled",
|
||||||
|
"description": "We recommend enabling this setting to help us improve the app. Your data will be kept private on your computer, and you can opt out at any time.",
|
||||||
|
"controllerType": "checkbox",
|
||||||
|
"controllerProps": {
|
||||||
|
"value": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "log-cleaning-interval",
|
||||||
|
"title": "Log Cleaning Interval",
|
||||||
|
"description": "Log cleaning interval in milliseconds.",
|
||||||
|
"controllerType": "input",
|
||||||
|
"controllerProps": {
|
||||||
|
"value": "120000",
|
||||||
|
"placeholder": "Interval in milliseconds. E.g. 120000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
@ -4,6 +4,7 @@ import sourceMaps from 'rollup-plugin-sourcemaps'
|
|||||||
import typescript from 'rollup-plugin-typescript2'
|
import typescript from 'rollup-plugin-typescript2'
|
||||||
import json from '@rollup/plugin-json'
|
import json from '@rollup/plugin-json'
|
||||||
import replace from '@rollup/plugin-replace'
|
import replace from '@rollup/plugin-replace'
|
||||||
|
const settingJson = require('./resources/settings.json')
|
||||||
const packageJson = require('./package.json')
|
const packageJson = require('./package.json')
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
@ -19,6 +20,7 @@ export default [
|
|||||||
replace({
|
replace({
|
||||||
preventAssignment: true,
|
preventAssignment: true,
|
||||||
NODE: JSON.stringify(`${packageJson.name}/${packageJson.node}`),
|
NODE: JSON.stringify(`${packageJson.name}/${packageJson.node}`),
|
||||||
|
SETTINGS: JSON.stringify(settingJson),
|
||||||
}),
|
}),
|
||||||
// Allow json resolution
|
// Allow json resolution
|
||||||
json(),
|
json(),
|
||||||
|
|||||||
@ -1,27 +1,63 @@
|
|||||||
import {
|
import {
|
||||||
GpuSetting,
|
GpuSetting,
|
||||||
MonitoringExtension,
|
MonitoringExtension,
|
||||||
|
MonitoringInterface,
|
||||||
OperatingSystemInfo,
|
OperatingSystemInfo,
|
||||||
executeOnMain,
|
executeOnMain,
|
||||||
} from '@janhq/core'
|
} from '@janhq/core'
|
||||||
|
|
||||||
|
declare const SETTINGS: Array<any>
|
||||||
|
|
||||||
|
enum Settings {
|
||||||
|
logEnabled = 'log-enabled',
|
||||||
|
logCleaningInterval = 'log-cleaning-interval',
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* JanMonitoringExtension is a extension that provides system monitoring functionality.
|
* JanMonitoringExtension is a extension that provides system monitoring functionality.
|
||||||
* It implements the MonitoringExtension interface from the @janhq/core package.
|
* It implements the MonitoringExtension interface from the @janhq/core package.
|
||||||
*/
|
*/
|
||||||
export default class JanMonitoringExtension extends MonitoringExtension {
|
export default class JanMonitoringExtension
|
||||||
|
extends MonitoringExtension
|
||||||
|
implements MonitoringInterface
|
||||||
|
{
|
||||||
/**
|
/**
|
||||||
* Called when the extension is loaded.
|
* Called when the extension is loaded.
|
||||||
*/
|
*/
|
||||||
async onLoad() {
|
async onLoad() {
|
||||||
|
// Register extension settings
|
||||||
|
this.registerSettings(SETTINGS)
|
||||||
|
|
||||||
|
const logEnabled = await this.getSetting<boolean>(Settings.logEnabled, true)
|
||||||
|
const logCleaningInterval = parseInt(
|
||||||
|
await this.getSetting<string>(Settings.logCleaningInterval, '120000')
|
||||||
|
)
|
||||||
|
// Register File Logger provided by this extension
|
||||||
|
await executeOnMain(NODE, 'registerLogger', {
|
||||||
|
logEnabled,
|
||||||
|
logCleaningInterval: isNaN(logCleaningInterval)
|
||||||
|
? 120000
|
||||||
|
: logCleaningInterval,
|
||||||
|
})
|
||||||
|
|
||||||
// Attempt to fetch nvidia info
|
// Attempt to fetch nvidia info
|
||||||
await executeOnMain(NODE, 'updateNvidiaInfo')
|
await executeOnMain(NODE, 'updateNvidiaInfo')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onSettingUpdate<T>(key: string, value: T): void {
|
||||||
|
if (key === Settings.logEnabled) {
|
||||||
|
executeOnMain(NODE, 'updateLogger', { logEnabled: value })
|
||||||
|
} else if (key === Settings.logCleaningInterval) {
|
||||||
|
executeOnMain(NODE, 'updateLogger', { logCleaningInterval: value })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the extension is unloaded.
|
* Called when the extension is unloaded.
|
||||||
*/
|
*/
|
||||||
onUnload(): void {}
|
onUnload(): void {
|
||||||
|
// Register File Logger provided by this extension
|
||||||
|
executeOnMain(NODE, 'unregisterLogger')
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the GPU configuration.
|
* Returns the GPU configuration.
|
||||||
@ -47,6 +83,10 @@ export default class JanMonitoringExtension extends MonitoringExtension {
|
|||||||
return executeOnMain(NODE, 'getCurrentLoad')
|
return executeOnMain(NODE, 'getCurrentLoad')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns information about the OS
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
getOsInfo(): Promise<OperatingSystemInfo> {
|
getOsInfo(): Promise<OperatingSystemInfo> {
|
||||||
return executeOnMain(NODE, 'getOsInfo')
|
return executeOnMain(NODE, 'getOsInfo')
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
GpuSetting,
|
GpuSetting,
|
||||||
GpuSettingInfo,
|
GpuSettingInfo,
|
||||||
|
LoggerManager,
|
||||||
OperatingSystemInfo,
|
OperatingSystemInfo,
|
||||||
ResourceInfo,
|
ResourceInfo,
|
||||||
SupportedPlatforms,
|
SupportedPlatforms,
|
||||||
@ -12,6 +13,7 @@ import { exec } from 'child_process'
|
|||||||
import { writeFileSync, existsSync, readFileSync, mkdirSync } from 'fs'
|
import { writeFileSync, existsSync, readFileSync, mkdirSync } from 'fs'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import os from 'os'
|
import os from 'os'
|
||||||
|
import { FileLogger } from './logger'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Path to the settings directory
|
* Path to the settings directory
|
||||||
@ -346,3 +348,22 @@ export const getOsInfo = (): OperatingSystemInfo => {
|
|||||||
|
|
||||||
return osInfo
|
return osInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const registerLogger = ({ logEnabled, logCleaningInterval }) => {
|
||||||
|
const logger = new FileLogger(logEnabled, logCleaningInterval)
|
||||||
|
LoggerManager.instance().register(logger)
|
||||||
|
logger.cleanLogs()
|
||||||
|
}
|
||||||
|
|
||||||
|
export const unregisterLogger = () => {
|
||||||
|
LoggerManager.instance().unregister('file')
|
||||||
|
}
|
||||||
|
|
||||||
|
export const updateLogger = ({ logEnabled, logCleaningInterval }) => {
|
||||||
|
const logger = LoggerManager.instance().loggers.get('file') as FileLogger
|
||||||
|
if (logger && logEnabled !== undefined) logger.logEnabled = logEnabled
|
||||||
|
if (logger && logCleaningInterval)
|
||||||
|
logger.logCleaningInterval = logCleaningInterval
|
||||||
|
// Rerun
|
||||||
|
logger && logger.cleanLogs()
|
||||||
|
}
|
||||||
|
|||||||
138
extensions/monitoring-extension/src/node/logger.ts
Normal file
138
extensions/monitoring-extension/src/node/logger.ts
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
import fs from 'fs'
|
||||||
|
import util from 'util'
|
||||||
|
import {
|
||||||
|
getAppConfigurations,
|
||||||
|
getJanDataFolderPath,
|
||||||
|
Logger,
|
||||||
|
} from '@janhq/core/node'
|
||||||
|
import path, { join } from 'path'
|
||||||
|
|
||||||
|
export class FileLogger extends Logger {
|
||||||
|
name = 'file'
|
||||||
|
logCleaningInterval: number = 120000
|
||||||
|
timeout: NodeJS.Timeout | null = null
|
||||||
|
appLogPath: string = './'
|
||||||
|
logEnabled: boolean = true
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
logEnabled: boolean = true,
|
||||||
|
logCleaningInterval: number = 120000
|
||||||
|
) {
|
||||||
|
super()
|
||||||
|
this.logEnabled = logEnabled
|
||||||
|
if (logCleaningInterval) this.logCleaningInterval = logCleaningInterval
|
||||||
|
|
||||||
|
const appConfigurations = getAppConfigurations()
|
||||||
|
const logFolderPath = join(appConfigurations.data_folder, 'logs')
|
||||||
|
if (!fs.existsSync(logFolderPath)) {
|
||||||
|
fs.mkdirSync(logFolderPath, { recursive: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
this.appLogPath = join(logFolderPath, 'app.log')
|
||||||
|
}
|
||||||
|
|
||||||
|
log(args: any) {
|
||||||
|
if (!this.logEnabled) return
|
||||||
|
let message = args[0]
|
||||||
|
const scope = args[1]
|
||||||
|
if (!message) return
|
||||||
|
const path = this.appLogPath
|
||||||
|
if (!scope && !message.startsWith('[')) {
|
||||||
|
message = `[APP]::${message}`
|
||||||
|
} else if (scope) {
|
||||||
|
message = `${scope}::${message}`
|
||||||
|
}
|
||||||
|
|
||||||
|
message = `${new Date().toISOString()} ${message}`
|
||||||
|
|
||||||
|
writeLog(message, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanLogs(
|
||||||
|
maxFileSizeBytes?: number | undefined,
|
||||||
|
daysToKeep?: number | undefined
|
||||||
|
): void {
|
||||||
|
// clear existing timeout
|
||||||
|
// incase we rerun it with different values
|
||||||
|
if (this.timeout) clearTimeout(this.timeout)
|
||||||
|
this.timeout = undefined
|
||||||
|
|
||||||
|
if (!this.logEnabled) return
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
'Validating app logs. Next attempt in ',
|
||||||
|
this.logCleaningInterval
|
||||||
|
)
|
||||||
|
|
||||||
|
const size = maxFileSizeBytes ?? 1 * 1024 * 1024 // 1 MB
|
||||||
|
const days = daysToKeep ?? 7 // 7 days
|
||||||
|
const logDirectory = path.join(getJanDataFolderPath(), 'logs')
|
||||||
|
|
||||||
|
// Perform log cleaning
|
||||||
|
const currentDate = new Date()
|
||||||
|
fs.readdir(logDirectory, (err, files) => {
|
||||||
|
if (err) {
|
||||||
|
console.error('Error reading log directory:', err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
files.forEach((file) => {
|
||||||
|
const filePath = path.join(logDirectory, file)
|
||||||
|
fs.stat(filePath, (err, stats) => {
|
||||||
|
if (err) {
|
||||||
|
console.error('Error getting file stats:', err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check size
|
||||||
|
if (stats.size > size) {
|
||||||
|
fs.unlink(filePath, (err) => {
|
||||||
|
if (err) {
|
||||||
|
console.error('Error deleting log file:', err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
console.debug(
|
||||||
|
`Deleted log file due to exceeding size limit: ${filePath}`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// Check age
|
||||||
|
const creationDate = new Date(stats.ctime)
|
||||||
|
const daysDifference = Math.floor(
|
||||||
|
(currentDate.getTime() - creationDate.getTime()) /
|
||||||
|
(1000 * 3600 * 24)
|
||||||
|
)
|
||||||
|
if (daysDifference > days) {
|
||||||
|
fs.unlink(filePath, (err) => {
|
||||||
|
if (err) {
|
||||||
|
console.error('Error deleting log file:', err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
console.debug(`Deleted old log file: ${filePath}`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// Schedule the next execution with doubled delays
|
||||||
|
this.timeout = setTimeout(
|
||||||
|
() => this.cleanLogs(maxFileSizeBytes, daysToKeep),
|
||||||
|
this.logCleaningInterval
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const writeLog = (message: string, logPath: string) => {
|
||||||
|
if (!fs.existsSync(logPath)) {
|
||||||
|
fs.writeFileSync(logPath, message)
|
||||||
|
} else {
|
||||||
|
const logFile = fs.createWriteStream(logPath, {
|
||||||
|
flags: 'a',
|
||||||
|
})
|
||||||
|
logFile.write(util.format(message) + '\n')
|
||||||
|
logFile.close()
|
||||||
|
console.debug(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
35
server/helpers/logger.ts
Normal file
35
server/helpers/logger.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { log } from '@janhq/core/node'
|
||||||
|
import { FastifyBaseLogger } from 'fastify'
|
||||||
|
import { ChildLoggerOptions } from 'fastify/types/logger'
|
||||||
|
import pino from 'pino'
|
||||||
|
|
||||||
|
export class Logger implements FastifyBaseLogger {
|
||||||
|
child(
|
||||||
|
bindings: pino.Bindings,
|
||||||
|
options?: ChildLoggerOptions | undefined
|
||||||
|
): FastifyBaseLogger {
|
||||||
|
return new Logger()
|
||||||
|
}
|
||||||
|
level = 'info'
|
||||||
|
|
||||||
|
silent = () => {}
|
||||||
|
|
||||||
|
info = function (msg: any) {
|
||||||
|
log(msg)
|
||||||
|
}
|
||||||
|
error = function (msg: any) {
|
||||||
|
log(msg)
|
||||||
|
}
|
||||||
|
debug = function (msg: any) {
|
||||||
|
log(msg)
|
||||||
|
}
|
||||||
|
fatal = function (msg: any) {
|
||||||
|
log(msg)
|
||||||
|
}
|
||||||
|
warn = function (msg: any) {
|
||||||
|
log(msg)
|
||||||
|
}
|
||||||
|
trace = function (msg: any) {
|
||||||
|
log(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,13 +1,9 @@
|
|||||||
import fastify from 'fastify'
|
import fastify from 'fastify'
|
||||||
import dotenv from 'dotenv'
|
import dotenv from 'dotenv'
|
||||||
import {
|
import { v1Router, log, getJanExtensionsPath } from '@janhq/core/node'
|
||||||
getServerLogPath,
|
|
||||||
v1Router,
|
|
||||||
logServer,
|
|
||||||
getJanExtensionsPath,
|
|
||||||
} from '@janhq/core/node'
|
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import tcpPortUsed from 'tcp-port-used'
|
import tcpPortUsed from 'tcp-port-used'
|
||||||
|
import { Logger } from './helpers/logger'
|
||||||
|
|
||||||
// Load environment variables
|
// Load environment variables
|
||||||
dotenv.config()
|
dotenv.config()
|
||||||
@ -52,7 +48,7 @@ export const startServer = async (configs?: ServerConfig): Promise<boolean> => {
|
|||||||
const inUse = await tcpPortUsed.check(Number(configs.port), configs.host)
|
const inUse = await tcpPortUsed.check(Number(configs.port), configs.host)
|
||||||
if (inUse) {
|
if (inUse) {
|
||||||
const errorMessage = `Port ${configs.port} is already in use.`
|
const errorMessage = `Port ${configs.port} is already in use.`
|
||||||
logServer(errorMessage)
|
log(errorMessage, '[SERVER]')
|
||||||
throw new Error(errorMessage)
|
throw new Error(errorMessage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -62,19 +58,15 @@ export const startServer = async (configs?: ServerConfig): Promise<boolean> => {
|
|||||||
hostSetting = configs?.host ?? JAN_API_HOST
|
hostSetting = configs?.host ?? JAN_API_HOST
|
||||||
portSetting = configs?.port ?? JAN_API_PORT
|
portSetting = configs?.port ?? JAN_API_PORT
|
||||||
corsEnabled = configs?.isCorsEnabled ?? true
|
corsEnabled = configs?.isCorsEnabled ?? true
|
||||||
const serverLogPath = getServerLogPath()
|
|
||||||
|
|
||||||
// Start the server
|
// Start the server
|
||||||
try {
|
try {
|
||||||
// Log server start
|
// Log server start
|
||||||
if (isVerbose) logServer(`Debug: Starting JAN API server...`)
|
if (isVerbose) log(`Debug: Starting JAN API server...`, '[SERVER]')
|
||||||
|
|
||||||
// Initialize Fastify server with logging
|
// Initialize Fastify server with logging
|
||||||
server = fastify({
|
server = fastify({
|
||||||
logger: {
|
logger: new Logger(),
|
||||||
level: 'info',
|
|
||||||
file: serverLogPath,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Register CORS if enabled
|
// Register CORS if enabled
|
||||||
@ -134,14 +126,15 @@ export const startServer = async (configs?: ServerConfig): Promise<boolean> => {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
// Log server listening
|
// Log server listening
|
||||||
if (isVerbose)
|
if (isVerbose)
|
||||||
logServer(
|
log(
|
||||||
`Debug: JAN API listening at: http://${hostSetting}:${portSetting}`
|
`Debug: JAN API listening at: http://${hostSetting}:${portSetting}`,
|
||||||
|
'[SERVER]'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
return true
|
return true
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Log any errors
|
// Log any errors
|
||||||
if (isVerbose) logServer(`Error: ${e}`)
|
if (isVerbose) log(`Error: ${e}`, '[SERVER]')
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -152,11 +145,11 @@ export const startServer = async (configs?: ServerConfig): Promise<boolean> => {
|
|||||||
export const stopServer = async () => {
|
export const stopServer = async () => {
|
||||||
try {
|
try {
|
||||||
// Log server stop
|
// Log server stop
|
||||||
if (isVerbose) logServer(`Debug: Server stopped`)
|
if (isVerbose) log(`Debug: Server stopped`, '[SERVER]')
|
||||||
// Stop the server
|
// Stop the server
|
||||||
await server?.close()
|
await server?.close()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Log any errors
|
// Log any errors
|
||||||
if (isVerbose) logServer(`Error: ${e}`)
|
if (isVerbose) log(`Error: ${e}`, '[SERVER]')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,9 +28,11 @@ const ServerLogs = (props: ServerLogsProps) => {
|
|||||||
|
|
||||||
const updateLogs = useCallback(
|
const updateLogs = useCallback(
|
||||||
() =>
|
() =>
|
||||||
getLogs('server').then((log) => {
|
getLogs('app').then((log) => {
|
||||||
if (typeof log?.split === 'function') {
|
if (typeof log?.split === 'function') {
|
||||||
setLogs(log.split(/\r?\n|\r|\n/g))
|
setLogs(
|
||||||
|
log.split(/\r?\n|\r|\n/g).filter((e) => e.includes('[SERVER]::'))
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
|||||||
@ -19,7 +19,8 @@ export class ExtensionManager {
|
|||||||
* @param extension - The extension to register.
|
* @param extension - The extension to register.
|
||||||
*/
|
*/
|
||||||
register<T extends BaseExtension>(name: string, extension: T) {
|
register<T extends BaseExtension>(name: string, extension: T) {
|
||||||
this.extensions.set(extension.type() ?? name, extension)
|
// Register for naming use
|
||||||
|
this.extensions.set(name, extension)
|
||||||
|
|
||||||
// Register AI Engines
|
// Register AI Engines
|
||||||
if ('provider' in extension && typeof extension.provider === 'string') {
|
if ('provider' in extension && typeof extension.provider === 'string') {
|
||||||
@ -35,10 +36,26 @@ export class ExtensionManager {
|
|||||||
* @param type - The type of the extension to retrieve.
|
* @param type - The type of the extension to retrieve.
|
||||||
* @returns The extension, if found.
|
* @returns The extension, if found.
|
||||||
*/
|
*/
|
||||||
get<T extends BaseExtension>(
|
get<T extends BaseExtension>(type: ExtensionTypeEnum): T | undefined {
|
||||||
type: ExtensionTypeEnum | string
|
return this.getAll().findLast((e) => e.type() === type) as T | undefined
|
||||||
): T | undefined {
|
}
|
||||||
return this.extensions.get(type) as T | undefined
|
|
||||||
|
/**
|
||||||
|
* Retrieves a extension by its type.
|
||||||
|
* @param type - The type of the extension to retrieve.
|
||||||
|
* @returns The extension, if found.
|
||||||
|
*/
|
||||||
|
getByName(name: string): BaseExtension | undefined {
|
||||||
|
return this.extensions.get(name) as BaseExtension | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a extension by its type.
|
||||||
|
* @param type - The type of the extension to retrieve.
|
||||||
|
* @returns The extension, if found.
|
||||||
|
*/
|
||||||
|
getAll(): BaseExtension[] {
|
||||||
|
return Array.from(this.extensions.values())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -25,12 +25,12 @@ export const useLogs = () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const openServerLog = useCallback(async () => {
|
const openServerLog = useCallback(async () => {
|
||||||
const fullPath = await joinPath([janDataFolderPath, 'logs', 'server.log'])
|
const fullPath = await joinPath([janDataFolderPath, 'logs', 'app.log'])
|
||||||
return openFileExplorer(fullPath)
|
return openFileExplorer(fullPath)
|
||||||
}, [janDataFolderPath])
|
}, [janDataFolderPath])
|
||||||
|
|
||||||
const clearServerLog = useCallback(async () => {
|
const clearServerLog = useCallback(async () => {
|
||||||
await fs.writeFileSync(await joinPath(['file://logs', 'server.log']), '')
|
await fs.writeFileSync(await joinPath(['file://logs', 'app.log']), '')
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return { getLogs, openServerLog, clearServerLog }
|
return { getLogs, openServerLog, clearServerLog }
|
||||||
|
|||||||
@ -79,7 +79,7 @@ const TensorRtExtensionItem: React.FC<Props> = ({ item }) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const getExtensionInstallationState = async () => {
|
const getExtensionInstallationState = async () => {
|
||||||
const extension = extensionManager.get(item.name ?? '')
|
const extension = extensionManager.getByName(item.name ?? '')
|
||||||
if (!extension) return
|
if (!extension) return
|
||||||
|
|
||||||
if (typeof extension?.installationState === 'function') {
|
if (typeof extension?.installationState === 'function') {
|
||||||
@ -92,13 +92,13 @@ const TensorRtExtensionItem: React.FC<Props> = ({ item }) => {
|
|||||||
}, [item.name, isInstalling])
|
}, [item.name, isInstalling])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const extension = extensionManager.get(item.name ?? '')
|
const extension = extensionManager.getByName(item.name ?? '')
|
||||||
if (!extension) return
|
if (!extension) return
|
||||||
setCompatibility(extension.compatibility())
|
setCompatibility(extension.compatibility())
|
||||||
}, [setCompatibility, item.name])
|
}, [setCompatibility, item.name])
|
||||||
|
|
||||||
const onInstallClick = useCallback(async () => {
|
const onInstallClick = useCallback(async () => {
|
||||||
const extension = extensionManager.get(item.name ?? '')
|
const extension = extensionManager.getByName(item.name ?? '')
|
||||||
if (!extension) return
|
if (!extension) return
|
||||||
|
|
||||||
await extension.install()
|
await extension.install()
|
||||||
|
|||||||
@ -17,13 +17,12 @@ const ExtensionSetting: React.FC = () => {
|
|||||||
const getExtensionSettings = async () => {
|
const getExtensionSettings = async () => {
|
||||||
if (!selectedExtensionName) return
|
if (!selectedExtensionName) return
|
||||||
const allSettings: SettingComponentProps[] = []
|
const allSettings: SettingComponentProps[] = []
|
||||||
const baseExtension = extensionManager.get(selectedExtensionName)
|
const baseExtension = extensionManager.getByName(selectedExtensionName)
|
||||||
if (!baseExtension) return
|
if (!baseExtension) return
|
||||||
if (typeof baseExtension.getSettings === 'function') {
|
if (typeof baseExtension.getSettings === 'function') {
|
||||||
const setting = await baseExtension.getSettings()
|
const setting = await baseExtension.getSettings()
|
||||||
if (setting) allSettings.push(...setting)
|
if (setting) allSettings.push(...setting)
|
||||||
}
|
}
|
||||||
|
|
||||||
setSettings(allSettings)
|
setSettings(allSettings)
|
||||||
}
|
}
|
||||||
getExtensionSettings()
|
getExtensionSettings()
|
||||||
@ -40,7 +39,7 @@ const ExtensionSetting: React.FC = () => {
|
|||||||
|
|
||||||
const extensionName = setting.extensionName
|
const extensionName = setting.extensionName
|
||||||
if (extensionName) {
|
if (extensionName) {
|
||||||
extensionManager.get(extensionName)?.updateSettings([setting])
|
extensionManager.getByName(extensionName)?.updateSettings([setting])
|
||||||
}
|
}
|
||||||
|
|
||||||
return setting
|
return setting
|
||||||
|
|||||||
@ -0,0 +1,47 @@
|
|||||||
|
import { CheckboxComponentProps, SettingComponentProps } from '@janhq/core'
|
||||||
|
import { Switch } from '@janhq/uikit'
|
||||||
|
import { Marked, Renderer } from 'marked'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
settingProps: SettingComponentProps
|
||||||
|
onValueChanged?: (e: boolean) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const marked: Marked = new Marked({
|
||||||
|
renderer: {
|
||||||
|
link: (href, title, text) => {
|
||||||
|
return Renderer.prototype.link
|
||||||
|
?.apply(this, [href, title, text])
|
||||||
|
.replace('<a', "<a class='text-blue-500' target='_blank'")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const SettingDetailToggleItem: React.FC<Props> = ({
|
||||||
|
settingProps,
|
||||||
|
onValueChanged,
|
||||||
|
}) => {
|
||||||
|
const { value } = settingProps.controllerProps as CheckboxComponentProps
|
||||||
|
|
||||||
|
const description = marked.parse(settingProps.description ?? '', {
|
||||||
|
async: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex w-full justify-between py-6">
|
||||||
|
<div className="flex flex-1 flex-col space-y-1">
|
||||||
|
<h1 className="text-base font-bold">{settingProps.title}</h1>
|
||||||
|
{
|
||||||
|
<div
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
|
dangerouslySetInnerHTML={{ __html: description }}
|
||||||
|
className="text-sm font-normal text-muted-foreground"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<Switch checked={value} onCheckedChange={onValueChanged} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SettingDetailToggleItem
|
||||||
@ -1,6 +1,7 @@
|
|||||||
import { SettingComponentProps } from '@janhq/core'
|
import { SettingComponentProps } from '@janhq/core'
|
||||||
|
|
||||||
import SettingDetailTextInputItem from './SettingDetailTextInputItem'
|
import SettingDetailTextInputItem from './SettingDetailTextInputItem'
|
||||||
|
import SettingDetailToggleItem from './SettingDetailToggleItem'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
componentProps: SettingComponentProps[]
|
componentProps: SettingComponentProps[]
|
||||||
@ -23,6 +24,16 @@ const SettingDetailItem: React.FC<Props> = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'checkbox': {
|
||||||
|
return (
|
||||||
|
<SettingDetailToggleItem
|
||||||
|
key={data.key}
|
||||||
|
settingProps={data}
|
||||||
|
onValueChanged={(value) => onValueUpdated(data.key, value)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,20 +15,13 @@ const SettingMenu: React.FC = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const getAllSettings = async () => {
|
const getAllSettings = async () => {
|
||||||
const activeExtensions = await extensionManager.getActive()
|
|
||||||
const extensionsMenu: string[] = []
|
const extensionsMenu: string[] = []
|
||||||
|
const extensions = extensionManager.getAll()
|
||||||
for (const extension of activeExtensions) {
|
for (const extension of extensions) {
|
||||||
const extensionName = extension.name
|
if (typeof extension.getSettings === 'function') {
|
||||||
if (!extensionName) continue
|
const settings = await extension.getSettings()
|
||||||
|
|
||||||
const baseExtension = extensionManager.get(extensionName)
|
|
||||||
if (!baseExtension) continue
|
|
||||||
|
|
||||||
if (typeof baseExtension.getSettings === 'function') {
|
|
||||||
const settings = await baseExtension.getSettings()
|
|
||||||
if (settings && settings.length > 0) {
|
if (settings && settings.length > 0) {
|
||||||
extensionsMenu.push(extensionName)
|
extensionsMenu.push(extension.name ?? extension.url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user