* feat(Model): #1028 made model.json optional Signed-off-by: James <james@jan.ai> --------- Signed-off-by: James <james@jan.ai> Co-authored-by: James <james@jan.ai>
This commit is contained in:
parent
aecef0df5a
commit
26eb1d9a67
@ -53,9 +53,10 @@ export enum FileSystemRoute {
|
|||||||
writeFileSync = 'writeFileSync',
|
writeFileSync = 'writeFileSync',
|
||||||
}
|
}
|
||||||
export enum FileManagerRoute {
|
export enum FileManagerRoute {
|
||||||
synceFile = 'syncFile',
|
syncFile = 'syncFile',
|
||||||
getUserSpace = 'getUserSpace',
|
getUserSpace = 'getUserSpace',
|
||||||
getResourcePath = 'getResourcePath',
|
getResourcePath = 'getResourcePath',
|
||||||
|
fileStat = 'fileStat',
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ApiFunction = (...args: any[]) => any
|
export type ApiFunction = (...args: any[]) => any
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import { FileStat } from './types'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute a extension module function in main process
|
* Execute a extension module function in main process
|
||||||
*
|
*
|
||||||
@ -74,6 +76,15 @@ const openExternalUrl: (url: string) => Promise<any> = (url) =>
|
|||||||
*/
|
*/
|
||||||
const getResourcePath: () => Promise<string> = () => global.core.api?.getResourcePath()
|
const getResourcePath: () => Promise<string> = () => global.core.api?.getResourcePath()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the file's stats.
|
||||||
|
*
|
||||||
|
* @param path - The path to the file.
|
||||||
|
* @returns {Promise<FileStat>} - A promise that resolves with the file's stats.
|
||||||
|
*/
|
||||||
|
const fileStat: (path: string) => Promise<FileStat | undefined> = (path) =>
|
||||||
|
global.core.api?.fileStat(path)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register extension point function type definition
|
* Register extension point function type definition
|
||||||
*/
|
*/
|
||||||
@ -97,4 +108,5 @@ export {
|
|||||||
joinPath,
|
joinPath,
|
||||||
openExternalUrl,
|
openExternalUrl,
|
||||||
baseName,
|
baseName,
|
||||||
|
fileStat,
|
||||||
}
|
}
|
||||||
|
|||||||
12
core/src/node/api/routes/fileManager.ts
Normal file
12
core/src/node/api/routes/fileManager.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { FileManagerRoute } from '../../../api'
|
||||||
|
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.getResourcePath}`, async (request: any, reply: any) => {})
|
||||||
|
|
||||||
|
app.post(`/app/${FileManagerRoute.fileStat}`, async (request: any, reply: any) => {})
|
||||||
|
}
|
||||||
4
core/src/types/file/index.ts
Normal file
4
core/src/types/file/index.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export type FileStat = {
|
||||||
|
isDirectory: boolean
|
||||||
|
size: number
|
||||||
|
}
|
||||||
@ -4,3 +4,4 @@ export * from './thread'
|
|||||||
export * from './message'
|
export * from './message'
|
||||||
export * from './inference'
|
export * from './inference'
|
||||||
export * from './monitoring'
|
export * from './monitoring'
|
||||||
|
export * from './file'
|
||||||
|
|||||||
@ -4,14 +4,17 @@ import reflect from '@alumna/reflect'
|
|||||||
|
|
||||||
import { FileManagerRoute } from '@janhq/core'
|
import { FileManagerRoute } from '@janhq/core'
|
||||||
import { userSpacePath, getResourcePath } from './../utils/path'
|
import { userSpacePath, getResourcePath } from './../utils/path'
|
||||||
|
import fs from 'fs'
|
||||||
|
import { join } from 'path'
|
||||||
|
import { FileStat } from '@janhq/core/.'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles file system extensions operations.
|
* Handles file system extensions operations.
|
||||||
*/
|
*/
|
||||||
export function handleFileMangerIPCs() {
|
export function handleFileMangerIPCs() {
|
||||||
// Handles the 'synceFile' IPC event. This event is triggered to synchronize a file from a source path to a destination path.
|
// Handles the 'syncFile' IPC event. This event is triggered to synchronize a file from a source path to a destination path.
|
||||||
ipcMain.handle(
|
ipcMain.handle(
|
||||||
FileManagerRoute.synceFile,
|
FileManagerRoute.syncFile,
|
||||||
async (_event, src: string, dest: string) => {
|
async (_event, src: string, dest: string) => {
|
||||||
return reflect({
|
return reflect({
|
||||||
src,
|
src,
|
||||||
@ -31,7 +34,33 @@ export function handleFileMangerIPCs() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Handles the 'getResourcePath' IPC event. This event is triggered to get the resource path.
|
// Handles the 'getResourcePath' IPC event. This event is triggered to get the resource path.
|
||||||
ipcMain.handle(FileManagerRoute.getResourcePath, async (_event) => {
|
ipcMain.handle(FileManagerRoute.getResourcePath, async (_event) =>
|
||||||
return getResourcePath()
|
getResourcePath()
|
||||||
})
|
)
|
||||||
|
|
||||||
|
// handle fs is directory here
|
||||||
|
ipcMain.handle(
|
||||||
|
FileManagerRoute.fileStat,
|
||||||
|
async (_event, path: string): Promise<FileStat | undefined> => {
|
||||||
|
const normalizedPath = path
|
||||||
|
.replace(`file://`, '')
|
||||||
|
.replace(`file:/`, '')
|
||||||
|
.replace(`file:\\\\`, '')
|
||||||
|
.replace(`file:\\`, '')
|
||||||
|
|
||||||
|
const fullPath = join(userSpacePath, normalizedPath)
|
||||||
|
const isExist = fs.existsSync(fullPath)
|
||||||
|
if (!isExist) return undefined
|
||||||
|
|
||||||
|
const isDirectory = fs.lstatSync(fullPath).isDirectory()
|
||||||
|
const size = fs.statSync(fullPath).size
|
||||||
|
|
||||||
|
const fileStat: FileStat = {
|
||||||
|
isDirectory,
|
||||||
|
size,
|
||||||
|
}
|
||||||
|
|
||||||
|
return fileStat
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,11 +5,12 @@ import {
|
|||||||
abortDownload,
|
abortDownload,
|
||||||
getResourcePath,
|
getResourcePath,
|
||||||
getUserSpace,
|
getUserSpace,
|
||||||
|
fileStat,
|
||||||
InferenceEngine,
|
InferenceEngine,
|
||||||
joinPath,
|
joinPath,
|
||||||
|
ModelExtension,
|
||||||
|
Model,
|
||||||
} from '@janhq/core'
|
} from '@janhq/core'
|
||||||
import { ModelExtension, Model } from '@janhq/core'
|
|
||||||
import { baseName } from '@janhq/core/.'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A extension for models
|
* A extension for models
|
||||||
@ -21,6 +22,9 @@ export default class JanModelExtension implements ModelExtension {
|
|||||||
private static readonly _incompletedModelFileName = '.download'
|
private static readonly _incompletedModelFileName = '.download'
|
||||||
private static readonly _offlineInferenceEngine = InferenceEngine.nitro
|
private static readonly _offlineInferenceEngine = InferenceEngine.nitro
|
||||||
|
|
||||||
|
private static readonly _configDirName = 'config'
|
||||||
|
private static readonly _defaultModelFileName = 'default-model.json'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements type from JanExtension.
|
* Implements type from JanExtension.
|
||||||
* @override
|
* @override
|
||||||
@ -199,7 +203,7 @@ export default class JanModelExtension implements ModelExtension {
|
|||||||
): Promise<Model[]> {
|
): Promise<Model[]> {
|
||||||
try {
|
try {
|
||||||
if (!(await fs.existsSync(JanModelExtension._homeDir))) {
|
if (!(await fs.existsSync(JanModelExtension._homeDir))) {
|
||||||
console.debug('model folder not found')
|
console.error('Model folder not found')
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -220,13 +224,22 @@ export default class JanModelExtension implements ModelExtension {
|
|||||||
dirName,
|
dirName,
|
||||||
JanModelExtension._modelMetadataFileName,
|
JanModelExtension._modelMetadataFileName,
|
||||||
])
|
])
|
||||||
let model = await this.readModelMetadata(jsonPath)
|
|
||||||
model = typeof model === 'object' ? model : JSON.parse(model)
|
|
||||||
|
|
||||||
if (selector && !(await selector?.(dirName, model))) {
|
if (await fs.existsSync(jsonPath)) {
|
||||||
return
|
// if we have the model.json file, read it
|
||||||
|
let model = await this.readModelMetadata(jsonPath)
|
||||||
|
model = typeof model === 'object' ? model : JSON.parse(model)
|
||||||
|
|
||||||
|
if (selector && !(await selector?.(dirName, model))) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return model
|
||||||
|
} else {
|
||||||
|
// otherwise, we generate our own model file
|
||||||
|
// TODO: we might have more than one binary file here. This will be addressed with new version of Model file
|
||||||
|
// which is the PR from Hiro on branch Jan can see
|
||||||
|
return this.generateModelMetadata(dirName)
|
||||||
}
|
}
|
||||||
return model
|
|
||||||
})
|
})
|
||||||
const results = await Promise.allSettled(readJsonPromises)
|
const results = await Promise.allSettled(readJsonPromises)
|
||||||
const modelData = results.map((result) => {
|
const modelData = results.map((result) => {
|
||||||
@ -254,6 +267,84 @@ export default class JanModelExtension implements ModelExtension {
|
|||||||
return fs.readFileSync(path, 'utf-8')
|
return fs.readFileSync(path, 'utf-8')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the case where we have the model directory but we don't have the corresponding
|
||||||
|
* model.json file associated with it.
|
||||||
|
*
|
||||||
|
* This function will create a model.json file for the model.
|
||||||
|
*
|
||||||
|
* @param dirName the director which reside in ~/jan/models but does not have model.json file.
|
||||||
|
*/
|
||||||
|
private async generateModelMetadata(dirName: string): Promise<Model> {
|
||||||
|
const files: string[] = await fs.readdirSync(
|
||||||
|
await joinPath([JanModelExtension._homeDir, dirName])
|
||||||
|
)
|
||||||
|
|
||||||
|
// sort files by name
|
||||||
|
files.sort()
|
||||||
|
|
||||||
|
// find the first file which is not a directory
|
||||||
|
let binaryFileName: string | undefined = undefined
|
||||||
|
let binaryFileSize: number | undefined = undefined
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
if (file.endsWith(JanModelExtension._incompletedModelFileName)) continue
|
||||||
|
if (file.endsWith('.json')) continue
|
||||||
|
|
||||||
|
const path = await joinPath([JanModelExtension._homeDir, dirName, file])
|
||||||
|
const fileStats = await fileStat(path)
|
||||||
|
if (fileStats.isDirectory) continue
|
||||||
|
binaryFileSize = fileStats.size
|
||||||
|
binaryFileName = file
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!binaryFileName) {
|
||||||
|
console.warn(`Unable to find binary file for model ${dirName}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultModel = await this.getDefaultModel()
|
||||||
|
if (!defaultModel) {
|
||||||
|
console.error('Unable to find default model')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const model: Model = {
|
||||||
|
...defaultModel,
|
||||||
|
id: dirName,
|
||||||
|
name: dirName,
|
||||||
|
created: Date.now(),
|
||||||
|
description: `${dirName} - user self import model`,
|
||||||
|
}
|
||||||
|
|
||||||
|
const modelFilePath = await joinPath([
|
||||||
|
JanModelExtension._homeDir,
|
||||||
|
dirName,
|
||||||
|
JanModelExtension._modelMetadataFileName,
|
||||||
|
])
|
||||||
|
|
||||||
|
await fs.writeFileSync(modelFilePath, JSON.stringify(model, null, 2))
|
||||||
|
|
||||||
|
return model
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getDefaultModel(): Promise<Model | undefined> {
|
||||||
|
const defaultModelPath = await joinPath([
|
||||||
|
JanModelExtension._homeDir,
|
||||||
|
JanModelExtension._configDirName,
|
||||||
|
JanModelExtension._defaultModelFileName,
|
||||||
|
])
|
||||||
|
|
||||||
|
if (!(await fs.existsSync(defaultModelPath))) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const model = await this.readModelMetadata(defaultModelPath)
|
||||||
|
|
||||||
|
return typeof model === 'object' ? model : JSON.parse(model)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets all available models.
|
* Gets all available models.
|
||||||
* @returns A Promise that resolves with an array of all models.
|
* @returns A Promise that resolves with an array of all models.
|
||||||
|
|||||||
35
models/config/default-model.json
Normal file
35
models/config/default-model.json
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"object": "model",
|
||||||
|
"version": 1,
|
||||||
|
"format": "gguf",
|
||||||
|
"source_url": "N/A",
|
||||||
|
"id": "N/A",
|
||||||
|
"name": "N/A",
|
||||||
|
"created": 0,
|
||||||
|
"description": "User self import model",
|
||||||
|
"settings": {
|
||||||
|
"ctx_len": 4096,
|
||||||
|
"ngl": 0,
|
||||||
|
"embedding": false,
|
||||||
|
"n_parallel": 0,
|
||||||
|
"cpu_threads": 0,
|
||||||
|
"prompt_template": ""
|
||||||
|
},
|
||||||
|
"parameters": {
|
||||||
|
"temperature": 0,
|
||||||
|
"token_limit": 0,
|
||||||
|
"top_k": 0,
|
||||||
|
"top_p": 0,
|
||||||
|
"stream": false,
|
||||||
|
"max_tokens": 4096,
|
||||||
|
"stop": [],
|
||||||
|
"frequency_penalty": 0,
|
||||||
|
"presence_penalty": 0
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"author": "User",
|
||||||
|
"tags": [],
|
||||||
|
"size": 0
|
||||||
|
},
|
||||||
|
"engine": "nitro"
|
||||||
|
}
|
||||||
@ -17,6 +17,7 @@
|
|||||||
"build": "tsc"
|
"build": "tsc"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@alumna/reflect": "^1.1.3",
|
||||||
"@fastify/cors": "^8.4.2",
|
"@fastify/cors": "^8.4.2",
|
||||||
"@fastify/static": "^6.12.0",
|
"@fastify/static": "^6.12.0",
|
||||||
"@fastify/swagger": "^8.13.0",
|
"@fastify/swagger": "^8.13.0",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user