Louis 83f090826e
feat: Jan Hub Revamp (#4491)
* feat: model hub revamp UI

* chore: model description - consistent markdown css

* chore: add model versions dropdown

* chore: integrate APIs - model sources

* chore: update model display name

* chore: lint fix

* chore: page transition animation

* feat: model search dropdown - deeplink

* chore: bump cortex version

* chore: add remote model sources

* chore: model download state

* chore: fix model metadata label

* chore: polish model detail page markdown

* test: fix test cases

* chore: initialize default Hub model sources

* chore: fix model stats

* chore: clean up click outside and inside hooks

* feat: change hub banner

* chore: lint fix

* chore: fix css long model id
2025-01-28 22:23:25 +07:00

243 lines
5.3 KiB
TypeScript

import PQueue from 'p-queue'
import ky from 'ky'
import { extractModelLoadParams, Model, ModelSource } from '@janhq/core'
import { extractInferenceParams } from '@janhq/core'
/**
* cortex.cpp Model APIs interface
*/
interface ICortexAPI {
getModel(model: string): Promise<Model>
getModels(): Promise<Model[]>
pullModel(model: string, id?: string, name?: string): Promise<void>
importModel(
path: string,
modelPath: string,
name?: string,
option?: string
): Promise<void>
deleteModel(model: string): Promise<void>
updateModel(model: object): Promise<void>
cancelModelPull(model: string): Promise<void>
configs(body: { [key: string]: any }): Promise<void>
getSources(): Promise<ModelSource[]>
addSource(source: string): Promise<void>
deleteSource(source: string): Promise<void>
}
type Data = {
data: any[]
}
export class CortexAPI implements ICortexAPI {
queue = new PQueue({ concurrency: 1 })
constructor() {
this.queue.add(() => this.healthz())
}
/**
* Fetches a model detail from cortex.cpp
* @param model
* @returns
*/
getModel(model: string): Promise<any> {
return this.queue.add(() =>
ky
.get(`${API_URL}/v1/models/${model}`)
.json()
.then((e) => this.transformModel(e))
)
}
/**
* Fetches models list from cortex.cpp
* @param model
* @returns
*/
getModels(): Promise<Model[]> {
return this.queue
.add(() => ky.get(`${API_URL}/v1/models?limit=-1`).json<Data>())
.then((e) =>
typeof e === 'object' ? e.data.map((e) => this.transformModel(e)) : []
)
}
/**
* Pulls a model from HuggingFace via cortex.cpp
* @param model
* @returns
*/
pullModel(model: string, id?: string, name?: string): Promise<void> {
return this.queue.add(() =>
ky
.post(`${API_URL}/v1/models/pull`, { json: { model, id, name } })
.json()
.catch(async (e) => {
throw (await e.response?.json()) ?? e
})
.then()
)
}
/**
* Imports a model from a local path via cortex.cpp
* @param model
* @returns
*/
importModel(
model: string,
modelPath: string,
name?: string,
option?: string
): Promise<void> {
return this.queue.add(() =>
ky
.post(`${API_URL}/v1/models/import`, {
json: { model, modelPath, name, option },
})
.json()
.catch((e) => console.debug(e)) // Ignore error
.then()
)
}
/**
* Deletes a model from cortex.cpp
* @param model
* @returns
*/
deleteModel(model: string): Promise<void> {
return this.queue.add(() =>
ky.delete(`${API_URL}/v1/models/${model}`).json().then()
)
}
/**
* Update a model in cortex.cpp
* @param model
* @returns
*/
updateModel(model: Partial<Model>): Promise<void> {
return this.queue.add(() =>
ky
.patch(`${API_URL}/v1/models/${model.id}`, { json: { ...model } })
.json()
.then()
)
}
/**
* Cancel model pull in cortex.cpp
* @param model
* @returns
*/
cancelModelPull(model: string): Promise<void> {
return this.queue.add(() =>
ky
.delete(`${API_URL}/v1/models/pull`, { json: { taskId: model } })
.json()
.then()
)
}
/**
* Check model status
* @param model
*/
async getModelStatus(model: string): Promise<boolean> {
return this.queue
.add(() => ky.get(`${API_URL}/v1/models/status/${model}`))
.then((e) => true)
.catch(() => false)
}
// BEGIN - Model Sources
/**
* Get model sources
* @param model
*/
async getSources(): Promise<ModelSource[]> {
return this.queue
.add(() => ky.get(`${API_URL}/v1/models/sources`).json<Data>())
.then((e) => (typeof e === 'object' ? (e.data as ModelSource[]) : []))
.catch(() => [])
}
/**
* Add a model source
* @param model
*/
async addSource(source: string): Promise<any> {
return this.queue.add(() =>
ky.post(`${API_URL}/v1/models/sources`, {
json: {
source,
},
})
)
}
/**
* Delete a model source
* @param model
*/
async deleteSource(source: string): Promise<any> {
return this.queue.add(() =>
ky.delete(`${API_URL}/v1/models/sources`, {
json: {
source,
},
})
)
}
// END - Model Sources
/**
* Do health check on cortex.cpp
* @returns
*/
healthz(): Promise<void> {
return ky
.get(`${API_URL}/healthz`, {
retry: {
limit: 20,
delay: () => 500,
methods: ['get'],
},
})
.then(() => {})
}
/**
* Configure model pull options
* @param body
*/
configs(body: { [key: string]: any }): Promise<void> {
return this.queue.add(() =>
ky.patch(`${API_URL}/v1/configs`, { json: body }).then(() => {})
)
}
/**
* TRansform model to the expected format (e.g. parameters, settings, metadata)
* @param model
* @returns
*/
private transformModel(model: any) {
model.parameters = {
...extractInferenceParams(model),
...model.parameters,
...model.inference_params,
}
model.settings = {
...extractModelLoadParams(model),
...model.settings,
}
model.metadata = model.metadata ?? {
tags: [],
size: model.size ?? model.metadata?.size ?? 0,
}
return model as Model
}
}