Skip to content
This repository was archived by the owner on Jul 4, 2025. It is now read-only.

Commit 7924e66

Browse files
feat: add log to commands, load dependencies indicator (#821)
Co-authored-by: Louis <louis@jan.ai>
1 parent 10cf4b3 commit 7924e66

File tree

10 files changed

+72
-32
lines changed

10 files changed

+72
-32
lines changed

cortex-js/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,11 @@
5252
"class-validator": "^0.14.1",
5353
"cli-progress": "^3.12.0",
5454
"cortexso-node": "^0.0.4",
55-
"cpu-instructions": "^0.0.10",
55+
"cpu-instructions": "^0.0.11",
5656
"decompress": "^4.2.1",
5757
"js-yaml": "^4.1.0",
5858
"nest-commander": "^3.13.0",
59+
"ora": "5.4.1",
5960
"readline": "^1.3.0",
6061
"reflect-metadata": "^0.2.0",
6162
"rxjs": "^7.8.1",

cortex-js/src/command.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1-
#!/usr/bin/env node --no-warnings
1+
#!/usr/bin/env node
2+
import ora from 'ora';
3+
const dependenciesSpinner = ora('Loading dependencies...').start();
4+
const time = Date.now();
25
import { CommandFactory } from 'nest-commander';
36
import { CommandModule } from './command.module';
47
import { TelemetryUsecases } from './usecases/telemetry/telemetry.usecases';
58
import { TelemetrySource } from './domain/telemetry/telemetry.interface';
6-
import { AsyncLocalStorage } from 'async_hooks';
79
import { ContextService } from '@/infrastructure/services/context/context.service';
810

9-
export const asyncLocalStorage = new AsyncLocalStorage();
11+
dependenciesSpinner.succeed('Dependencies loaded in ' + (Date.now() - time) + 'ms');
1012

1113
async function bootstrap() {
1214
let telemetryUseCase: TelemetryUsecases | null = null;

cortex-js/src/domain/telemetry/telemetry.interface.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ export interface TelemetryAnonymized {
103103
sessionId: string | null;
104104
lastActiveAt?: string | null;
105105
}
106+
106107
export interface BenchmarkHardware {
107108
gpu: any[];
108109
cpu: any;

cortex-js/src/infrastructure/commanders/chat.command.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
Option,
55
InquirerService,
66
} from 'nest-commander';
7+
import ora from 'ora';
78
import { ChatCliUsecases } from './usecases/chat.cli.usecases';
89
import { exit } from 'node:process';
910
import { PSCliUsecases } from './usecases/ps.cli.usecases';
@@ -48,6 +49,7 @@ export class ChatCommand extends CommandRunner {
4849

4950
async run(passedParams: string[], options: ChatOptions): Promise<void> {
5051
let modelId = passedParams[0];
52+
const checkingSpinner = ora('Checking model...').start();
5153
// First attempt to get message from input or options
5254
// Extract input from 1 to end of array
5355
let message = options.message ?? passedParams.slice(1).join(' ');
@@ -66,10 +68,11 @@ export class ChatCommand extends CommandRunner {
6668
} else if (models.length > 0) {
6769
modelId = await this.modelInquiry(models);
6870
} else {
69-
console.error('Model ID is required');
71+
checkingSpinner.fail('Model ID is required');
7072
exit(1);
7173
}
7274
}
75+
checkingSpinner.succeed(`Model found`);
7376

7477
if (!message) options.attach = true;
7578
const result = await this.chatCliUsecases.chat(

cortex-js/src/infrastructure/commanders/embeddings.command.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
Option,
55
SubCommand,
66
} from 'nest-commander';
7+
import ora from 'ora';
78
import { ModelsUsecases } from '@/usecases/models/models.usecases';
89
import { PSCliUsecases } from './usecases/ps.cli.usecases';
910
import { ChatCliUsecases } from './usecases/chat.cli.usecases';
@@ -39,6 +40,7 @@ export class EmbeddingCommand extends CommandRunner {
3940
options: EmbeddingCommandOptions,
4041
): Promise<void> {
4142
let modelId = passedParams[0];
43+
const checkingSpinner = ora('Checking model...').start();
4244
// First attempt to get message from input or options
4345
let input: string | string[] = options.input ?? passedParams.splice(1);
4446

@@ -54,11 +56,11 @@ export class EmbeddingCommand extends CommandRunner {
5456
} else if (models.length > 0) {
5557
modelId = await this.modelInquiry(models);
5658
} else {
57-
console.error('Model ID is required');
59+
checkingSpinner.fail('Model ID is required');
5860
process.exit(1);
5961
}
6062
}
61-
63+
checkingSpinner.succeed(`Model found`);
6264
return this.chatCliUsecases
6365
.embeddings(modelId, input)
6466
.then((res) =>

cortex-js/src/infrastructure/commanders/models/model-start.command.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
Option,
55
InquirerService,
66
} from 'nest-commander';
7+
import ora from 'ora';
78
import { exit } from 'node:process';
89
import { ModelsCliUsecases } from '@commanders/usecases/models.cli.usecases';
910
import { CortexUsecases } from '@/usecases/cortex/cortex.usecases';
@@ -44,11 +45,12 @@ export class ModelStartCommand extends CommandRunner {
4445

4546
async run(passedParams: string[], options: ModelStartOptions): Promise<void> {
4647
let modelId = passedParams[0];
48+
const checkingSpinner = ora('Checking model...').start();
4749
if (!modelId) {
4850
try {
4951
modelId = await this.modelInquiry();
5052
} catch {
51-
console.error('Model ID is required');
53+
checkingSpinner.fail('Model ID is required');
5254
exit(1);
5355
}
5456
}
@@ -59,26 +61,25 @@ export class ModelStartCommand extends CommandRunner {
5961
!Array.isArray(existingModel.files) ||
6062
/^(http|https):\/\/[^/]+\/.*/.test(existingModel.files[0])
6163
) {
62-
console.error(
63-
`${modelId} not found on filesystem.\nPlease try 'cortex pull ${modelId}' first.`,
64-
);
64+
checkingSpinner.fail(`Model ${modelId} not found on filesystem.\nPlease try 'cortex pull ${modelId}' first.`);
6565
process.exit(1);
6666
}
6767

6868
checkModelCompatibility(modelId);
69-
69+
checkingSpinner.succeed('Model found');
7070
const engine = existingModel.engine || Engines.llamaCPP;
7171
// Pull engine if not exist
7272
if (
7373
!existsSync(join(await this.fileService.getCortexCppEnginePath(), engine))
7474
) {
75+
const engineSpinner = ora('Installing engine...').start();
7576
await this.initUsecases.installEngine(
7677
await this.initUsecases.defaultInstallationOptions(),
7778
'latest',
7879
engine,
7980
);
81+
engineSpinner.succeed();
8082
}
81-
8283
await this.cortexUsecases
8384
.startCortex(options.attach)
8485
.then(() => this.modelsCliUsecases.startModel(modelId, options.preset))

cortex-js/src/infrastructure/commanders/ps.command.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
import ora from 'ora';
12
import { CommandRunner, SubCommand } from 'nest-commander';
23
import { PSCliUsecases } from './usecases/ps.cli.usecases';
34
import { SetCommandContext } from './decorators/CommandContext';
45
import { ContextService } from '../services/context/context.service';
6+
import { ModelStat } from './types/model-stat.interface';
57

68
@SubCommand({
79
name: 'ps',
@@ -16,12 +18,20 @@ export class PSCommand extends CommandRunner {
1618
super();
1719
}
1820
async run(): Promise<void> {
21+
const runningSpinner = ora('Running PS command...').start();
22+
let checkingSpinner: ora.Ora
1923
return this.usecases
2024
.getModels()
21-
.then(console.table)
22-
.then(() => this.usecases.isAPIServerOnline())
25+
.then((models: ModelStat[]) => {
26+
runningSpinner.succeed();
27+
console.table(models);
28+
})
29+
.then(() => {
30+
checkingSpinner = ora('Checking API server...').start();
31+
return this.usecases.isAPIServerOnline();
32+
})
2333
.then((isOnline) => {
24-
if (isOnline) console.log('API server is online');
34+
checkingSpinner.succeed(isOnline ? 'API server is online' : 'API server is offline');
2535
});
2636
}
27-
}
37+
}

cortex-js/src/infrastructure/commanders/shortcuts/run.command.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
InquirerService,
77
} from 'nest-commander';
88
import { exit } from 'node:process';
9+
import ora from 'ora';
910
import { ChatCliUsecases } from '@commanders/usecases/chat.cli.usecases';
1011
import { ModelsCliUsecases } from '@commanders/usecases/models.cli.usecases';
1112
import { ModelNotFoundException } from '@/infrastructure/exception/model-not-found.exception';
@@ -44,21 +45,23 @@ export class RunCommand extends CommandRunner {
4445

4546
async run(passedParams: string[], options: RunOptions): Promise<void> {
4647
let modelId = passedParams[0];
48+
const checkingSpinner = ora('Checking model...').start();
4749
if (!modelId) {
4850
try {
4951
modelId = await this.modelInquiry();
5052
} catch {
51-
console.error('Model ID is required');
53+
checkingSpinner.fail('Model ID is required');
5254
exit(1);
5355
}
5456
}
5557
// If not exist
5658
// Try Pull
5759
if (!(await this.modelsCliUsecases.getModel(modelId))) {
60+
checkingSpinner.succeed('Model not found. Attempting to pull...');
5861
await this.modelsCliUsecases.pullModel(modelId).catch((e: Error) => {
5962
if (e instanceof ModelNotFoundException)
60-
console.error('Model does not exist.');
61-
else console.error(e.message ?? e);
63+
checkingSpinner.fail('Model does not exist.');
64+
else checkingSpinner.fail(e.message ?? e);
6265
exit(1);
6366
});
6467
}
@@ -70,23 +73,27 @@ export class RunCommand extends CommandRunner {
7073
!Array.isArray(existingModel.files) ||
7174
/^(http|https):\/\/[^/]+\/.*/.test(existingModel.files[0])
7275
) {
73-
console.error('Model is not available.');
76+
checkingSpinner.fail(
77+
`Model is not available`
78+
);
7479
process.exit(1);
7580
}
81+
checkingSpinner.succeed('Model found');
7682

7783
// Check model compatibility on this machine
7884
checkModelCompatibility(modelId);
79-
8085
const engine = existingModel.engine || Engines.llamaCPP;
8186
// Pull engine if not exist
8287
if (
8388
!existsSync(join(await this.fileService.getCortexCppEnginePath(), engine))
8489
) {
90+
const engineSpinner = ora('Installing engine...').start();
8591
await this.initUsecases.installEngine(
8692
await this.initUsecases.defaultInstallationOptions(),
8793
'latest',
8894
engine,
8995
);
96+
engineSpinner.succeed('Engine installed');
9097
}
9198

9299
return this.cortexUsecases

cortex-js/src/infrastructure/commanders/usecases/ps.cli.usecases.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { HttpStatus, Injectable } from '@nestjs/common';
2+
import ora from 'ora';
23
import {
34
CORTEX_CPP_MODELS_URL,
45
CORTEX_JS_HEALTH_URL,
@@ -26,6 +27,7 @@ export class PSCliUsecases {
2627
*/
2728
async getModels(): Promise<ModelStat[]> {
2829
const configs = await this.fileService.getConfig();
30+
const runningSpinner = ora('Getting models...').start();
2931
return new Promise<ModelStat[]>((resolve, reject) =>
3032
firstValueFrom(
3133
this.httpService.get(
@@ -40,6 +42,7 @@ export class PSCliUsecases {
4042
Array.isArray(data.data) &&
4143
data.data.length > 0
4244
) {
45+
runningSpinner.succeed();
4346
resolve(
4447
data.data.map((e) => {
4548
const startTime = e.start_time ?? new Date();
@@ -59,7 +62,10 @@ export class PSCliUsecases {
5962
} else reject();
6063
})
6164
.catch(reject),
62-
).catch(() => []);
65+
).catch(() => {
66+
runningSpinner.succeed('');
67+
return [];
68+
});
6369
}
6470

6571
/**

cortex-js/src/usecases/models/models.usecases.ts

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1+
import ora from 'ora';
12
import { CreateModelDto } from '@/infrastructure/dtos/models/create-model.dto';
23
import { UpdateModelDto } from '@/infrastructure/dtos/models/update-model.dto';
34
import { BadRequestException, Injectable } from '@nestjs/common';
45
import { Model, ModelSettingParams } from '@/domain/models/model.interface';
56
import { ModelNotFoundException } from '@/infrastructure/exception/model-not-found.exception';
67
import { basename, join } from 'path';
7-
import { promises, existsSync, mkdirSync, rmdirSync, readFileSync } from 'fs';
8+
import { promises, existsSync, mkdirSync, readFileSync, rmSync } from 'fs';
89
import { StartModelSuccessDto } from '@/infrastructure/dtos/models/start-model-success.dto';
910
import { ExtensionRepository } from '@/domain/repositories/extension.interface';
1011
import { EngineExtension } from '@/domain/abstracts/engine.abstract';
@@ -123,8 +124,7 @@ export class ModelsUsecases {
123124
.remove(id)
124125
.then(
125126
() =>
126-
existsSync(modelFolder) &&
127-
rmdirSync(modelFolder, { recursive: true }),
127+
existsSync(modelFolder) && rmSync(modelFolder, { recursive: true }),
128128
)
129129
.then(() => {
130130
const modelEvent: ModelEvent = {
@@ -163,7 +163,7 @@ export class ModelsUsecases {
163163
modelId,
164164
};
165165
}
166-
console.log('Loading model...');
166+
const loadingModelSpinner = ora('Loading model...').start();
167167
// update states and emitting event
168168
this.activeModelStatuses[modelId] = {
169169
model: modelId,
@@ -210,10 +210,13 @@ export class ModelsUsecases {
210210
};
211211
this.eventEmitter.emit('model.event', modelEvent);
212212
})
213-
.then(() => ({
214-
message: 'Model loaded successfully',
215-
modelId,
216-
}))
213+
.then(() => {
214+
loadingModelSpinner.succeed('Model loaded');
215+
return {
216+
message: 'Model loaded successfully',
217+
modelId,
218+
};
219+
})
217220
.catch(async (e) => {
218221
// remove the model from this.activeModelStatus.
219222
delete this.activeModelStatuses[modelId];
@@ -229,6 +232,7 @@ export class ModelsUsecases {
229232
modelId,
230233
};
231234
}
235+
loadingModelSpinner.fail('Model loading failed');
232236
await this.telemetryUseCases.createCrashReport(
233237
e,
234238
TelemetrySource.CORTEX_CPP,
@@ -359,7 +363,9 @@ export class ModelsUsecases {
359363
toDownloads,
360364
// Post processing
361365
async () => {
362-
console.log('Update model metadata...');
366+
const uploadModelMetadataSpiner = ora(
367+
'Updating model metadata...',
368+
).start();
363369
// Post processing after download
364370
if (existsSync(join(modelFolder, 'model.yml'))) {
365371
const model: CreateModelDto = load(
@@ -409,6 +415,7 @@ export class ModelsUsecases {
409415
});
410416
}
411417
}
418+
uploadModelMetadataSpiner.succeed('Model metadata updated');
412419
const modelEvent: ModelEvent = {
413420
model: modelId,
414421
event: 'model-downloaded',

0 commit comments

Comments
 (0)