From 90e69771dd464c825dfa0379e131860af49f7b0e Mon Sep 17 00:00:00 2001 From: marknguyen1302 Date: Thu, 4 Jul 2024 01:45:31 +0700 Subject: [PATCH 01/11] feat: add log to commands, load dependencies indicator --- cortex-js/package.json | 22 ++++++++++++------- cortex-js/src/command.ts | 6 +++-- .../commanders/models/model-start.command.ts | 4 +++- .../infrastructure/commanders/ps.command.ts | 9 ++++++-- .../commanders/shortcuts/run.command.ts | 2 ++ .../commanders/usecases/ps.cli.usecases.ts | 1 + 6 files changed, 31 insertions(+), 13 deletions(-) diff --git a/cortex-js/package.json b/cortex-js/package.json index a9f21c68a..6f8f0ca3a 100644 --- a/cortex-js/package.json +++ b/cortex-js/package.json @@ -54,9 +54,11 @@ "class-validator": "^0.14.1", "cli-progress": "^3.12.0", "cortexso-node": "^0.0.4", + "cpuinfo": "file:./cpuinfo", "decompress": "^4.2.1", "js-yaml": "^4.1.0", "nest-commander": "^3.13.0", + "ora": "5.4.1", "readline": "^1.3.0", "reflect-metadata": "^0.2.0", "rxjs": "^7.8.1", @@ -65,8 +67,7 @@ "typeorm": "^0.3.20", "ulid": "^2.3.0", "uuid": "^9.0.1", - "yaml": "^2.4.2", - "cpuinfo": "file:./cpuinfo" + "yaml": "^2.4.2" }, "devDependencies": { "@nestjs/cli": "^10.0.0", @@ -84,6 +85,8 @@ "@types/uuid": "^9.0.8", "@typescript-eslint/eslint-plugin": "^6.0.0", "@typescript-eslint/parser": "^6.0.0", + "@vercel/ncc": "^0.38.0", + "@yao-pkg/pkg": "^5.12.0", "bun": "^1.1.15", "cpx": "^1.5.0", "eslint": "^8.42.0", @@ -94,18 +97,16 @@ "jest": "^29.5.0", "nest-commander-testing": "^3.3.0", "node-gyp": "^10.1.0", + "patch-package": "^8.0.0", "prettier": "^3.0.0", + "resedit-cli": "^2.0.0", "run-script-os": "^1.1.6", "source-map-support": "^0.5.21", "supertest": "^6.3.3", "ts-jest": "^29.1.0", "ts-loader": "^9.4.3", "tsconfig-paths": "^4.2.0", - "typescript": "^5.1.3", - "patch-package": "^8.0.0", - "@yao-pkg/pkg": "^5.12.0", - "resedit-cli": "^2.0.0", - "@vercel/ncc": "^0.38.0" + "typescript": "^5.1.3" }, "resolutions": { "ajv": "8.15.0", @@ -137,7 +138,12 @@ }, "pkg": { "scripts": "command/**/*.js", - "assets": ["command/*.node", "**/package.json", "node_modules/axios/**/*", "cpuinfo/**/*"], + "assets": [ + "command/*.node", + "**/package.json", + "node_modules/axios/**/*", + "cpuinfo/**/*" + ], "outputPath": "dist" } } diff --git a/cortex-js/src/command.ts b/cortex-js/src/command.ts index 274e401b3..59175c0e6 100644 --- a/cortex-js/src/command.ts +++ b/cortex-js/src/command.ts @@ -1,12 +1,14 @@ #!/usr/bin/env node --no-warnings +import ora from 'ora'; +const dependenciesSpinner = ora('Loading dependencies...').start(); +const time = Date.now(); import { CommandFactory } from 'nest-commander'; import { CommandModule } from './command.module'; import { TelemetryUsecases } from './usecases/telemetry/telemetry.usecases'; import { TelemetrySource } from './domain/telemetry/telemetry.interface'; -import { AsyncLocalStorage } from 'async_hooks'; import { ContextService } from '@/infrastructure/services/context/context.service'; -export const asyncLocalStorage = new AsyncLocalStorage(); +dependenciesSpinner.succeed('Dependencies loaded in ' + (Date.now() - time) + 'ms'); async function bootstrap() { let telemetryUseCase: TelemetryUsecases | null = null; diff --git a/cortex-js/src/infrastructure/commanders/models/model-start.command.ts b/cortex-js/src/infrastructure/commanders/models/model-start.command.ts index d2078cc9f..b57ea649a 100644 --- a/cortex-js/src/infrastructure/commanders/models/model-start.command.ts +++ b/cortex-js/src/infrastructure/commanders/models/model-start.command.ts @@ -44,6 +44,7 @@ export class ModelStartCommand extends CommandRunner { async run(passedParams: string[], options: ModelStartOptions): Promise { let modelId = passedParams[0]; + console.log('Finding model...'); if (!modelId) { try { modelId = await this.modelInquiry(); @@ -72,13 +73,14 @@ export class ModelStartCommand extends CommandRunner { if ( !existsSync(join(await this.fileService.getCortexCppEnginePath(), engine)) ) { + console.log('Installing engine...'); await this.initUsecases.installEngine( await this.initUsecases.defaultInstallationOptions(), 'latest', engine, ); } - + console.log('Starting model...'); await this.cortexUsecases .startCortex(options.attach) .then(() => this.modelsCliUsecases.startModel(modelId, options.preset)) diff --git a/cortex-js/src/infrastructure/commanders/ps.command.ts b/cortex-js/src/infrastructure/commanders/ps.command.ts index c00bbed2c..4351831dc 100644 --- a/cortex-js/src/infrastructure/commanders/ps.command.ts +++ b/cortex-js/src/infrastructure/commanders/ps.command.ts @@ -16,12 +16,17 @@ export class PSCommand extends CommandRunner { super(); } async run(): Promise { + console.log('Running PS command...'); return this.usecases .getModels() .then(console.table) - .then(() => this.usecases.isAPIServerOnline()) + .then(() => { + console.log('Checking API server...'); + return this.usecases.isAPIServerOnline(); + }) .then((isOnline) => { if (isOnline) console.log('API server is online'); + else console.log('API server is offline'); }); } -} +} \ No newline at end of file diff --git a/cortex-js/src/infrastructure/commanders/shortcuts/run.command.ts b/cortex-js/src/infrastructure/commanders/shortcuts/run.command.ts index 0ebbca61b..f9a0bf78a 100644 --- a/cortex-js/src/infrastructure/commanders/shortcuts/run.command.ts +++ b/cortex-js/src/infrastructure/commanders/shortcuts/run.command.ts @@ -44,6 +44,7 @@ export class RunCommand extends CommandRunner { async run(passedParams: string[], options: RunOptions): Promise { let modelId = passedParams[0]; + console.log('Running model...'); if (!modelId) { try { modelId = await this.modelInquiry(); @@ -82,6 +83,7 @@ export class RunCommand extends CommandRunner { if ( !existsSync(join(await this.fileService.getCortexCppEnginePath(), engine)) ) { + console.log('Installing engine...'); await this.initUsecases.installEngine( await this.initUsecases.defaultInstallationOptions(), 'latest', diff --git a/cortex-js/src/infrastructure/commanders/usecases/ps.cli.usecases.ts b/cortex-js/src/infrastructure/commanders/usecases/ps.cli.usecases.ts index 6fad12b6b..2dba2a84d 100644 --- a/cortex-js/src/infrastructure/commanders/usecases/ps.cli.usecases.ts +++ b/cortex-js/src/infrastructure/commanders/usecases/ps.cli.usecases.ts @@ -26,6 +26,7 @@ export class PSCliUsecases { */ async getModels(): Promise { const configs = await this.fileService.getConfig(); + console.log('Getting models...'); return new Promise((resolve, reject) => firstValueFrom( this.httpService.get( From ed069562db3982a0dad9f70f3d6068947d5686dd Mon Sep 17 00:00:00 2001 From: marknguyen1302 Date: Thu, 4 Jul 2024 10:22:42 +0700 Subject: [PATCH 02/11] remove log --- .../src/infrastructure/commanders/models/model-start.command.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/cortex-js/src/infrastructure/commanders/models/model-start.command.ts b/cortex-js/src/infrastructure/commanders/models/model-start.command.ts index b57ea649a..8ec79392a 100644 --- a/cortex-js/src/infrastructure/commanders/models/model-start.command.ts +++ b/cortex-js/src/infrastructure/commanders/models/model-start.command.ts @@ -44,7 +44,6 @@ export class ModelStartCommand extends CommandRunner { async run(passedParams: string[], options: ModelStartOptions): Promise { let modelId = passedParams[0]; - console.log('Finding model...'); if (!modelId) { try { modelId = await this.modelInquiry(); From 7e047a878e4b8e88f04bb04363e0c4a28c19377d Mon Sep 17 00:00:00 2001 From: marknguyen1302 Date: Thu, 4 Jul 2024 15:10:57 +0700 Subject: [PATCH 03/11] add indicator --- .../domain/telemetry/telemetry.interface.ts | 1 + .../infrastructure/commanders/chat.command.ts | 5 ++++- .../commanders/embeddings.command.ts | 6 ++++-- .../commanders/models/model-start.command.ts | 14 +++++++------- .../infrastructure/commanders/ps.command.ts | 15 ++++++++++----- .../commanders/shortcuts/run.command.ts | 18 +++++++++++------- .../commanders/usecases/ps.cli.usecases.ts | 9 +++++++-- .../src/usecases/models/models.usecases.ts | 15 ++++++++++----- 8 files changed, 54 insertions(+), 29 deletions(-) diff --git a/cortex-js/src/domain/telemetry/telemetry.interface.ts b/cortex-js/src/domain/telemetry/telemetry.interface.ts index c897a436e..15e330586 100644 --- a/cortex-js/src/domain/telemetry/telemetry.interface.ts +++ b/cortex-js/src/domain/telemetry/telemetry.interface.ts @@ -103,6 +103,7 @@ export interface TelemetryAnonymized { sessionId: string | null; lastActiveAt?: string | null; } + export interface BenchmarkHardware { gpu: any[]; cpu: any; diff --git a/cortex-js/src/infrastructure/commanders/chat.command.ts b/cortex-js/src/infrastructure/commanders/chat.command.ts index 8b72c7b37..fb4ba0427 100644 --- a/cortex-js/src/infrastructure/commanders/chat.command.ts +++ b/cortex-js/src/infrastructure/commanders/chat.command.ts @@ -4,6 +4,7 @@ import { Option, InquirerService, } from 'nest-commander'; +import ora from 'ora'; import { ChatCliUsecases } from './usecases/chat.cli.usecases'; import { exit } from 'node:process'; import { PSCliUsecases } from './usecases/ps.cli.usecases'; @@ -48,6 +49,7 @@ export class ChatCommand extends CommandRunner { async run(passedParams: string[], options: ChatOptions): Promise { let modelId = passedParams[0]; + const checkingSpinner = ora('Checking model...').start(); // First attempt to get message from input or options // Extract input from 1 to end of array let message = options.message ?? passedParams.slice(1).join(' '); @@ -66,10 +68,11 @@ export class ChatCommand extends CommandRunner { } else if (models.length > 0) { modelId = await this.modelInquiry(models); } else { - console.error('Model ID is required'); + checkingSpinner.fail('Model ID is required'); exit(1); } } + checkingSpinner.succeed(`Model found`); if (!message) options.attach = true; const result = await this.chatCliUsecases.chat( diff --git a/cortex-js/src/infrastructure/commanders/embeddings.command.ts b/cortex-js/src/infrastructure/commanders/embeddings.command.ts index 473c84254..a04bd9839 100644 --- a/cortex-js/src/infrastructure/commanders/embeddings.command.ts +++ b/cortex-js/src/infrastructure/commanders/embeddings.command.ts @@ -4,6 +4,7 @@ import { Option, SubCommand, } from 'nest-commander'; +import ora from 'ora'; import { ModelsUsecases } from '@/usecases/models/models.usecases'; import { PSCliUsecases } from './usecases/ps.cli.usecases'; import { ChatCliUsecases } from './usecases/chat.cli.usecases'; @@ -39,6 +40,7 @@ export class EmbeddingCommand extends CommandRunner { options: EmbeddingCommandOptions, ): Promise { let modelId = passedParams[0]; + const checkingSpinner = ora('Checking model...').start(); // First attempt to get message from input or options let input: string | string[] = options.input ?? passedParams.splice(1); @@ -54,11 +56,11 @@ export class EmbeddingCommand extends CommandRunner { } else if (models.length > 0) { modelId = await this.modelInquiry(models); } else { - console.error('Model ID is required'); + checkingSpinner.fail('Model ID is required'); process.exit(1); } } - + checkingSpinner.succeed(`Model found`); return this.chatCliUsecases .embeddings(modelId, input) .then((res) => diff --git a/cortex-js/src/infrastructure/commanders/models/model-start.command.ts b/cortex-js/src/infrastructure/commanders/models/model-start.command.ts index 8ec79392a..5cec03037 100644 --- a/cortex-js/src/infrastructure/commanders/models/model-start.command.ts +++ b/cortex-js/src/infrastructure/commanders/models/model-start.command.ts @@ -4,6 +4,7 @@ import { Option, InquirerService, } from 'nest-commander'; +import ora from 'ora'; import { exit } from 'node:process'; import { ModelsCliUsecases } from '@commanders/usecases/models.cli.usecases'; import { CortexUsecases } from '@/usecases/cortex/cortex.usecases'; @@ -44,11 +45,12 @@ export class ModelStartCommand extends CommandRunner { async run(passedParams: string[], options: ModelStartOptions): Promise { let modelId = passedParams[0]; + const checkingSpinner = ora('Checking model...').start(); if (!modelId) { try { modelId = await this.modelInquiry(); } catch { - console.error('Model ID is required'); + checkingSpinner.fail('Model ID is required'); exit(1); } } @@ -59,27 +61,25 @@ export class ModelStartCommand extends CommandRunner { !Array.isArray(existingModel.files) || /^(http|https):\/\/[^/]+\/.*/.test(existingModel.files[0]) ) { - console.error( - `${modelId} not found on filesystem.\nPlease try 'cortex pull ${modelId}' first.`, - ); + checkingSpinner.fail(`Model ${modelId} not found on filesystem.\nPlease try 'cortex pull ${modelId}' first.`); process.exit(1); } checkModelCompatibility(modelId); - + checkingSpinner.succeed('Model found'); const engine = existingModel.engine || Engines.llamaCPP; // Pull engine if not exist if ( !existsSync(join(await this.fileService.getCortexCppEnginePath(), engine)) ) { - console.log('Installing engine...'); + const engineSpinner = ora('Installing engine...').start(); await this.initUsecases.installEngine( await this.initUsecases.defaultInstallationOptions(), 'latest', engine, ); + engineSpinner.succeed(); } - console.log('Starting model...'); await this.cortexUsecases .startCortex(options.attach) .then(() => this.modelsCliUsecases.startModel(modelId, options.preset)) diff --git a/cortex-js/src/infrastructure/commanders/ps.command.ts b/cortex-js/src/infrastructure/commanders/ps.command.ts index 4351831dc..5d900c328 100644 --- a/cortex-js/src/infrastructure/commanders/ps.command.ts +++ b/cortex-js/src/infrastructure/commanders/ps.command.ts @@ -1,7 +1,9 @@ +import ora from 'ora'; import { CommandRunner, SubCommand } from 'nest-commander'; import { PSCliUsecases } from './usecases/ps.cli.usecases'; import { SetCommandContext } from './decorators/CommandContext'; import { ContextService } from '../services/context/context.service'; +import { ModelStat } from './types/model-stat.interface'; @SubCommand({ name: 'ps', @@ -16,17 +18,20 @@ export class PSCommand extends CommandRunner { super(); } async run(): Promise { - console.log('Running PS command...'); + const runningSpinner = ora('Running PS command...').start(); + let checkingSpinner: ora.Ora return this.usecases .getModels() - .then(console.table) + .then((models: ModelStat[]) => { + runningSpinner.succeed(); + console.table(models); + }) .then(() => { - console.log('Checking API server...'); + checkingSpinner = ora('Checking API server...').start(); return this.usecases.isAPIServerOnline(); }) .then((isOnline) => { - if (isOnline) console.log('API server is online'); - else console.log('API server is offline'); + checkingSpinner.succeed(isOnline ? 'API server is online' : 'API server is offline'); }); } } \ No newline at end of file diff --git a/cortex-js/src/infrastructure/commanders/shortcuts/run.command.ts b/cortex-js/src/infrastructure/commanders/shortcuts/run.command.ts index f9a0bf78a..8e2f2464c 100644 --- a/cortex-js/src/infrastructure/commanders/shortcuts/run.command.ts +++ b/cortex-js/src/infrastructure/commanders/shortcuts/run.command.ts @@ -6,6 +6,7 @@ import { InquirerService, } from 'nest-commander'; import { exit } from 'node:process'; +import ora from 'ora'; import { ChatCliUsecases } from '@commanders/usecases/chat.cli.usecases'; import { ModelsCliUsecases } from '@commanders/usecases/models.cli.usecases'; import { ModelNotFoundException } from '@/infrastructure/exception/model-not-found.exception'; @@ -44,12 +45,12 @@ export class RunCommand extends CommandRunner { async run(passedParams: string[], options: RunOptions): Promise { let modelId = passedParams[0]; - console.log('Running model...'); + const checkingSpinner = ora('Checking model...').start(); if (!modelId) { try { modelId = await this.modelInquiry(); } catch { - console.error('Model ID is required'); + checkingSpinner.fail('Model ID is required'); exit(1); } } @@ -58,8 +59,8 @@ export class RunCommand extends CommandRunner { if (!(await this.modelsCliUsecases.getModel(modelId))) { await this.modelsCliUsecases.pullModel(modelId).catch((e: Error) => { if (e instanceof ModelNotFoundException) - console.error('Model does not exist.'); - else console.error(e.message ?? e); + checkingSpinner.fail('Model does not exist.'); + else checkingSpinner.fail(e.message ?? e); exit(1); }); } @@ -71,24 +72,27 @@ export class RunCommand extends CommandRunner { !Array.isArray(existingModel.files) || /^(http|https):\/\/[^/]+\/.*/.test(existingModel.files[0]) ) { - console.error('Model is not available.'); + checkingSpinner.fail( + `Model is not available` + ); process.exit(1); } // Check model compatibility on this machine checkModelCompatibility(modelId); - + checkingSpinner.succeed('Model found') const engine = existingModel.engine || Engines.llamaCPP; // Pull engine if not exist if ( !existsSync(join(await this.fileService.getCortexCppEnginePath(), engine)) ) { - console.log('Installing engine...'); + const engineSpinner = ora('Installing engine...').start(); await this.initUsecases.installEngine( await this.initUsecases.defaultInstallationOptions(), 'latest', engine, ); + engineSpinner.succeed('Engine installed'); } return this.cortexUsecases diff --git a/cortex-js/src/infrastructure/commanders/usecases/ps.cli.usecases.ts b/cortex-js/src/infrastructure/commanders/usecases/ps.cli.usecases.ts index 2dba2a84d..20d68fc01 100644 --- a/cortex-js/src/infrastructure/commanders/usecases/ps.cli.usecases.ts +++ b/cortex-js/src/infrastructure/commanders/usecases/ps.cli.usecases.ts @@ -1,4 +1,5 @@ import { HttpStatus, Injectable } from '@nestjs/common'; +import ora from 'ora'; import { CORTEX_CPP_MODELS_URL, CORTEX_JS_HEALTH_URL, @@ -26,7 +27,7 @@ export class PSCliUsecases { */ async getModels(): Promise { const configs = await this.fileService.getConfig(); - console.log('Getting models...'); + const runningSpinner = ora('Getting models...').start(); return new Promise((resolve, reject) => firstValueFrom( this.httpService.get( @@ -41,6 +42,7 @@ export class PSCliUsecases { Array.isArray(data.data) && data.data.length > 0 ) { + runningSpinner.succeed(); resolve( data.data.map((e) => { const startTime = e.start_time ?? new Date(); @@ -60,7 +62,10 @@ export class PSCliUsecases { } else reject(); }) .catch(reject), - ).catch(() => []); + ).catch(() => { + runningSpinner.succeed(''); + return []; + }); } /** diff --git a/cortex-js/src/usecases/models/models.usecases.ts b/cortex-js/src/usecases/models/models.usecases.ts index b6b08a3d0..382201ecf 100644 --- a/cortex-js/src/usecases/models/models.usecases.ts +++ b/cortex-js/src/usecases/models/models.usecases.ts @@ -1,3 +1,4 @@ +import ora from 'ora'; import { CreateModelDto } from '@/infrastructure/dtos/models/create-model.dto'; import { UpdateModelDto } from '@/infrastructure/dtos/models/update-model.dto'; import { BadRequestException, Injectable } from '@nestjs/common'; @@ -163,7 +164,7 @@ export class ModelsUsecases { modelId, }; } - console.log('Loading model...'); + const loadingModelSpinner = ora('Loading model...').start(); // update states and emitting event this.activeModelStatuses[modelId] = { model: modelId, @@ -210,11 +211,15 @@ export class ModelsUsecases { }; this.eventEmitter.emit('model.event', modelEvent); }) - .then(() => ({ - message: 'Model loaded successfully', - modelId, - })) + .then(() => { + loadingModelSpinner.succeed('Model loaded'); + return { + message: 'Model loaded successfully', + modelId, + }; + }) .catch(async (e) => { + loadingModelSpinner.fail('Model loading failed'); // remove the model from this.activeModelStatus. delete this.activeModelStatuses[modelId]; const modelEvent: ModelEvent = { From bae68dd786d33c86b220fc4e92e223a585544901 Mon Sep 17 00:00:00 2001 From: marknguyen1302 Date: Thu, 4 Jul 2024 01:45:31 +0700 Subject: [PATCH 04/11] feat: add log to commands, load dependencies indicator --- cortex-js/package.json | 1 + cortex-js/src/command.ts | 6 ++++-- .../commanders/models/model-start.command.ts | 4 +++- cortex-js/src/infrastructure/commanders/ps.command.ts | 9 +++++++-- .../infrastructure/commanders/shortcuts/run.command.ts | 2 ++ .../commanders/usecases/ps.cli.usecases.ts | 1 + 6 files changed, 18 insertions(+), 5 deletions(-) diff --git a/cortex-js/package.json b/cortex-js/package.json index 23383a1c6..ca45aa0c3 100644 --- a/cortex-js/package.json +++ b/cortex-js/package.json @@ -56,6 +56,7 @@ "decompress": "^4.2.1", "js-yaml": "^4.1.0", "nest-commander": "^3.13.0", + "ora": "5.4.1", "readline": "^1.3.0", "reflect-metadata": "^0.2.0", "rxjs": "^7.8.1", diff --git a/cortex-js/src/command.ts b/cortex-js/src/command.ts index 274e401b3..59175c0e6 100644 --- a/cortex-js/src/command.ts +++ b/cortex-js/src/command.ts @@ -1,12 +1,14 @@ #!/usr/bin/env node --no-warnings +import ora from 'ora'; +const dependenciesSpinner = ora('Loading dependencies...').start(); +const time = Date.now(); import { CommandFactory } from 'nest-commander'; import { CommandModule } from './command.module'; import { TelemetryUsecases } from './usecases/telemetry/telemetry.usecases'; import { TelemetrySource } from './domain/telemetry/telemetry.interface'; -import { AsyncLocalStorage } from 'async_hooks'; import { ContextService } from '@/infrastructure/services/context/context.service'; -export const asyncLocalStorage = new AsyncLocalStorage(); +dependenciesSpinner.succeed('Dependencies loaded in ' + (Date.now() - time) + 'ms'); async function bootstrap() { let telemetryUseCase: TelemetryUsecases | null = null; diff --git a/cortex-js/src/infrastructure/commanders/models/model-start.command.ts b/cortex-js/src/infrastructure/commanders/models/model-start.command.ts index d2078cc9f..b57ea649a 100644 --- a/cortex-js/src/infrastructure/commanders/models/model-start.command.ts +++ b/cortex-js/src/infrastructure/commanders/models/model-start.command.ts @@ -44,6 +44,7 @@ export class ModelStartCommand extends CommandRunner { async run(passedParams: string[], options: ModelStartOptions): Promise { let modelId = passedParams[0]; + console.log('Finding model...'); if (!modelId) { try { modelId = await this.modelInquiry(); @@ -72,13 +73,14 @@ export class ModelStartCommand extends CommandRunner { if ( !existsSync(join(await this.fileService.getCortexCppEnginePath(), engine)) ) { + console.log('Installing engine...'); await this.initUsecases.installEngine( await this.initUsecases.defaultInstallationOptions(), 'latest', engine, ); } - + console.log('Starting model...'); await this.cortexUsecases .startCortex(options.attach) .then(() => this.modelsCliUsecases.startModel(modelId, options.preset)) diff --git a/cortex-js/src/infrastructure/commanders/ps.command.ts b/cortex-js/src/infrastructure/commanders/ps.command.ts index c00bbed2c..4351831dc 100644 --- a/cortex-js/src/infrastructure/commanders/ps.command.ts +++ b/cortex-js/src/infrastructure/commanders/ps.command.ts @@ -16,12 +16,17 @@ export class PSCommand extends CommandRunner { super(); } async run(): Promise { + console.log('Running PS command...'); return this.usecases .getModels() .then(console.table) - .then(() => this.usecases.isAPIServerOnline()) + .then(() => { + console.log('Checking API server...'); + return this.usecases.isAPIServerOnline(); + }) .then((isOnline) => { if (isOnline) console.log('API server is online'); + else console.log('API server is offline'); }); } -} +} \ No newline at end of file diff --git a/cortex-js/src/infrastructure/commanders/shortcuts/run.command.ts b/cortex-js/src/infrastructure/commanders/shortcuts/run.command.ts index 0ebbca61b..f9a0bf78a 100644 --- a/cortex-js/src/infrastructure/commanders/shortcuts/run.command.ts +++ b/cortex-js/src/infrastructure/commanders/shortcuts/run.command.ts @@ -44,6 +44,7 @@ export class RunCommand extends CommandRunner { async run(passedParams: string[], options: RunOptions): Promise { let modelId = passedParams[0]; + console.log('Running model...'); if (!modelId) { try { modelId = await this.modelInquiry(); @@ -82,6 +83,7 @@ export class RunCommand extends CommandRunner { if ( !existsSync(join(await this.fileService.getCortexCppEnginePath(), engine)) ) { + console.log('Installing engine...'); await this.initUsecases.installEngine( await this.initUsecases.defaultInstallationOptions(), 'latest', diff --git a/cortex-js/src/infrastructure/commanders/usecases/ps.cli.usecases.ts b/cortex-js/src/infrastructure/commanders/usecases/ps.cli.usecases.ts index 6fad12b6b..2dba2a84d 100644 --- a/cortex-js/src/infrastructure/commanders/usecases/ps.cli.usecases.ts +++ b/cortex-js/src/infrastructure/commanders/usecases/ps.cli.usecases.ts @@ -26,6 +26,7 @@ export class PSCliUsecases { */ async getModels(): Promise { const configs = await this.fileService.getConfig(); + console.log('Getting models...'); return new Promise((resolve, reject) => firstValueFrom( this.httpService.get( From 0ca858fd9ac30644e596c50e54d28dce0a99dda7 Mon Sep 17 00:00:00 2001 From: marknguyen1302 Date: Thu, 4 Jul 2024 10:22:42 +0700 Subject: [PATCH 05/11] remove log --- .../src/infrastructure/commanders/models/model-start.command.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/cortex-js/src/infrastructure/commanders/models/model-start.command.ts b/cortex-js/src/infrastructure/commanders/models/model-start.command.ts index b57ea649a..8ec79392a 100644 --- a/cortex-js/src/infrastructure/commanders/models/model-start.command.ts +++ b/cortex-js/src/infrastructure/commanders/models/model-start.command.ts @@ -44,7 +44,6 @@ export class ModelStartCommand extends CommandRunner { async run(passedParams: string[], options: ModelStartOptions): Promise { let modelId = passedParams[0]; - console.log('Finding model...'); if (!modelId) { try { modelId = await this.modelInquiry(); From 6d3b43f6d6d4aa18fefc6177f1be1059ec2f4392 Mon Sep 17 00:00:00 2001 From: marknguyen1302 Date: Thu, 4 Jul 2024 15:10:57 +0700 Subject: [PATCH 06/11] add indicator --- .../domain/telemetry/telemetry.interface.ts | 1 + .../infrastructure/commanders/chat.command.ts | 5 ++++- .../commanders/embeddings.command.ts | 6 ++++-- .../commanders/models/model-start.command.ts | 14 +++++++------- .../infrastructure/commanders/ps.command.ts | 15 ++++++++++----- .../commanders/shortcuts/run.command.ts | 18 +++++++++++------- .../commanders/usecases/ps.cli.usecases.ts | 9 +++++++-- .../src/usecases/models/models.usecases.ts | 15 ++++++++++----- 8 files changed, 54 insertions(+), 29 deletions(-) diff --git a/cortex-js/src/domain/telemetry/telemetry.interface.ts b/cortex-js/src/domain/telemetry/telemetry.interface.ts index c897a436e..15e330586 100644 --- a/cortex-js/src/domain/telemetry/telemetry.interface.ts +++ b/cortex-js/src/domain/telemetry/telemetry.interface.ts @@ -103,6 +103,7 @@ export interface TelemetryAnonymized { sessionId: string | null; lastActiveAt?: string | null; } + export interface BenchmarkHardware { gpu: any[]; cpu: any; diff --git a/cortex-js/src/infrastructure/commanders/chat.command.ts b/cortex-js/src/infrastructure/commanders/chat.command.ts index 8b72c7b37..fb4ba0427 100644 --- a/cortex-js/src/infrastructure/commanders/chat.command.ts +++ b/cortex-js/src/infrastructure/commanders/chat.command.ts @@ -4,6 +4,7 @@ import { Option, InquirerService, } from 'nest-commander'; +import ora from 'ora'; import { ChatCliUsecases } from './usecases/chat.cli.usecases'; import { exit } from 'node:process'; import { PSCliUsecases } from './usecases/ps.cli.usecases'; @@ -48,6 +49,7 @@ export class ChatCommand extends CommandRunner { async run(passedParams: string[], options: ChatOptions): Promise { let modelId = passedParams[0]; + const checkingSpinner = ora('Checking model...').start(); // First attempt to get message from input or options // Extract input from 1 to end of array let message = options.message ?? passedParams.slice(1).join(' '); @@ -66,10 +68,11 @@ export class ChatCommand extends CommandRunner { } else if (models.length > 0) { modelId = await this.modelInquiry(models); } else { - console.error('Model ID is required'); + checkingSpinner.fail('Model ID is required'); exit(1); } } + checkingSpinner.succeed(`Model found`); if (!message) options.attach = true; const result = await this.chatCliUsecases.chat( diff --git a/cortex-js/src/infrastructure/commanders/embeddings.command.ts b/cortex-js/src/infrastructure/commanders/embeddings.command.ts index 473c84254..a04bd9839 100644 --- a/cortex-js/src/infrastructure/commanders/embeddings.command.ts +++ b/cortex-js/src/infrastructure/commanders/embeddings.command.ts @@ -4,6 +4,7 @@ import { Option, SubCommand, } from 'nest-commander'; +import ora from 'ora'; import { ModelsUsecases } from '@/usecases/models/models.usecases'; import { PSCliUsecases } from './usecases/ps.cli.usecases'; import { ChatCliUsecases } from './usecases/chat.cli.usecases'; @@ -39,6 +40,7 @@ export class EmbeddingCommand extends CommandRunner { options: EmbeddingCommandOptions, ): Promise { let modelId = passedParams[0]; + const checkingSpinner = ora('Checking model...').start(); // First attempt to get message from input or options let input: string | string[] = options.input ?? passedParams.splice(1); @@ -54,11 +56,11 @@ export class EmbeddingCommand extends CommandRunner { } else if (models.length > 0) { modelId = await this.modelInquiry(models); } else { - console.error('Model ID is required'); + checkingSpinner.fail('Model ID is required'); process.exit(1); } } - + checkingSpinner.succeed(`Model found`); return this.chatCliUsecases .embeddings(modelId, input) .then((res) => diff --git a/cortex-js/src/infrastructure/commanders/models/model-start.command.ts b/cortex-js/src/infrastructure/commanders/models/model-start.command.ts index 8ec79392a..5cec03037 100644 --- a/cortex-js/src/infrastructure/commanders/models/model-start.command.ts +++ b/cortex-js/src/infrastructure/commanders/models/model-start.command.ts @@ -4,6 +4,7 @@ import { Option, InquirerService, } from 'nest-commander'; +import ora from 'ora'; import { exit } from 'node:process'; import { ModelsCliUsecases } from '@commanders/usecases/models.cli.usecases'; import { CortexUsecases } from '@/usecases/cortex/cortex.usecases'; @@ -44,11 +45,12 @@ export class ModelStartCommand extends CommandRunner { async run(passedParams: string[], options: ModelStartOptions): Promise { let modelId = passedParams[0]; + const checkingSpinner = ora('Checking model...').start(); if (!modelId) { try { modelId = await this.modelInquiry(); } catch { - console.error('Model ID is required'); + checkingSpinner.fail('Model ID is required'); exit(1); } } @@ -59,27 +61,25 @@ export class ModelStartCommand extends CommandRunner { !Array.isArray(existingModel.files) || /^(http|https):\/\/[^/]+\/.*/.test(existingModel.files[0]) ) { - console.error( - `${modelId} not found on filesystem.\nPlease try 'cortex pull ${modelId}' first.`, - ); + checkingSpinner.fail(`Model ${modelId} not found on filesystem.\nPlease try 'cortex pull ${modelId}' first.`); process.exit(1); } checkModelCompatibility(modelId); - + checkingSpinner.succeed('Model found'); const engine = existingModel.engine || Engines.llamaCPP; // Pull engine if not exist if ( !existsSync(join(await this.fileService.getCortexCppEnginePath(), engine)) ) { - console.log('Installing engine...'); + const engineSpinner = ora('Installing engine...').start(); await this.initUsecases.installEngine( await this.initUsecases.defaultInstallationOptions(), 'latest', engine, ); + engineSpinner.succeed(); } - console.log('Starting model...'); await this.cortexUsecases .startCortex(options.attach) .then(() => this.modelsCliUsecases.startModel(modelId, options.preset)) diff --git a/cortex-js/src/infrastructure/commanders/ps.command.ts b/cortex-js/src/infrastructure/commanders/ps.command.ts index 4351831dc..5d900c328 100644 --- a/cortex-js/src/infrastructure/commanders/ps.command.ts +++ b/cortex-js/src/infrastructure/commanders/ps.command.ts @@ -1,7 +1,9 @@ +import ora from 'ora'; import { CommandRunner, SubCommand } from 'nest-commander'; import { PSCliUsecases } from './usecases/ps.cli.usecases'; import { SetCommandContext } from './decorators/CommandContext'; import { ContextService } from '../services/context/context.service'; +import { ModelStat } from './types/model-stat.interface'; @SubCommand({ name: 'ps', @@ -16,17 +18,20 @@ export class PSCommand extends CommandRunner { super(); } async run(): Promise { - console.log('Running PS command...'); + const runningSpinner = ora('Running PS command...').start(); + let checkingSpinner: ora.Ora return this.usecases .getModels() - .then(console.table) + .then((models: ModelStat[]) => { + runningSpinner.succeed(); + console.table(models); + }) .then(() => { - console.log('Checking API server...'); + checkingSpinner = ora('Checking API server...').start(); return this.usecases.isAPIServerOnline(); }) .then((isOnline) => { - if (isOnline) console.log('API server is online'); - else console.log('API server is offline'); + checkingSpinner.succeed(isOnline ? 'API server is online' : 'API server is offline'); }); } } \ No newline at end of file diff --git a/cortex-js/src/infrastructure/commanders/shortcuts/run.command.ts b/cortex-js/src/infrastructure/commanders/shortcuts/run.command.ts index f9a0bf78a..8e2f2464c 100644 --- a/cortex-js/src/infrastructure/commanders/shortcuts/run.command.ts +++ b/cortex-js/src/infrastructure/commanders/shortcuts/run.command.ts @@ -6,6 +6,7 @@ import { InquirerService, } from 'nest-commander'; import { exit } from 'node:process'; +import ora from 'ora'; import { ChatCliUsecases } from '@commanders/usecases/chat.cli.usecases'; import { ModelsCliUsecases } from '@commanders/usecases/models.cli.usecases'; import { ModelNotFoundException } from '@/infrastructure/exception/model-not-found.exception'; @@ -44,12 +45,12 @@ export class RunCommand extends CommandRunner { async run(passedParams: string[], options: RunOptions): Promise { let modelId = passedParams[0]; - console.log('Running model...'); + const checkingSpinner = ora('Checking model...').start(); if (!modelId) { try { modelId = await this.modelInquiry(); } catch { - console.error('Model ID is required'); + checkingSpinner.fail('Model ID is required'); exit(1); } } @@ -58,8 +59,8 @@ export class RunCommand extends CommandRunner { if (!(await this.modelsCliUsecases.getModel(modelId))) { await this.modelsCliUsecases.pullModel(modelId).catch((e: Error) => { if (e instanceof ModelNotFoundException) - console.error('Model does not exist.'); - else console.error(e.message ?? e); + checkingSpinner.fail('Model does not exist.'); + else checkingSpinner.fail(e.message ?? e); exit(1); }); } @@ -71,24 +72,27 @@ export class RunCommand extends CommandRunner { !Array.isArray(existingModel.files) || /^(http|https):\/\/[^/]+\/.*/.test(existingModel.files[0]) ) { - console.error('Model is not available.'); + checkingSpinner.fail( + `Model is not available` + ); process.exit(1); } // Check model compatibility on this machine checkModelCompatibility(modelId); - + checkingSpinner.succeed('Model found') const engine = existingModel.engine || Engines.llamaCPP; // Pull engine if not exist if ( !existsSync(join(await this.fileService.getCortexCppEnginePath(), engine)) ) { - console.log('Installing engine...'); + const engineSpinner = ora('Installing engine...').start(); await this.initUsecases.installEngine( await this.initUsecases.defaultInstallationOptions(), 'latest', engine, ); + engineSpinner.succeed('Engine installed'); } return this.cortexUsecases diff --git a/cortex-js/src/infrastructure/commanders/usecases/ps.cli.usecases.ts b/cortex-js/src/infrastructure/commanders/usecases/ps.cli.usecases.ts index 2dba2a84d..20d68fc01 100644 --- a/cortex-js/src/infrastructure/commanders/usecases/ps.cli.usecases.ts +++ b/cortex-js/src/infrastructure/commanders/usecases/ps.cli.usecases.ts @@ -1,4 +1,5 @@ import { HttpStatus, Injectable } from '@nestjs/common'; +import ora from 'ora'; import { CORTEX_CPP_MODELS_URL, CORTEX_JS_HEALTH_URL, @@ -26,7 +27,7 @@ export class PSCliUsecases { */ async getModels(): Promise { const configs = await this.fileService.getConfig(); - console.log('Getting models...'); + const runningSpinner = ora('Getting models...').start(); return new Promise((resolve, reject) => firstValueFrom( this.httpService.get( @@ -41,6 +42,7 @@ export class PSCliUsecases { Array.isArray(data.data) && data.data.length > 0 ) { + runningSpinner.succeed(); resolve( data.data.map((e) => { const startTime = e.start_time ?? new Date(); @@ -60,7 +62,10 @@ export class PSCliUsecases { } else reject(); }) .catch(reject), - ).catch(() => []); + ).catch(() => { + runningSpinner.succeed(''); + return []; + }); } /** diff --git a/cortex-js/src/usecases/models/models.usecases.ts b/cortex-js/src/usecases/models/models.usecases.ts index b6b08a3d0..382201ecf 100644 --- a/cortex-js/src/usecases/models/models.usecases.ts +++ b/cortex-js/src/usecases/models/models.usecases.ts @@ -1,3 +1,4 @@ +import ora from 'ora'; import { CreateModelDto } from '@/infrastructure/dtos/models/create-model.dto'; import { UpdateModelDto } from '@/infrastructure/dtos/models/update-model.dto'; import { BadRequestException, Injectable } from '@nestjs/common'; @@ -163,7 +164,7 @@ export class ModelsUsecases { modelId, }; } - console.log('Loading model...'); + const loadingModelSpinner = ora('Loading model...').start(); // update states and emitting event this.activeModelStatuses[modelId] = { model: modelId, @@ -210,11 +211,15 @@ export class ModelsUsecases { }; this.eventEmitter.emit('model.event', modelEvent); }) - .then(() => ({ - message: 'Model loaded successfully', - modelId, - })) + .then(() => { + loadingModelSpinner.succeed('Model loaded'); + return { + message: 'Model loaded successfully', + modelId, + }; + }) .catch(async (e) => { + loadingModelSpinner.fail('Model loading failed'); // remove the model from this.activeModelStatus. delete this.activeModelStatuses[modelId]; const modelEvent: ModelEvent = { From ff6b838766dd2d97753272befe5cb62bc06a62f2 Mon Sep 17 00:00:00 2001 From: marknguyen1302 Date: Thu, 4 Jul 2024 15:58:43 +0700 Subject: [PATCH 07/11] fix log --- .../infrastructure/commanders/shortcuts/run.command.ts | 8 ++++---- cortex-js/src/usecases/models/models.usecases.ts | 6 +++++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/cortex-js/src/infrastructure/commanders/shortcuts/run.command.ts b/cortex-js/src/infrastructure/commanders/shortcuts/run.command.ts index 8e2f2464c..0fa96a941 100644 --- a/cortex-js/src/infrastructure/commanders/shortcuts/run.command.ts +++ b/cortex-js/src/infrastructure/commanders/shortcuts/run.command.ts @@ -57,10 +57,11 @@ export class RunCommand extends CommandRunner { // If not exist // Try Pull if (!(await this.modelsCliUsecases.getModel(modelId))) { + checkingSpinner.succeed('Model not found. Attempting to pull...'); await this.modelsCliUsecases.pullModel(modelId).catch((e: Error) => { if (e instanceof ModelNotFoundException) - checkingSpinner.fail('Model does not exist.'); - else checkingSpinner.fail(e.message ?? e); + console.error('Model does not exist.'); + else console.error(e.message ?? e); exit(1); }); } @@ -72,7 +73,7 @@ export class RunCommand extends CommandRunner { !Array.isArray(existingModel.files) || /^(http|https):\/\/[^/]+\/.*/.test(existingModel.files[0]) ) { - checkingSpinner.fail( + console.error( `Model is not available` ); process.exit(1); @@ -80,7 +81,6 @@ export class RunCommand extends CommandRunner { // Check model compatibility on this machine checkModelCompatibility(modelId); - checkingSpinner.succeed('Model found') const engine = existingModel.engine || Engines.llamaCPP; // Pull engine if not exist if ( diff --git a/cortex-js/src/usecases/models/models.usecases.ts b/cortex-js/src/usecases/models/models.usecases.ts index 382201ecf..e6cf32132 100644 --- a/cortex-js/src/usecases/models/models.usecases.ts +++ b/cortex-js/src/usecases/models/models.usecases.ts @@ -219,6 +219,7 @@ export class ModelsUsecases { }; }) .catch(async (e) => { + console.error(e); loadingModelSpinner.fail('Model loading failed'); // remove the model from this.activeModelStatus. delete this.activeModelStatuses[modelId]; @@ -364,7 +365,9 @@ export class ModelsUsecases { toDownloads, // Post processing async () => { - console.log('Update model metadata...'); + const uploadModelMetadataSpiner = ora( + 'Updating model metadata...', + ).start(); // Post processing after download if (existsSync(join(modelFolder, 'model.yml'))) { const model: CreateModelDto = load( @@ -414,6 +417,7 @@ export class ModelsUsecases { }); } } + uploadModelMetadataSpiner.succeed('Model metadata updated'); const modelEvent: ModelEvent = { model: modelId, event: 'model-downloaded', From adab32957751c6eabd51377fd50b327d8264afd2 Mon Sep 17 00:00:00 2001 From: marknguyen1302 Date: Thu, 4 Jul 2024 16:24:48 +0700 Subject: [PATCH 08/11] fix wrong log --- .../src/infrastructure/commanders/shortcuts/run.command.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cortex-js/src/infrastructure/commanders/shortcuts/run.command.ts b/cortex-js/src/infrastructure/commanders/shortcuts/run.command.ts index 8f08454df..5c6e96ffe 100644 --- a/cortex-js/src/infrastructure/commanders/shortcuts/run.command.ts +++ b/cortex-js/src/infrastructure/commanders/shortcuts/run.command.ts @@ -73,11 +73,12 @@ export class RunCommand extends CommandRunner { !Array.isArray(existingModel.files) || /^(http|https):\/\/[^/]+\/.*/.test(existingModel.files[0]) ) { - console.error( + checkingSpinner.fail( `Model is not available` ); process.exit(1); } + checkingSpinner.succeed('Model found'); // Check model compatibility on this machine checkModelCompatibility(modelId); From 19e651c87d75dad662cd7fd3d71546273c326baf Mon Sep 17 00:00:00 2001 From: Louis Date: Thu, 4 Jul 2024 16:41:05 +0700 Subject: [PATCH 09/11] chore: remove --no-warning --- cortex-js/src/command.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cortex-js/src/command.ts b/cortex-js/src/command.ts index 59175c0e6..1573a2a38 100644 --- a/cortex-js/src/command.ts +++ b/cortex-js/src/command.ts @@ -1,4 +1,4 @@ -#!/usr/bin/env node --no-warnings +#!/usr/bin/env node import ora from 'ora'; const dependenciesSpinner = ora('Loading dependencies...').start(); const time = Date.now(); From b84f622878bdd9004763b233358d9b9828001250 Mon Sep 17 00:00:00 2001 From: Louis Date: Thu, 4 Jul 2024 16:42:38 +0700 Subject: [PATCH 10/11] chore: fix warning --- cortex-js/src/usecases/models/models.usecases.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cortex-js/src/usecases/models/models.usecases.ts b/cortex-js/src/usecases/models/models.usecases.ts index 629083c2b..d98f0022f 100644 --- a/cortex-js/src/usecases/models/models.usecases.ts +++ b/cortex-js/src/usecases/models/models.usecases.ts @@ -5,7 +5,7 @@ import { BadRequestException, Injectable } from '@nestjs/common'; import { Model, ModelSettingParams } from '@/domain/models/model.interface'; import { ModelNotFoundException } from '@/infrastructure/exception/model-not-found.exception'; import { basename, join } from 'path'; -import { promises, existsSync, mkdirSync, rmdirSync, readFileSync } from 'fs'; +import { promises, existsSync, mkdirSync, readFileSync, rmSync } from 'fs'; import { StartModelSuccessDto } from '@/infrastructure/dtos/models/start-model-success.dto'; import { ExtensionRepository } from '@/domain/repositories/extension.interface'; import { EngineExtension } from '@/domain/abstracts/engine.abstract'; @@ -125,7 +125,7 @@ export class ModelsUsecases { .then( () => existsSync(modelFolder) && - rmdirSync(modelFolder, { recursive: true }), + rmSync(modelFolder, { recursive: true }), ) .then(() => { const modelEvent: ModelEvent = { From bd68647b5f7f9e9102c68b99729aba48be0c062a Mon Sep 17 00:00:00 2001 From: Louis Date: Thu, 4 Jul 2024 16:45:24 +0700 Subject: [PATCH 11/11] fix: model loading failed on already loaded model --- cortex-js/package.json | 2 +- cortex-js/src/usecases/models/models.usecases.ts | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/cortex-js/package.json b/cortex-js/package.json index ca45aa0c3..a1fc603e3 100644 --- a/cortex-js/package.json +++ b/cortex-js/package.json @@ -52,7 +52,7 @@ "class-validator": "^0.14.1", "cli-progress": "^3.12.0", "cortexso-node": "^0.0.4", - "cpu-instructions": "^0.0.10", + "cpu-instructions": "^0.0.11", "decompress": "^4.2.1", "js-yaml": "^4.1.0", "nest-commander": "^3.13.0", diff --git a/cortex-js/src/usecases/models/models.usecases.ts b/cortex-js/src/usecases/models/models.usecases.ts index d98f0022f..6d714799d 100644 --- a/cortex-js/src/usecases/models/models.usecases.ts +++ b/cortex-js/src/usecases/models/models.usecases.ts @@ -124,8 +124,7 @@ export class ModelsUsecases { .remove(id) .then( () => - existsSync(modelFolder) && - rmSync(modelFolder, { recursive: true }), + existsSync(modelFolder) && rmSync(modelFolder, { recursive: true }), ) .then(() => { const modelEvent: ModelEvent = { @@ -219,7 +218,6 @@ export class ModelsUsecases { }; }) .catch(async (e) => { - loadingModelSpinner.fail('Model loading failed'); // remove the model from this.activeModelStatus. delete this.activeModelStatuses[modelId]; const modelEvent: ModelEvent = { @@ -234,6 +232,7 @@ export class ModelsUsecases { modelId, }; } + loadingModelSpinner.fail('Model loading failed'); await this.telemetryUseCases.createCrashReport( e, TelemetrySource.CORTEX_CPP,