chore: cortex.cpp gpu activation could cause a race condition (#4825)

This commit is contained in:
Louis 2025-03-21 14:55:39 +07:00 committed by GitHub
parent 2271c8d3d6
commit 7e46295af1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 374 additions and 252 deletions

View File

@ -22,22 +22,26 @@ type MessageList = {
export default class CortexConversationalExtension extends ConversationalExtension { export default class CortexConversationalExtension extends ConversationalExtension {
queue = new PQueue({ concurrency: 1 }) queue = new PQueue({ concurrency: 1 })
api?: KyInstance
/** /**
* Extended API instance for making requests to the Cortex API. * Get the API instance
* @returns * @returns
*/ */
api: KyInstance async apiInstance(): Promise<KyInstance> {
/** if(this.api) return this.api
* Called when the extension is loaded. const apiKey = (await window.core?.api.appToken()) ?? 'cortex.cpp'
*/
async onLoad() {
const apiKey = await window.core?.api.appToken() ?? 'cortex.cpp'
this.api = ky.extend({ this.api = ky.extend({
prefixUrl: API_URL, prefixUrl: API_URL,
headers: { headers: {
Authorization: `Bearer ${apiKey}`, Authorization: `Bearer ${apiKey}`,
}, },
}) })
return this.api
}
/**
* Called when the extension is loaded.
*/
async onLoad() {
this.queue.add(() => this.healthz()) this.queue.add(() => this.healthz())
} }
@ -51,10 +55,12 @@ export default class CortexConversationalExtension extends ConversationalExtensi
*/ */
async listThreads(): Promise<Thread[]> { async listThreads(): Promise<Thread[]> {
return this.queue.add(() => return this.queue.add(() =>
this.api this.apiInstance().then((api) =>
api
.get('v1/threads?limit=-1') .get('v1/threads?limit=-1')
.json<ThreadList>() .json<ThreadList>()
.then((e) => e.data) .then((e) => e.data)
)
) as Promise<Thread[]> ) as Promise<Thread[]>
} }
@ -64,7 +70,9 @@ export default class CortexConversationalExtension extends ConversationalExtensi
*/ */
async createThread(thread: Thread): Promise<Thread> { async createThread(thread: Thread): Promise<Thread> {
return this.queue.add(() => return this.queue.add(() =>
this.api.post('v1/threads', { json: thread }).json<Thread>() this.apiInstance().then((api) =>
api.post('v1/threads', { json: thread }).json<Thread>()
)
) as Promise<Thread> ) as Promise<Thread>
} }
@ -75,7 +83,9 @@ export default class CortexConversationalExtension extends ConversationalExtensi
async modifyThread(thread: Thread): Promise<void> { async modifyThread(thread: Thread): Promise<void> {
return this.queue return this.queue
.add(() => .add(() =>
this.api.patch(`v1/threads/${thread.id}`, { json: thread }) this.apiInstance().then((api) =>
api.patch(`v1/threads/${thread.id}`, { json: thread })
)
) )
.then() .then()
} }
@ -86,7 +96,9 @@ export default class CortexConversationalExtension extends ConversationalExtensi
*/ */
async deleteThread(threadId: string): Promise<void> { async deleteThread(threadId: string): Promise<void> {
return this.queue return this.queue
.add(() => this.api.delete(`v1/threads/${threadId}`)) .add(() =>
this.apiInstance().then((api) => api.delete(`v1/threads/${threadId}`))
)
.then() .then()
} }
@ -97,11 +109,13 @@ export default class CortexConversationalExtension extends ConversationalExtensi
*/ */
async createMessage(message: ThreadMessage): Promise<ThreadMessage> { async createMessage(message: ThreadMessage): Promise<ThreadMessage> {
return this.queue.add(() => return this.queue.add(() =>
this.api this.apiInstance().then((api) =>
api
.post(`v1/threads/${message.thread_id}/messages`, { .post(`v1/threads/${message.thread_id}/messages`, {
json: message, json: message,
}) })
.json<ThreadMessage>() .json<ThreadMessage>()
)
) as Promise<ThreadMessage> ) as Promise<ThreadMessage>
} }
@ -112,14 +126,13 @@ export default class CortexConversationalExtension extends ConversationalExtensi
*/ */
async modifyMessage(message: ThreadMessage): Promise<ThreadMessage> { async modifyMessage(message: ThreadMessage): Promise<ThreadMessage> {
return this.queue.add(() => return this.queue.add(() =>
this.api this.apiInstance().then((api) =>
.patch( api
`v1/threads/${message.thread_id}/messages/${message.id}`, .patch(`v1/threads/${message.thread_id}/messages/${message.id}`, {
{
json: message, json: message,
} })
)
.json<ThreadMessage>() .json<ThreadMessage>()
)
) as Promise<ThreadMessage> ) as Promise<ThreadMessage>
} }
@ -132,7 +145,9 @@ export default class CortexConversationalExtension extends ConversationalExtensi
async deleteMessage(threadId: string, messageId: string): Promise<void> { async deleteMessage(threadId: string, messageId: string): Promise<void> {
return this.queue return this.queue
.add(() => .add(() =>
this.api.delete(`v1/threads/${threadId}/messages/${messageId}`) this.apiInstance().then((api) =>
api.delete(`v1/threads/${threadId}/messages/${messageId}`)
)
) )
.then() .then()
} }
@ -144,10 +159,12 @@ export default class CortexConversationalExtension extends ConversationalExtensi
*/ */
async listMessages(threadId: string): Promise<ThreadMessage[]> { async listMessages(threadId: string): Promise<ThreadMessage[]> {
return this.queue.add(() => return this.queue.add(() =>
this.api this.apiInstance().then((api) =>
api
.get(`v1/threads/${threadId}/messages?order=asc&limit=-1`) .get(`v1/threads/${threadId}/messages?order=asc&limit=-1`)
.json<MessageList>() .json<MessageList>()
.then((e) => e.data) .then((e) => e.data)
)
) as Promise<ThreadMessage[]> ) as Promise<ThreadMessage[]>
} }
@ -159,9 +176,11 @@ export default class CortexConversationalExtension extends ConversationalExtensi
*/ */
async getThreadAssistant(threadId: string): Promise<ThreadAssistantInfo> { async getThreadAssistant(threadId: string): Promise<ThreadAssistantInfo> {
return this.queue.add(() => return this.queue.add(() =>
this.api this.apiInstance().then((api) =>
api
.get(`v1/assistants/${threadId}?limit=-1`) .get(`v1/assistants/${threadId}?limit=-1`)
.json<ThreadAssistantInfo>() .json<ThreadAssistantInfo>()
)
) as Promise<ThreadAssistantInfo> ) as Promise<ThreadAssistantInfo>
} }
/** /**
@ -175,9 +194,11 @@ export default class CortexConversationalExtension extends ConversationalExtensi
assistant: ThreadAssistantInfo assistant: ThreadAssistantInfo
): Promise<ThreadAssistantInfo> { ): Promise<ThreadAssistantInfo> {
return this.queue.add(() => return this.queue.add(() =>
this.api this.apiInstance().then((api) =>
api
.post(`v1/assistants/${threadId}`, { json: assistant }) .post(`v1/assistants/${threadId}`, { json: assistant })
.json<ThreadAssistantInfo>() .json<ThreadAssistantInfo>()
)
) as Promise<ThreadAssistantInfo> ) as Promise<ThreadAssistantInfo>
} }
@ -192,9 +213,11 @@ export default class CortexConversationalExtension extends ConversationalExtensi
assistant: ThreadAssistantInfo assistant: ThreadAssistantInfo
): Promise<ThreadAssistantInfo> { ): Promise<ThreadAssistantInfo> {
return this.queue.add(() => return this.queue.add(() =>
this.api this.apiInstance().then((api) =>
api
.patch(`v1/assistants/${threadId}`, { json: assistant }) .patch(`v1/assistants/${threadId}`, { json: assistant })
.json<ThreadAssistantInfo>() .json<ThreadAssistantInfo>()
)
) as Promise<ThreadAssistantInfo> ) as Promise<ThreadAssistantInfo>
} }
@ -203,10 +226,12 @@ export default class CortexConversationalExtension extends ConversationalExtensi
* @returns * @returns
*/ */
async healthz(): Promise<void> { async healthz(): Promise<void> {
return this.api return this.apiInstance()
.get('healthz', { .then((api) =>
api.get('healthz', {
retry: { limit: 20, delay: () => 500, methods: ['get'] }, retry: { limit: 20, delay: () => 500, methods: ['get'] },
}) })
)
.then(() => {}) .then(() => {})
} }
} }

View File

@ -31,15 +31,13 @@ interface ModelList {
export default class JanEngineManagementExtension extends EngineManagementExtension { export default class JanEngineManagementExtension extends EngineManagementExtension {
queue = new PQueue({ concurrency: 1 }) queue = new PQueue({ concurrency: 1 })
api?: KyInstance
/** /**
* Extended API instance for making requests to the Cortex API. * Get the API instance
* @returns * @returns
*/ */
api: KyInstance async apiInstance(): Promise<KyInstance> {
/** if(this.api) return this.api
* Called when the extension is loaded.
*/
async onLoad() {
const apiKey = (await window.core?.api.appToken()) ?? 'cortex.cpp' const apiKey = (await window.core?.api.appToken()) ?? 'cortex.cpp'
this.api = ky.extend({ this.api = ky.extend({
prefixUrl: API_URL, prefixUrl: API_URL,
@ -47,6 +45,12 @@ export default class JanEngineManagementExtension extends EngineManagementExtens
Authorization: `Bearer ${apiKey}`, Authorization: `Bearer ${apiKey}`,
}, },
}) })
return this.api
}
/**
* Called when the extension is loaded.
*/
async onLoad() {
// Symlink Engines Directory // Symlink Engines Directory
await executeOnMain(NODE, 'symlinkEngines') await executeOnMain(NODE, 'symlinkEngines')
// Run Healthcheck // Run Healthcheck
@ -71,10 +75,12 @@ export default class JanEngineManagementExtension extends EngineManagementExtens
*/ */
async getEngines(): Promise<Engines> { async getEngines(): Promise<Engines> {
return this.queue.add(() => return this.queue.add(() =>
this.api this.apiInstance().then((api) =>
api
.get('v1/engines') .get('v1/engines')
.json<Engines>() .json<Engines>()
.then((e) => e) .then((e) => e)
)
) as Promise<Engines> ) as Promise<Engines>
} }
@ -82,12 +88,15 @@ export default class JanEngineManagementExtension extends EngineManagementExtens
* @returns A Promise that resolves to an object of list engines. * @returns A Promise that resolves to an object of list engines.
*/ */
async getRemoteModels(name: string): Promise<any> { async getRemoteModels(name: string): Promise<any> {
return this.api return this.apiInstance().then(
(api) =>
api
.get(`v1/models/remote/${name}`) .get(`v1/models/remote/${name}`)
.json<ModelList>() .json<ModelList>()
.catch(() => ({ .catch(() => ({
data: [], data: [],
})) as Promise<ModelList> })) as Promise<ModelList>
)
} }
/** /**
@ -96,10 +105,12 @@ export default class JanEngineManagementExtension extends EngineManagementExtens
*/ */
async getInstalledEngines(name: InferenceEngine): Promise<EngineVariant[]> { async getInstalledEngines(name: InferenceEngine): Promise<EngineVariant[]> {
return this.queue.add(() => return this.queue.add(() =>
this.api this.apiInstance().then((api) =>
api
.get(`v1/engines/${name}`) .get(`v1/engines/${name}`)
.json<EngineVariant[]>() .json<EngineVariant[]>()
.then((e) => e) .then((e) => e)
)
) as Promise<EngineVariant[]> ) as Promise<EngineVariant[]>
} }
@ -115,12 +126,14 @@ export default class JanEngineManagementExtension extends EngineManagementExtens
platform?: string platform?: string
) { ) {
return this.queue.add(() => return this.queue.add(() =>
this.api this.apiInstance().then((api) =>
api
.get(`v1/engines/${name}/releases/${version}`) .get(`v1/engines/${name}/releases/${version}`)
.json<EngineReleased[]>() .json<EngineReleased[]>()
.then((e) => .then((e) =>
platform ? e.filter((r) => r.name.includes(platform)) : e platform ? e.filter((r) => r.name.includes(platform)) : e
) )
)
) as Promise<EngineReleased[]> ) as Promise<EngineReleased[]>
} }
@ -131,12 +144,14 @@ export default class JanEngineManagementExtension extends EngineManagementExtens
*/ */
async getLatestReleasedEngine(name: InferenceEngine, platform?: string) { async getLatestReleasedEngine(name: InferenceEngine, platform?: string) {
return this.queue.add(() => return this.queue.add(() =>
this.api this.apiInstance().then((api) =>
api
.get(`v1/engines/${name}/releases/latest`) .get(`v1/engines/${name}/releases/latest`)
.json<EngineReleased[]>() .json<EngineReleased[]>()
.then((e) => .then((e) =>
platform ? e.filter((r) => r.name.includes(platform)) : e platform ? e.filter((r) => r.name.includes(platform)) : e
) )
)
) as Promise<EngineReleased[]> ) as Promise<EngineReleased[]>
} }
@ -146,9 +161,11 @@ export default class JanEngineManagementExtension extends EngineManagementExtens
*/ */
async installEngine(name: string, engineConfig: EngineConfig) { async installEngine(name: string, engineConfig: EngineConfig) {
return this.queue.add(() => return this.queue.add(() =>
this.api this.apiInstance().then((api) =>
api
.post(`v1/engines/${name}/install`, { json: engineConfig }) .post(`v1/engines/${name}/install`, { json: engineConfig })
.then((e) => e) .then((e) => e)
)
) as Promise<{ messages: string }> ) as Promise<{ messages: string }>
} }
@ -179,9 +196,8 @@ export default class JanEngineManagementExtension extends EngineManagementExtens
engineConfig.metadata.header_template = DEFAULT_REQUEST_HEADERS_TRANSFORM engineConfig.metadata.header_template = DEFAULT_REQUEST_HEADERS_TRANSFORM
return this.queue.add(() => return this.queue.add(() =>
this.api this.apiInstance().then((api) =>
.post('v1/engines', { json: engineConfig }) api.post('v1/engines', { json: engineConfig }).then((e) => {
.then((e) => {
if (persistModels && engineConfig.metadata?.get_models_url) { if (persistModels && engineConfig.metadata?.get_models_url) {
// Pull /models from remote models endpoint // Pull /models from remote models endpoint
return this.populateRemoteModels(engineConfig) return this.populateRemoteModels(engineConfig)
@ -190,6 +206,7 @@ export default class JanEngineManagementExtension extends EngineManagementExtens
} }
return e return e
}) })
)
) as Promise<{ messages: string }> ) as Promise<{ messages: string }>
} }
@ -199,9 +216,11 @@ export default class JanEngineManagementExtension extends EngineManagementExtens
*/ */
async uninstallEngine(name: InferenceEngine, engineConfig: EngineConfig) { async uninstallEngine(name: InferenceEngine, engineConfig: EngineConfig) {
return this.queue.add(() => return this.queue.add(() =>
this.api this.apiInstance().then((api) =>
api
.delete(`v1/engines/${name}/install`, { json: engineConfig }) .delete(`v1/engines/${name}/install`, { json: engineConfig })
.then((e) => e) .then((e) => e)
)
) as Promise<{ messages: string }> ) as Promise<{ messages: string }>
} }
@ -210,9 +229,10 @@ export default class JanEngineManagementExtension extends EngineManagementExtens
* @param model - Remote model object. * @param model - Remote model object.
*/ */
async addRemoteModel(model: Model) { async addRemoteModel(model: Model) {
return this.queue return this.queue.add(() =>
.add(() => this.apiInstance()
this.api .then((api) =>
api
.post('v1/models/add', { .post('v1/models/add', {
json: { json: {
inference_params: { inference_params: {
@ -229,6 +249,7 @@ export default class JanEngineManagementExtension extends EngineManagementExtens
.then((e) => e) .then((e) => e)
) )
.then(() => {}) .then(() => {})
)
} }
/** /**
@ -237,10 +258,12 @@ export default class JanEngineManagementExtension extends EngineManagementExtens
*/ */
async getDefaultEngineVariant(name: InferenceEngine) { async getDefaultEngineVariant(name: InferenceEngine) {
return this.queue.add(() => return this.queue.add(() =>
this.api this.apiInstance().then((api) =>
api
.get(`v1/engines/${name}/default`) .get(`v1/engines/${name}/default`)
.json<{ messages: string }>() .json<{ messages: string }>()
.then((e) => e) .then((e) => e)
)
) as Promise<DefaultEngineVariant> ) as Promise<DefaultEngineVariant>
} }
@ -254,9 +277,11 @@ export default class JanEngineManagementExtension extends EngineManagementExtens
engineConfig: EngineConfig engineConfig: EngineConfig
) { ) {
return this.queue.add(() => return this.queue.add(() =>
this.api this.apiInstance().then((api) =>
api
.post(`v1/engines/${name}/default`, { json: engineConfig }) .post(`v1/engines/${name}/default`, { json: engineConfig })
.then((e) => e) .then((e) => e)
)
) as Promise<{ messages: string }> ) as Promise<{ messages: string }>
} }
@ -265,9 +290,11 @@ export default class JanEngineManagementExtension extends EngineManagementExtens
*/ */
async updateEngine(name: InferenceEngine, engineConfig?: EngineConfig) { async updateEngine(name: InferenceEngine, engineConfig?: EngineConfig) {
return this.queue.add(() => return this.queue.add(() =>
this.api this.apiInstance().then((api) =>
api
.post(`v1/engines/${name}/update`, { json: engineConfig }) .post(`v1/engines/${name}/update`, { json: engineConfig })
.then((e) => e) .then((e) => e)
)
) as Promise<{ messages: string }> ) as Promise<{ messages: string }>
} }
@ -276,10 +303,12 @@ export default class JanEngineManagementExtension extends EngineManagementExtens
* @returns * @returns
*/ */
async healthz(): Promise<void> { async healthz(): Promise<void> {
return this.api return this.apiInstance()
.get('healthz', { .then((api) =>
api.get('healthz', {
retry: { limit: 20, delay: () => 500, methods: ['get'] }, retry: { limit: 20, delay: () => 500, methods: ['get'] },
}) })
)
.then(() => { .then(() => {
this.queue.concurrency = Infinity this.queue.concurrency = Infinity
}) })

View File

@ -17,18 +17,21 @@ export default class JSONHardwareManagementExtension extends HardwareManagementE
this.queue.add(() => this.healthz()) this.queue.add(() => this.healthz())
} }
api?: KyInstance
/** /**
* Get the API instance * Get the API instance
* @returns * @returns
*/ */
async apiInstance(): Promise<KyInstance> { async apiInstance(): Promise<KyInstance> {
if(this.api) return this.api
const apiKey = (await window.core?.api.appToken()) ?? 'cortex.cpp' const apiKey = (await window.core?.api.appToken()) ?? 'cortex.cpp'
return ky.extend({ this.api = ky.extend({
prefixUrl: API_URL, prefixUrl: API_URL,
headers: { headers: {
Authorization: `Bearer ${apiKey}`, Authorization: `Bearer ${apiKey}`,
}, },
}) })
return this.api
} }
/** /**

View File

@ -75,7 +75,22 @@ export default class JanInferenceCortexExtension extends LocalOAIEngine {
abortControllers = new Map<string, AbortController>() abortControllers = new Map<string, AbortController>()
api!: KyInstance api?: KyInstance
/**
* Get the API instance
* @returns
*/
async apiInstance(): Promise<KyInstance> {
if(this.api) return this.api
const apiKey = (await window.core?.api.appToken()) ?? 'cortex.cpp'
this.api = ky.extend({
prefixUrl: CORTEX_API_URL,
headers: {
Authorization: `Bearer ${apiKey}`,
},
})
return this.api
}
/** /**
* Authorization headers for the API requests. * Authorization headers for the API requests.
@ -92,13 +107,6 @@ export default class JanInferenceCortexExtension extends LocalOAIEngine {
*/ */
async onLoad() { async onLoad() {
super.onLoad() super.onLoad()
const apiKey = (await window.core?.api.appToken()) ?? 'cortex.cpp'
this.api = ky.extend({
prefixUrl: CORTEX_API_URL,
headers: {
Authorization: `Bearer ${apiKey}`,
},
})
// Register Settings // Register Settings
this.registerSettings(SETTINGS) this.registerSettings(SETTINGS)
@ -172,7 +180,8 @@ export default class JanInferenceCortexExtension extends LocalOAIEngine {
this.abortControllers.set(model.id, controller) this.abortControllers.set(model.id, controller)
return await this.queue.add(() => return await this.queue.add(() =>
this.api this.apiInstance().then((api) =>
api
.post('v1/models/start', { .post('v1/models/start', {
json: { json: {
...extractModelLoadParams(model.settings), ...extractModelLoadParams(model.settings),
@ -199,10 +208,12 @@ export default class JanInferenceCortexExtension extends LocalOAIEngine {
.finally(() => this.abortControllers.delete(model.id)) .finally(() => this.abortControllers.delete(model.id))
.then() .then()
) )
)
} }
override async unloadModel(model: Model): Promise<void> { override async unloadModel(model: Model): Promise<void> {
return this.api return this.apiInstance().then((api) =>
api
.post('v1/models/stop', { .post('v1/models/stop', {
json: { model: model.id }, json: { model: model.id },
}) })
@ -211,6 +222,7 @@ export default class JanInferenceCortexExtension extends LocalOAIEngine {
this.abortControllers.get(model.id)?.abort() this.abortControllers.get(model.id)?.abort()
}) })
.then() .then()
)
} }
/** /**
@ -218,7 +230,8 @@ export default class JanInferenceCortexExtension extends LocalOAIEngine {
* @returns * @returns
*/ */
private async healthz(): Promise<void> { private async healthz(): Promise<void> {
return this.api return this.apiInstance().then((api) =>
api
.get('healthz', { .get('healthz', {
retry: { retry: {
limit: 20, limit: 20,
@ -227,6 +240,7 @@ export default class JanInferenceCortexExtension extends LocalOAIEngine {
}, },
}) })
.then(() => {}) .then(() => {})
)
} }
/** /**
@ -234,13 +248,15 @@ export default class JanInferenceCortexExtension extends LocalOAIEngine {
* @returns * @returns
*/ */
private async clean(): Promise<any> { private async clean(): Promise<any> {
return this.api return this.apiInstance()
.delete('processmanager/destroy', { .then((api) =>
api.delete('processmanager/destroy', {
timeout: 2000, // maximum 2 seconds timeout: 2000, // maximum 2 seconds
retry: { retry: {
limit: 0, limit: 0,
}, },
}) })
)
.catch(() => { .catch(() => {
// Do nothing // Do nothing
}) })

View File

@ -33,15 +33,13 @@ type Data<T> = {
export default class JanModelExtension extends ModelExtension { export default class JanModelExtension extends ModelExtension {
queue = new PQueue({ concurrency: 1 }) queue = new PQueue({ concurrency: 1 })
api?: KyInstance
/** /**
* Extended API instance for making requests to the Cortex API. * Get the API instance
* @returns * @returns
*/ */
api: KyInstance async apiInstance(): Promise<KyInstance> {
/** if(this.api) return this.api
* Called when the extension is loaded.
*/
async onLoad() {
const apiKey = (await window.core?.api.appToken()) ?? 'cortex.cpp' const apiKey = (await window.core?.api.appToken()) ?? 'cortex.cpp'
this.api = ky.extend({ this.api = ky.extend({
prefixUrl: CORTEX_API_URL, prefixUrl: CORTEX_API_URL,
@ -49,6 +47,12 @@ export default class JanModelExtension extends ModelExtension {
Authorization: `Bearer ${apiKey}`, Authorization: `Bearer ${apiKey}`,
}, },
}) })
return this.api
}
/**
* Called when the extension is loaded.
*/
async onLoad() {
this.queue.add(() => this.healthz()) this.queue.add(() => this.healthz())
this.registerSettings(SETTINGS) this.registerSettings(SETTINGS)
@ -94,7 +98,8 @@ export default class JanModelExtension extends ModelExtension {
* Sending POST to /models/pull/{id} endpoint to pull the model * Sending POST to /models/pull/{id} endpoint to pull the model
*/ */
return this.queue.add(() => return this.queue.add(() =>
this.api this.apiInstance().then((api) =>
api
.post('v1/models/pull', { json: { model, id, name }, timeout: false }) .post('v1/models/pull', { json: { model, id, name }, timeout: false })
.json() .json()
.catch(async (e) => { .catch(async (e) => {
@ -102,6 +107,7 @@ export default class JanModelExtension extends ModelExtension {
}) })
.then() .then()
) )
)
} }
/** /**
@ -115,11 +121,13 @@ export default class JanModelExtension extends ModelExtension {
* Sending DELETE to /models/pull/{id} endpoint to cancel a model pull * Sending DELETE to /models/pull/{id} endpoint to cancel a model pull
*/ */
return this.queue.add(() => return this.queue.add(() =>
this.api this.apiInstance().then((api) =>
api
.delete('v1/models/pull', { json: { taskId: model } }) .delete('v1/models/pull', { json: { taskId: model } })
.json() .json()
.then() .then()
) )
)
} }
/** /**
@ -129,7 +137,11 @@ export default class JanModelExtension extends ModelExtension {
*/ */
async deleteModel(model: string): Promise<void> { async deleteModel(model: string): Promise<void> {
return this.queue return this.queue
.add(() => this.api.delete(`v1/models/${model}`).json().then()) .add(() =>
this.apiInstance().then((api) =>
api.delete(`v1/models/${model}`).json().then()
)
)
.catch((e) => console.debug(e)) .catch((e) => console.debug(e))
.finally(async () => { .finally(async () => {
// Delete legacy model files // Delete legacy model files
@ -231,7 +243,8 @@ export default class JanModelExtension extends ModelExtension {
async updateModel(model: Partial<Model>): Promise<Model> { async updateModel(model: Partial<Model>): Promise<Model> {
return this.queue return this.queue
.add(() => .add(() =>
this.api this.apiInstance().then((api) =>
api
.patch(`v1/models/${model.id}`, { .patch(`v1/models/${model.id}`, {
json: { ...model }, json: { ...model },
timeout: false, timeout: false,
@ -239,6 +252,7 @@ export default class JanModelExtension extends ModelExtension {
.json() .json()
.then() .then()
) )
)
.then(() => this.getModel(model.id)) .then(() => this.getModel(model.id))
} }
@ -248,10 +262,12 @@ export default class JanModelExtension extends ModelExtension {
*/ */
async getModel(model: string): Promise<Model> { async getModel(model: string): Promise<Model> {
return this.queue.add(() => return this.queue.add(() =>
this.api this.apiInstance().then((api) =>
api
.get(`v1/models/${model}`) .get(`v1/models/${model}`)
.json() .json()
.then((e) => this.transformModel(e)) .then((e) => this.transformModel(e))
)
) as Promise<Model> ) as Promise<Model>
} }
@ -267,7 +283,8 @@ export default class JanModelExtension extends ModelExtension {
option?: OptionType option?: OptionType
): Promise<void> { ): Promise<void> {
return this.queue.add(() => return this.queue.add(() =>
this.api this.apiInstance().then((api) =>
api
.post('v1/models/import', { .post('v1/models/import', {
json: { model, modelPath, name, option }, json: { model, modelPath, name, option },
timeout: false, timeout: false,
@ -276,6 +293,7 @@ export default class JanModelExtension extends ModelExtension {
.catch((e) => console.debug(e)) // Ignore error .catch((e) => console.debug(e)) // Ignore error
.then() .then()
) )
)
} }
// BEGIN - Model Sources // BEGIN - Model Sources
@ -285,7 +303,11 @@ export default class JanModelExtension extends ModelExtension {
*/ */
async getSources(): Promise<ModelSource[]> { async getSources(): Promise<ModelSource[]> {
const sources = await this.queue const sources = await this.queue
.add(() => this.api.get('v1/models/sources').json<Data<ModelSource>>()) .add(() =>
this.apiInstance().then((api) =>
api.get('v1/models/sources').json<Data<ModelSource>>()
)
)
.then((e) => (typeof e === 'object' ? (e.data as ModelSource[]) : [])) .then((e) => (typeof e === 'object' ? (e.data as ModelSource[]) : []))
.catch(() => []) .catch(() => [])
return sources.concat( return sources.concat(
@ -299,12 +321,14 @@ export default class JanModelExtension extends ModelExtension {
*/ */
async addSource(source: string): Promise<any> { async addSource(source: string): Promise<any> {
return this.queue.add(() => return this.queue.add(() =>
this.api.post('v1/models/sources', { this.apiInstance().then((api) =>
api.post('v1/models/sources', {
json: { json: {
source, source,
}, },
}) })
) )
)
} }
/** /**
@ -313,13 +337,15 @@ export default class JanModelExtension extends ModelExtension {
*/ */
async deleteSource(source: string): Promise<any> { async deleteSource(source: string): Promise<any> {
return this.queue.add(() => return this.queue.add(() =>
this.api.delete('v1/models/sources', { this.apiInstance().then((api) =>
api.delete('v1/models/sources', {
json: { json: {
source, source,
}, },
timeout: false, timeout: false,
}) })
) )
)
} }
// END - Model Sources // END - Model Sources
@ -329,7 +355,9 @@ export default class JanModelExtension extends ModelExtension {
*/ */
async isModelLoaded(model: string): Promise<boolean> { async isModelLoaded(model: string): Promise<boolean> {
return this.queue return this.queue
.add(() => this.api.get(`v1/models/status/${model}`)) .add(() =>
this.apiInstance().then((api) => api.get(`v1/models/status/${model}`))
)
.then((e) => true) .then((e) => true)
.catch(() => false) .catch(() => false)
} }
@ -348,7 +376,11 @@ export default class JanModelExtension extends ModelExtension {
*/ */
async fetchModels(): Promise<Model[]> { async fetchModels(): Promise<Model[]> {
return this.queue return this.queue
.add(() => this.api.get('v1/models?limit=-1').json<Data<Model>>()) .add(() =>
this.apiInstance().then((api) =>
api.get('v1/models?limit=-1').json<Data<Model>>()
)
)
.then((e) => .then((e) =>
typeof e === 'object' ? e.data.map((e) => this.transformModel(e)) : [] typeof e === 'object' ? e.data.map((e) => this.transformModel(e)) : []
) )
@ -387,7 +419,11 @@ export default class JanModelExtension extends ModelExtension {
[key: string]: any [key: string]: any
}): Promise<void> { }): Promise<void> {
return this.queue return this.queue
.add(() => this.api.patch('v1/configs', { json: body }).then(() => {})) .add(() =>
this.apiInstance().then((api) =>
api.patch('v1/configs', { json: body }).then(() => {})
)
)
.catch((e) => console.debug(e)) .catch((e) => console.debug(e))
} }
@ -396,14 +432,16 @@ export default class JanModelExtension extends ModelExtension {
* @returns * @returns
*/ */
private healthz(): Promise<void> { private healthz(): Promise<void> {
return this.api return this.apiInstance()
.get('healthz', { .then((api) =>
api.get('healthz', {
retry: { retry: {
limit: 20, limit: 20,
delay: () => 500, delay: () => 500,
methods: ['get'], methods: ['get'],
}, },
}) })
)
.then(() => { .then(() => {
this.queue.concurrency = Infinity this.queue.concurrency = Infinity
}) })
@ -416,17 +454,22 @@ export default class JanModelExtension extends ModelExtension {
const models = await this.fetchModels() const models = await this.fetchModels()
return this.queue.add(() => return this.queue.add(() =>
this.api this.apiInstance()
.then((api) =>
api
.get('v1/models/hub?author=cortexso&tag=cortex.cpp') .get('v1/models/hub?author=cortexso&tag=cortex.cpp')
.json<Data<string>>() .json<Data<string>>()
.then((e) => { .then((e) => {
e.data?.forEach((model) => { e.data?.forEach((model) => {
if ( if (
!models.some((e) => 'modelSource' in e && e.modelSource === model) !models.some(
(e) => 'modelSource' in e && e.modelSource === model
)
) )
this.addSource(model).catch((e) => console.debug(e)) this.addSource(model).catch((e) => console.debug(e))
}) })
}) })
)
.catch((e) => console.debug(e)) .catch((e) => console.debug(e))
) )
} }

View File

@ -6,13 +6,14 @@ import { useState } from 'react'
import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd' import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd'
import { Progress, ScrollArea, Switch } from '@janhq/joi' import { Progress, ScrollArea, Switch } from '@janhq/joi'
import { useAtom, useAtomValue } from 'jotai' import { useAtom, useAtomValue, useSetAtom } from 'jotai'
import { atomWithStorage } from 'jotai/utils' import { atomWithStorage } from 'jotai/utils'
import { ChevronDownIcon, GripVerticalIcon } from 'lucide-react' import { ChevronDownIcon, GripVerticalIcon } from 'lucide-react'
import { twMerge } from 'tailwind-merge' import { twMerge } from 'tailwind-merge'
import { activeModelAtom } from '@/hooks/useActiveModel'
import { import {
useGetHardwareInfo, useGetHardwareInfo,
setActiveGpus, setActiveGpus,
@ -47,6 +48,7 @@ const Hardware = () => {
const ramUtilitized = useAtomValue(ramUtilitizedAtom) const ramUtilitized = useAtomValue(ramUtilitizedAtom)
const showScrollBar = useAtomValue(showScrollBarAtom) const showScrollBar = useAtomValue(showScrollBarAtom)
const [gpus, setGpus] = useAtom(gpusAtom) const [gpus, setGpus] = useAtom(gpusAtom)
const setActiveModel = useSetAtom(activeModelAtom)
const [orderGpus, setOrderGpus] = useAtom(orderGpusAtom) const [orderGpus, setOrderGpus] = useAtom(orderGpusAtom)
@ -70,11 +72,15 @@ const Hardware = () => {
.filter((gpu: any) => gpu.activated) .filter((gpu: any) => gpu.activated)
.map((gpu: any) => Number(gpu.id)) .map((gpu: any) => Number(gpu.id))
await setActiveGpus({ gpus: activeGpuIds }) await setActiveGpus({ gpus: activeGpuIds })
setActiveModel(undefined)
mutate() mutate()
window.location.reload()
} catch (error) { } catch (error) {
console.error('Failed to update active GPUs:', error) console.error('Failed to update active GPUs:', error)
} }
setIsActivatingGpu((prev) => {
prev.delete(id)
return new Set(prev)
})
} }
const handleDragEnd = (result: any) => { const handleDragEnd = (result: any) => {