From 2a06f17a47784a47e23f454937ea6c5b1c79f7a3 Mon Sep 17 00:00:00 2001 From: Louis Date: Sun, 16 Jun 2024 17:53:52 +0700 Subject: [PATCH 1/3] fix: failed tests --- cortex-js/package.json | 3 ++- cortex-js/src/app.module.ts | 2 -- .../src/download-manager/download-manager.service.spec.ts | 3 +++ .../src/infrastructure/controllers/chat.controller.spec.ts | 4 ++++ .../infrastructure/controllers/embeddings.controller.spec.ts | 4 ++++ .../src/infrastructure/controllers/models.controller.spec.ts | 4 ++++ cortex-js/src/usecases/chat/chat.usecases.spec.ts | 4 ++++ cortex-js/src/usecases/models/models.usecases.spec.ts | 4 ++++ cortex-js/test/jest-e2e.json | 3 ++- 9 files changed, 27 insertions(+), 4 deletions(-) diff --git a/cortex-js/package.json b/cortex-js/package.json index e83928bc1..42055a36c 100644 --- a/cortex-js/package.json +++ b/cortex-js/package.json @@ -114,7 +114,8 @@ "coverageDirectory": "../coverage", "testEnvironment": "node", "moduleNameMapper": { - "@/(.*)$": "/$1" + "@/(.*)$": "/$1", + "@commanders/(.*)$": "/../src/infrastructure/commanders/$1" } } } diff --git a/cortex-js/src/app.module.ts b/cortex-js/src/app.module.ts index 2fd0cbee6..37d2c0513 100644 --- a/cortex-js/src/app.module.ts +++ b/cortex-js/src/app.module.ts @@ -17,7 +17,6 @@ import { AppLoggerMiddleware } from './infrastructure/middlewares/app.logger.mid import { EventEmitterModule } from '@nestjs/event-emitter'; import { DownloadManagerModule } from './download-manager/download-manager.module'; import { EventsController } from './infrastructure/controllers/events.controller'; -import { AppController } from './infrastructure/controllers/app.controller'; import { AssistantsController } from './infrastructure/controllers/assistants.controller'; import { ChatController } from './infrastructure/controllers/chat.controller'; import { EmbeddingsController } from './infrastructure/controllers/embeddings.controller'; @@ -49,7 +48,6 @@ import { ProcessController } from './infrastructure/controllers/process.controll DownloadManagerModule, ], controllers: [ - AppController, AssistantsController, ChatController, EmbeddingsController, diff --git a/cortex-js/src/download-manager/download-manager.service.spec.ts b/cortex-js/src/download-manager/download-manager.service.spec.ts index 645d09d75..8263a632d 100644 --- a/cortex-js/src/download-manager/download-manager.service.spec.ts +++ b/cortex-js/src/download-manager/download-manager.service.spec.ts @@ -1,11 +1,14 @@ import { Test, TestingModule } from '@nestjs/testing'; import { DownloadManagerService } from './download-manager.service'; +import { HttpModule } from '@nestjs/axios'; +import { EventEmitterModule } from '@nestjs/event-emitter'; describe('DownloadManagerService', () => { let service: DownloadManagerService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ + imports: [HttpModule, EventEmitterModule.forRoot()], providers: [DownloadManagerService], }).compile(); diff --git a/cortex-js/src/infrastructure/controllers/chat.controller.spec.ts b/cortex-js/src/infrastructure/controllers/chat.controller.spec.ts index dc3434ede..fd3110b47 100644 --- a/cortex-js/src/infrastructure/controllers/chat.controller.spec.ts +++ b/cortex-js/src/infrastructure/controllers/chat.controller.spec.ts @@ -5,6 +5,8 @@ import { DatabaseModule } from '../database/database.module'; import { ExtensionModule } from '../repositories/extensions/extension.module'; import { ModelRepositoryModule } from '../repositories/models/model.module'; import { HttpModule } from '@nestjs/axios'; +import { DownloadManagerModule } from '@/download-manager/download-manager.module'; +import { EventEmitterModule } from '@nestjs/event-emitter'; describe('ChatController', () => { let controller: ChatController; @@ -16,6 +18,8 @@ describe('ChatController', () => { ExtensionModule, ModelRepositoryModule, HttpModule, + DownloadManagerModule, + EventEmitterModule.forRoot(), ], controllers: [ChatController], providers: [ChatUsecases], diff --git a/cortex-js/src/infrastructure/controllers/embeddings.controller.spec.ts b/cortex-js/src/infrastructure/controllers/embeddings.controller.spec.ts index c2cd08a81..50fb43d08 100644 --- a/cortex-js/src/infrastructure/controllers/embeddings.controller.spec.ts +++ b/cortex-js/src/infrastructure/controllers/embeddings.controller.spec.ts @@ -5,6 +5,8 @@ import { DatabaseModule } from '../database/database.module'; import { ModelRepositoryModule } from '../repositories/models/model.module'; import { ExtensionModule } from '../repositories/extensions/extension.module'; import { HttpModule } from '@nestjs/axios'; +import { DownloadManagerModule } from '@/download-manager/download-manager.module'; +import { EventEmitterModule } from '@nestjs/event-emitter'; describe('EmbeddingsController', () => { let controller: EmbeddingsController; @@ -16,6 +18,8 @@ describe('EmbeddingsController', () => { ModelRepositoryModule, ExtensionModule, HttpModule, + DownloadManagerModule, + EventEmitterModule.forRoot(), ], controllers: [EmbeddingsController], providers: [ChatUsecases], diff --git a/cortex-js/src/infrastructure/controllers/models.controller.spec.ts b/cortex-js/src/infrastructure/controllers/models.controller.spec.ts index d55a2b750..728313c5a 100644 --- a/cortex-js/src/infrastructure/controllers/models.controller.spec.ts +++ b/cortex-js/src/infrastructure/controllers/models.controller.spec.ts @@ -7,6 +7,8 @@ import { FileManagerModule } from '@/infrastructure/services/file-manager/file-m import { HttpModule } from '@nestjs/axios'; import { CortexUsecases } from '@/usecases/cortex/cortex.usecases'; import { ModelRepositoryModule } from '../repositories/models/model.module'; +import { DownloadManagerModule } from '@/download-manager/download-manager.module'; +import { EventEmitterModule } from '@nestjs/event-emitter'; describe('ModelsController', () => { let controller: ModelsController; @@ -19,6 +21,8 @@ describe('ModelsController', () => { FileManagerModule, HttpModule, ModelRepositoryModule, + DownloadManagerModule, + EventEmitterModule.forRoot(), ], controllers: [ModelsController], providers: [ModelsUsecases, CortexUsecases], diff --git a/cortex-js/src/usecases/chat/chat.usecases.spec.ts b/cortex-js/src/usecases/chat/chat.usecases.spec.ts index 366b6d252..c2f001ba2 100644 --- a/cortex-js/src/usecases/chat/chat.usecases.spec.ts +++ b/cortex-js/src/usecases/chat/chat.usecases.spec.ts @@ -4,6 +4,8 @@ import { DatabaseModule } from '@/infrastructure/database/database.module'; import { ExtensionModule } from '@/infrastructure/repositories/extensions/extension.module'; import { ModelRepositoryModule } from '@/infrastructure/repositories/models/model.module'; import { HttpModule } from '@nestjs/axios'; +import { DownloadManagerModule } from '@/download-manager/download-manager.module'; +import { EventEmitterModule } from '@nestjs/event-emitter'; describe('ChatService', () => { let service: ChatUsecases; @@ -15,6 +17,8 @@ describe('ChatService', () => { ExtensionModule, ModelRepositoryModule, HttpModule, + DownloadManagerModule, + EventEmitterModule.forRoot(), ], providers: [ChatUsecases], exports: [ChatUsecases], diff --git a/cortex-js/src/usecases/models/models.usecases.spec.ts b/cortex-js/src/usecases/models/models.usecases.spec.ts index 12ca0f992..43ce0dcf9 100644 --- a/cortex-js/src/usecases/models/models.usecases.spec.ts +++ b/cortex-js/src/usecases/models/models.usecases.spec.ts @@ -6,6 +6,8 @@ import { ExtensionModule } from '@/infrastructure/repositories/extensions/extens import { FileManagerModule } from '@/infrastructure/services/file-manager/file-manager.module'; import { HttpModule } from '@nestjs/axios'; import { ModelRepositoryModule } from '@/infrastructure/repositories/models/model.module'; +import { DownloadManagerModule } from '@/download-manager/download-manager.module'; +import { EventEmitterModule } from '@nestjs/event-emitter'; describe('ModelsService', () => { let service: ModelsUsecases; @@ -19,6 +21,8 @@ describe('ModelsService', () => { FileManagerModule, HttpModule, ModelRepositoryModule, + DownloadManagerModule, + EventEmitterModule.forRoot(), ], providers: [ModelsUsecases], exports: [ModelsUsecases], diff --git a/cortex-js/test/jest-e2e.json b/cortex-js/test/jest-e2e.json index 7b028de70..c74edd836 100644 --- a/cortex-js/test/jest-e2e.json +++ b/cortex-js/test/jest-e2e.json @@ -7,6 +7,7 @@ "^.+\\.(t|j)s$": "ts-jest" }, "moduleNameMapper": { - "@/(.*)$": "/../src/$1" + "@/(.*)$": "/../src/$1", + "@commanders/(.*)$": "/../src/infrastructure/commanders/$1" } } From bbac74cc4f38423ace3a8f3cdb4a84f1c476d683 Mon Sep 17 00:00:00 2001 From: Van-QA Date: Sun, 16 Jun 2024 21:04:05 +0700 Subject: [PATCH 2/3] feat: add validation for stdout with localAPIserver --- .../commanders/test/helpers.command.spec.ts | 5 +- .../test/model-list.command.spec.ts | 67 ------------------- .../commanders/test/models.command.spec.ts | 4 +- 3 files changed, 6 insertions(+), 70 deletions(-) delete mode 100644 cortex-js/src/infrastructure/commanders/test/model-list.command.spec.ts diff --git a/cortex-js/src/infrastructure/commanders/test/helpers.command.spec.ts b/cortex-js/src/infrastructure/commanders/test/helpers.command.spec.ts index 05d9562d6..5fcc68a53 100644 --- a/cortex-js/src/infrastructure/commanders/test/helpers.command.spec.ts +++ b/cortex-js/src/infrastructure/commanders/test/helpers.command.spec.ts @@ -91,8 +91,11 @@ describe('Helper commands', () => { expect(exitSpy.firstCall?.args[0]).toBe(1); }); - test('Local API server via localhost:1337/api', async () => { + test('Local API server via default host/port localhost:1337/api', async () => { await CommandTestFactory.run(commandInstance, ['serve']); + expect(stdoutSpy.firstCall?.args[0]).toContain( + 'Started server at http://localhost:1337', + ); // Add a delay of 1000 milliseconds (1 second) return new Promise(async (resolve) => { diff --git a/cortex-js/src/infrastructure/commanders/test/model-list.command.spec.ts b/cortex-js/src/infrastructure/commanders/test/model-list.command.spec.ts deleted file mode 100644 index d122457af..000000000 --- a/cortex-js/src/infrastructure/commanders/test/model-list.command.spec.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { TestingModule } from '@nestjs/testing'; -import { stubMethod } from 'hanbi'; -import { CommandTestFactory } from 'nest-commander-testing'; -import { CommandModule } from '@/command.module'; -import { FileManagerService } from '@/infrastructure/services/file-manager/file-manager.service'; -import { join } from 'path'; -import { mkdirSync, rmSync, writeFileSync } from 'fs'; - -let commandInstance: TestingModule; - -beforeEach( - () => - new Promise(async (res) => { - commandInstance = await CommandTestFactory.createTestingCommand({ - imports: [CommandModule], - }) - // .overrideProvider(LogService) - // .useValue({}) - .compile(); - const fileService = - await commandInstance.resolve(FileManagerService); - - // Attempt to create test folder - await fileService.writeConfigFile({ - dataFolderPath: join(__dirname, 'test_data'), - }); - res(); - }), -); - -afterEach( - () => - new Promise(async (res) => { - // Attempt to clean test folder - rmSync(join(__dirname, 'test_data'), { - recursive: true, - force: true, - }); - res(); - }), -); - -describe('models list returns array of models', () => { - test('empty model list', async () => { - const logMock = stubMethod(console, 'table'); - - await CommandTestFactory.run(commandInstance, ['models', 'list']); - expect(logMock.firstCall?.args[0]).toBeInstanceOf(Array); - expect(logMock.firstCall?.args[0].length).toBe(0); - }); - - test('many models in the list', async () => { - const logMock = stubMethod(console, 'table'); - - mkdirSync(join(__dirname, 'test_data', 'models'), { recursive: true }); - writeFileSync( - join(__dirname, 'test_data', 'models', 'test.yaml'), - 'model: test', - 'utf8', - ); - - await CommandTestFactory.run(commandInstance, ['models', 'list']); - expect(logMock.firstCall?.args[0]).toBeInstanceOf(Array); - expect(logMock.firstCall?.args[0].length).toBe(1); - expect(logMock.firstCall?.args[0][0].id).toBe('test'); - }); -}); diff --git a/cortex-js/src/infrastructure/commanders/test/models.command.spec.ts b/cortex-js/src/infrastructure/commanders/test/models.command.spec.ts index 7d512d2be..adbccf82e 100644 --- a/cortex-js/src/infrastructure/commanders/test/models.command.spec.ts +++ b/cortex-js/src/infrastructure/commanders/test/models.command.spec.ts @@ -34,14 +34,14 @@ afterEach( ); export const modelName = 'tinyllama'; -describe('Models list returns array of models', () => { +describe('Action with models', () => { test('Init with CPU', async () => { const logMock = stubMethod(console, 'log'); logMock.passThrough(); CommandTestFactory.setAnswers(['CPU', '', 'AVX2']); - await CommandTestFactory.run(commandInstance, ['init']); + await CommandTestFactory.run(commandInstance, ['setup']); expect(logMock.firstCall?.args[0]).toBe( 'Downloading engine file windows-amd64-avx2.tar.gz', ); From b0564c5373984287b5d0796c942a13a16df12c4f Mon Sep 17 00:00:00 2001 From: Louis Date: Mon, 17 Jun 2024 10:21:48 +0700 Subject: [PATCH 3/3] fix: error handling and command descriptions (#703) --- cortex-js/package.json | 5 +-- cortex-js/src/command.module.ts | 4 +++ .../commanders/benchmark.command.ts | 12 +++++-- .../infrastructure/commanders/chat.command.ts | 21 ++++++++--- .../commanders/cortex-command.commander.ts | 26 ++++++-------- .../commanders/embeddings.command.ts | 16 ++++++--- .../infrastructure/commanders/init.command.ts | 17 ++++++--- .../commanders/models/model-get.command.ts | 15 +++++--- .../commanders/models/model-list.command.ts | 2 +- .../commanders/models/model-pull.command.ts | 20 ++++++----- .../commanders/models/model-remove.command.ts | 15 +++++--- .../commanders/models/model-start.command.ts | 14 ++++++-- .../commanders/models/model-stop.command.ts | 15 +++++--- .../commanders/models/model-update.command.ts | 11 ++++-- .../commanders/serve.command.ts | 25 +++++-------- .../commanders/shortcuts/run.command.ts | 9 +++-- .../sub-commands/serve-stop.command.ts | 18 ++++++++++ .../commanders/usecases/chat.cli.usecases.ts | 5 +++ cortex-js/src/utils/logo.ts | 35 +++++++++++++++++++ 19 files changed, 205 insertions(+), 80 deletions(-) create mode 100644 cortex-js/src/infrastructure/commanders/sub-commands/serve-stop.command.ts create mode 100644 cortex-js/src/utils/logo.ts diff --git a/cortex-js/package.json b/cortex-js/package.json index 42055a36c..05061314d 100644 --- a/cortex-js/package.json +++ b/cortex-js/package.json @@ -1,9 +1,10 @@ { "name": "@janhq/cortex", "version": "0.0.1", - "description": "", - "author": "", + "description": "Cortex is an openAI-compatible local AI server that developers can use to build LLM apps. It is packaged with a Docker-inspired command-line interface and a Typescript client library. It can be used as a standalone server, or imported as a library.", + "author": "Jan ", "license": "AGPL-3.0", + "homepage": "https://github.com/janhq/cortex", "bin": { "cortex": "./dist/src/command.js" }, diff --git a/cortex-js/src/command.module.ts b/cortex-js/src/command.module.ts index 1c9f01486..a66db6e4e 100644 --- a/cortex-js/src/command.module.ts +++ b/cortex-js/src/command.module.ts @@ -31,6 +31,7 @@ import { EmbeddingCommand } from './infrastructure/commanders/embeddings.command import { BenchmarkCommand } from './infrastructure/commanders/benchmark.command'; import { EventEmitterModule } from '@nestjs/event-emitter'; import { DownloadManagerModule } from './download-manager/download-manager.module'; +import { ServeStopCommand } from './infrastructure/commanders/sub-commands/serve-stop.command'; @Module({ imports: [ @@ -78,6 +79,9 @@ import { DownloadManagerModule } from './download-manager/download-manager.modul // Shortcuts RunCommand, + + // Serve + ServeStopCommand, ], }) export class CommandModule {} diff --git a/cortex-js/src/infrastructure/commanders/benchmark.command.ts b/cortex-js/src/infrastructure/commanders/benchmark.command.ts index 96f94f9fc..899055043 100644 --- a/cortex-js/src/infrastructure/commanders/benchmark.command.ts +++ b/cortex-js/src/infrastructure/commanders/benchmark.command.ts @@ -4,7 +4,10 @@ import { BenchmarkConfig } from './types/benchmark-config.interface'; @SubCommand({ name: 'benchmark', - subCommands: [], + arguments: '[model_id]', + argsDescription: { + model_id: 'Model to benchmark with', + }, description: 'Benchmark and analyze the performance of a specific AI model using a variety of system resources', }) @@ -14,10 +17,13 @@ export class BenchmarkCommand extends CommandRunner { } async run( - _input: string[], + passedParams: string[], options?: Partial, ): Promise { - return this.benchmarkUsecases.benchmark(options ?? {}); + return this.benchmarkUsecases.benchmark({ + ...options, + ...(passedParams[0] ? { modelId: passedParams[0] } : {}), + }); } @Option({ diff --git a/cortex-js/src/infrastructure/commanders/chat.command.ts b/cortex-js/src/infrastructure/commanders/chat.command.ts index 718541d0d..5e242e7fd 100644 --- a/cortex-js/src/infrastructure/commanders/chat.command.ts +++ b/cortex-js/src/infrastructure/commanders/chat.command.ts @@ -16,7 +16,16 @@ type ChatOptions = { attach: boolean; }; -@SubCommand({ name: 'chat', description: 'Send a chat request to a model' }) +@SubCommand({ + name: 'chat', + description: 'Send a chat request to a model', + arguments: '[model_id] [message]', + argsDescription: { + model_id: + 'Model ID to chat with. If there is no model_id provided, it will prompt to select from running models.', + message: 'Message to send to the model', + }, +}) export class ChatCommand extends CommandRunner { constructor( private readonly inquirerService: InquirerService, @@ -27,17 +36,19 @@ export class ChatCommand extends CommandRunner { super(); } - async run(_input: string[], options: ChatOptions): Promise { - let modelId = _input[0]; + async run(passedParams: string[], options: ChatOptions): Promise { + let modelId = passedParams[0]; // First attempt to get message from input or options // Extract input from 1 to end of array - let message = options.message ?? _input.slice(1).join(' '); + let message = options.message ?? passedParams.slice(1).join(' '); // Check for model existing if (!modelId || !(await this.modelsUsecases.findOne(modelId))) { // Model ID is not provided // first input might be message input - message = _input.length ? _input.join(' ') : options.message ?? ''; + message = passedParams.length + ? passedParams.join(' ') + : options.message ?? ''; // If model ID is not provided, prompt user to select from running models const models = await this.psCliUsecases.getModels(); if (models.length === 1) { diff --git a/cortex-js/src/infrastructure/commanders/cortex-command.commander.ts b/cortex-js/src/infrastructure/commanders/cortex-command.commander.ts index 9bb9c33fd..467db7a6d 100644 --- a/cortex-js/src/infrastructure/commanders/cortex-command.commander.ts +++ b/cortex-js/src/infrastructure/commanders/cortex-command.commander.ts @@ -1,4 +1,4 @@ -import { RootCommand, CommandRunner, Option } from 'nest-commander'; +import { RootCommand, CommandRunner } from 'nest-commander'; import { ServeCommand } from './serve.command'; import { ChatCommand } from './chat.command'; import { ModelsCommand } from './models.command'; @@ -11,10 +11,9 @@ import pkg from '@/../package.json'; import { PresetCommand } from './presets.command'; import { EmbeddingCommand } from './embeddings.command'; import { BenchmarkCommand } from './benchmark.command'; +import chalk from 'chalk'; +import { printSlogan } from '@/utils/logo'; -interface CortexCommandOptions { - version: boolean; -} @RootCommand({ subCommands: [ ModelsCommand, @@ -32,17 +31,12 @@ interface CortexCommandOptions { description: 'Cortex CLI', }) export class CortexCommand extends CommandRunner { - async run(input: string[], option: CortexCommandOptions): Promise { - if (option.version) console.log(pkg.version); - } - - @Option({ - flags: '-v, --version', - description: 'Cortex version', - defaultValue: false, - name: 'version', - }) - parseVersion() { - return true; + async run(): Promise { + printSlogan(); + console.log('\n'); + console.log(`Cortex CLI - v${pkg.version}`); + console.log(chalk.blue(`Github: ${pkg.homepage}`)); + console.log('\n'); + this.command?.help(); } } diff --git a/cortex-js/src/infrastructure/commanders/embeddings.command.ts b/cortex-js/src/infrastructure/commanders/embeddings.command.ts index 872715762..68d874364 100644 --- a/cortex-js/src/infrastructure/commanders/embeddings.command.ts +++ b/cortex-js/src/infrastructure/commanders/embeddings.command.ts @@ -18,7 +18,12 @@ interface EmbeddingCommandOptions { @SubCommand({ name: 'embeddings', + arguments: '[model_id]', description: 'Creates an embedding vector representing the input text.', + argsDescription: { + model_id: + 'Model to use for embedding. If not provided, it will prompt to select from running models.', + }, }) export class EmbeddingCommand extends CommandRunner { constructor( @@ -29,16 +34,19 @@ export class EmbeddingCommand extends CommandRunner { ) { super(); } - async run(_input: string[], options: EmbeddingCommandOptions): Promise { - let modelId = _input[0]; + async run( + passedParams: string[], + options: EmbeddingCommandOptions, + ): Promise { + let modelId = passedParams[0]; // First attempt to get message from input or options - let input: string | string[] = options.input ?? _input.splice(1); + let input: string | string[] = options.input ?? passedParams.splice(1); // Check for model existing if (!modelId || !(await this.modelsUsecases.findOne(modelId))) { // Model ID is not provided // first input might be message input - input = _input ?? options.input; + input = passedParams ?? options.input; // If model ID is not provided, prompt user to select from running models const models = await this.psCliUsecases.getModels(); if (models.length === 1) { diff --git a/cortex-js/src/infrastructure/commanders/init.command.ts b/cortex-js/src/infrastructure/commanders/init.command.ts index 57817a9e1..9cb3d2229 100644 --- a/cortex-js/src/infrastructure/commanders/init.command.ts +++ b/cortex-js/src/infrastructure/commanders/init.command.ts @@ -10,7 +10,11 @@ import { InitOptions } from './types/init-options.interface'; @SubCommand({ name: 'init', aliases: ['setup'], + arguments: '[version]', description: "Init settings and download cortex's dependencies", + argsDescription: { + version: 'Version of cortex engine', + }, }) export class InitCommand extends CommandRunner { constructor( @@ -20,16 +24,19 @@ export class InitCommand extends CommandRunner { super(); } - async run(input: string[], options?: InitOptions): Promise { + async run(passedParams: string[], options?: InitOptions): Promise { if (options?.silent) { - return this.initSilently(input); + return this.initSilently(passedParams); } else { - return this.initPrompts(input, options); + return this.initPrompts(passedParams, options); } } - private initSilently = async (input: string[], options: InitOptions = {}) => { - const version = input[0] ?? 'latest'; + private initSilently = async ( + passedParams: string[], + options: InitOptions = {}, + ) => { + const version = passedParams[0] ?? 'latest'; if (process.platform === 'darwin') { const engineFileName = this.initUsecases.parseEngineFileName(options); return this.initUsecases.installEngine(engineFileName, version); diff --git a/cortex-js/src/infrastructure/commanders/models/model-get.command.ts b/cortex-js/src/infrastructure/commanders/models/model-get.command.ts index 01f2bbaaa..5973aadc6 100644 --- a/cortex-js/src/infrastructure/commanders/models/model-get.command.ts +++ b/cortex-js/src/infrastructure/commanders/models/model-get.command.ts @@ -2,19 +2,26 @@ import { CommandRunner, SubCommand } from 'nest-commander'; import { ModelsCliUsecases } from '@commanders/usecases/models.cli.usecases'; import { exit } from 'node:process'; -@SubCommand({ name: 'get', description: 'Get a model by ID.' }) +@SubCommand({ + name: 'get', + description: 'Get a model by ID.', + arguments: '', + argsDescription: { + model_id: 'Model ID to get information about.', + }, +}) export class ModelGetCommand extends CommandRunner { constructor(private readonly modelsCliUsecases: ModelsCliUsecases) { super(); } - async run(input: string[]): Promise { - if (input.length === 0) { + async run(passedParams: string[]): Promise { + if (passedParams.length === 0) { console.error('Model ID is required'); exit(1); } - const model = await this.modelsCliUsecases.getModel(input[0]); + const model = await this.modelsCliUsecases.getModel(passedParams[0]); if (!model) console.error('Model not found'); else console.log(model); } diff --git a/cortex-js/src/infrastructure/commanders/models/model-list.command.ts b/cortex-js/src/infrastructure/commanders/models/model-list.command.ts index 042d457dc..246eec115 100644 --- a/cortex-js/src/infrastructure/commanders/models/model-list.command.ts +++ b/cortex-js/src/infrastructure/commanders/models/model-list.command.ts @@ -10,7 +10,7 @@ export class ModelListCommand extends CommandRunner { super(); } - async run(_input: string[], option: ModelListOptions): Promise { + async run(passedParams: string[], option: ModelListOptions): Promise { const models = await this.modelsCliUsecases.listAllModels(); option.format === 'table' ? console.table( diff --git a/cortex-js/src/infrastructure/commanders/models/model-pull.command.ts b/cortex-js/src/infrastructure/commanders/models/model-pull.command.ts index 2e7c64b88..cbf253164 100644 --- a/cortex-js/src/infrastructure/commanders/models/model-pull.command.ts +++ b/cortex-js/src/infrastructure/commanders/models/model-pull.command.ts @@ -6,6 +6,8 @@ import { ModelNotFoundException } from '@/infrastructure/exception/model-not-fou @SubCommand({ name: 'pull', aliases: ['download'], + arguments: '', + argsDescription: { model_id: 'Model repo to pull' }, description: 'Download a model. Working with HuggingFace model id.', }) export class ModelPullCommand extends CommandRunner { @@ -13,18 +15,20 @@ export class ModelPullCommand extends CommandRunner { super(); } - async run(input: string[]) { - if (input.length < 1) { + async run(passedParams: string[]) { + if (passedParams.length < 1) { console.error('Model Id is required'); exit(1); } - await this.modelsCliUsecases.pullModel(input[0]).catch((e: Error) => { - if (e instanceof ModelNotFoundException) - console.error('Model does not exist.'); - else console.error(e); - exit(1); - }); + await this.modelsCliUsecases + .pullModel(passedParams[0]) + .catch((e: Error) => { + if (e instanceof ModelNotFoundException) + console.error('Model does not exist.'); + else console.error(e); + exit(1); + }); console.log('\nDownload complete!'); exit(0); diff --git a/cortex-js/src/infrastructure/commanders/models/model-remove.command.ts b/cortex-js/src/infrastructure/commanders/models/model-remove.command.ts index 883aa68e5..6bb80d02e 100644 --- a/cortex-js/src/infrastructure/commanders/models/model-remove.command.ts +++ b/cortex-js/src/infrastructure/commanders/models/model-remove.command.ts @@ -2,18 +2,25 @@ import { CommandRunner, SubCommand } from 'nest-commander'; import { ModelsCliUsecases } from '@commanders/usecases/models.cli.usecases'; import { exit } from 'node:process'; -@SubCommand({ name: 'remove', description: 'Remove a model by ID locally.' }) +@SubCommand({ + name: 'remove', + description: 'Remove a model by ID locally.', + arguments: '', + argsDescription: { + model_id: 'Model to remove', + }, +}) export class ModelRemoveCommand extends CommandRunner { constructor(private readonly modelsCliUsecases: ModelsCliUsecases) { super(); } - async run(input: string[]): Promise { - if (input.length === 0) { + async run(passedParams: string[]): Promise { + if (passedParams.length === 0) { console.error('Model ID is required'); exit(1); } - await this.modelsCliUsecases.removeModel(input[0]).then(console.log); + await this.modelsCliUsecases.removeModel(passedParams[0]).then(console.log); } } 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 b02510a3b..88c6f1c1d 100644 --- a/cortex-js/src/infrastructure/commanders/models/model-start.command.ts +++ b/cortex-js/src/infrastructure/commanders/models/model-start.command.ts @@ -12,7 +12,15 @@ type ModelStartOptions = { attach: boolean; preset?: string; }; -@SubCommand({ name: 'start', description: 'Start a model by ID.' }) +@SubCommand({ + name: 'start', + description: 'Start a model by ID.', + arguments: '[model_id]', + argsDescription: { + model_id: + 'Model ID to start. If there is no model ID, it will prompt you to select from the available models.', + }, +}) export class ModelStartCommand extends CommandRunner { constructor( private readonly inquirerService: InquirerService, @@ -22,8 +30,8 @@ export class ModelStartCommand extends CommandRunner { super(); } - async run(input: string[], options: ModelStartOptions): Promise { - let modelId = input[0]; + async run(passedParams: string[], options: ModelStartOptions): Promise { + let modelId = passedParams[0]; if (!modelId) { try { modelId = await this.modelInquiry(); diff --git a/cortex-js/src/infrastructure/commanders/models/model-stop.command.ts b/cortex-js/src/infrastructure/commanders/models/model-stop.command.ts index e236ec611..8a39fbc41 100644 --- a/cortex-js/src/infrastructure/commanders/models/model-stop.command.ts +++ b/cortex-js/src/infrastructure/commanders/models/model-stop.command.ts @@ -2,18 +2,25 @@ import { CommandRunner, SubCommand } from 'nest-commander'; import { exit } from 'node:process'; import { ModelsCliUsecases } from '@commanders/usecases/models.cli.usecases'; -@SubCommand({ name: 'stop', description: 'Stop a model by ID.' }) +@SubCommand({ + name: 'stop', + description: 'Stop a model by ID.', + arguments: '', + argsDescription: { + model_id: 'Model ID to stop.', + }, +}) export class ModelStopCommand extends CommandRunner { constructor(private readonly modelsCliUsecases: ModelsCliUsecases) { super(); } - async run(input: string[]): Promise { - if (input.length === 0) { + async run(passedParams: string[]): Promise { + if (passedParams.length === 0) { console.error('Model ID is required'); exit(1); } - await this.modelsCliUsecases.stopModel(input[0]).then(console.log); + await this.modelsCliUsecases.stopModel(passedParams[0]).then(console.log); } } diff --git a/cortex-js/src/infrastructure/commanders/models/model-update.command.ts b/cortex-js/src/infrastructure/commanders/models/model-update.command.ts index 1d77632d2..9bfd02d8d 100644 --- a/cortex-js/src/infrastructure/commanders/models/model-update.command.ts +++ b/cortex-js/src/infrastructure/commanders/models/model-update.command.ts @@ -8,13 +8,20 @@ type UpdateOptions = { options?: string[]; }; -@SubCommand({ name: 'update', description: 'Update configuration of a model.' }) +@SubCommand({ + name: 'update', + description: 'Update configuration of a model.', + arguments: '', + argsDescription: { + model_id: 'Model ID to update configuration.', + }, +}) export class ModelUpdateCommand extends CommandRunner { constructor(private readonly modelsCliUsecases: ModelsCliUsecases) { super(); } - async run(_input: string[], option: UpdateOptions): Promise { + async run(passedParams: string[], option: UpdateOptions): Promise { const modelId = option.model; if (!modelId) { console.error('Model Id is required'); diff --git a/cortex-js/src/infrastructure/commanders/serve.command.ts b/cortex-js/src/infrastructure/commanders/serve.command.ts index 181333e2d..eb60ea2ab 100644 --- a/cortex-js/src/infrastructure/commanders/serve.command.ts +++ b/cortex-js/src/infrastructure/commanders/serve.command.ts @@ -1,14 +1,14 @@ import { spawn } from 'child_process'; import { - CORTEX_JS_STOP_API_SERVER_URL, defaultCortexJsHost, defaultCortexJsPort, } from '@/infrastructure/constants/cortex'; import { CommandRunner, SubCommand, Option } from 'nest-commander'; import { join } from 'path'; +import { ServeStopCommand } from './sub-commands/serve-stop.command'; type ServeOptions = { - host?: string; + address?: string; port?: number; attach: boolean; }; @@ -16,23 +16,14 @@ type ServeOptions = { @SubCommand({ name: 'serve', description: 'Providing API endpoint for Cortex backend', + subCommands: [ServeStopCommand], }) export class ServeCommand extends CommandRunner { - async run(_input: string[], options?: ServeOptions): Promise { - const host = options?.host || defaultCortexJsHost; + async run(passedParams: string[], options?: ServeOptions): Promise { + const host = options?.address || defaultCortexJsHost; const port = options?.port || defaultCortexJsPort; - if (_input[0] === 'stop') { - return this.stopServer().then(() => console.log('API server stopped')); - } else { - return this.startServer(host, port, options); - } - } - - private async stopServer() { - return fetch(CORTEX_JS_STOP_API_SERVER_URL(), { - method: 'DELETE', - }).catch(() => {}); + return this.startServer(host, port, options); } private async startServer( @@ -63,8 +54,8 @@ export class ServeCommand extends CommandRunner { } @Option({ - flags: '-h, --host ', - description: 'Host to serve the application', + flags: '-a, --address
', + description: 'Address to use', }) parseHost(value: string) { return value; diff --git a/cortex-js/src/infrastructure/commanders/shortcuts/run.command.ts b/cortex-js/src/infrastructure/commanders/shortcuts/run.command.ts index 15a6d4f8b..e7c4120e8 100644 --- a/cortex-js/src/infrastructure/commanders/shortcuts/run.command.ts +++ b/cortex-js/src/infrastructure/commanders/shortcuts/run.command.ts @@ -21,6 +21,11 @@ type RunOptions = { @SubCommand({ name: 'run', + arguments: '[model_id]', + argsDescription: { + model_id: + 'Model to run. If the model is not available, it will attempt to pull.', + }, description: 'Shortcut to start a model and chat', }) export class RunCommand extends CommandRunner { @@ -33,8 +38,8 @@ export class RunCommand extends CommandRunner { super(); } - async run(input: string[], options: RunOptions): Promise { - let modelId = input[0]; + async run(passedParams: string[], options: RunOptions): Promise { + let modelId = passedParams[0]; if (!modelId) { try { modelId = await this.modelInquiry(); diff --git a/cortex-js/src/infrastructure/commanders/sub-commands/serve-stop.command.ts b/cortex-js/src/infrastructure/commanders/sub-commands/serve-stop.command.ts new file mode 100644 index 000000000..3e8fae60c --- /dev/null +++ b/cortex-js/src/infrastructure/commanders/sub-commands/serve-stop.command.ts @@ -0,0 +1,18 @@ +import { CORTEX_JS_STOP_API_SERVER_URL } from '@/infrastructure/constants/cortex'; +import { CommandRunner, SubCommand } from 'nest-commander'; + +@SubCommand({ + name: 'stop', + description: 'Stop the API server', +}) +export class ServeStopCommand extends CommandRunner { + async run(): Promise { + return this.stopServer().then(() => console.log('API server stopped')); + } + + private async stopServer() { + return fetch(CORTEX_JS_STOP_API_SERVER_URL(), { + method: 'DELETE', + }).catch(() => {}); + } +} diff --git a/cortex-js/src/infrastructure/commanders/usecases/chat.cli.usecases.ts b/cortex-js/src/infrastructure/commanders/usecases/chat.cli.usecases.ts index 4f20d5c60..424805347 100644 --- a/cortex-js/src/infrastructure/commanders/usecases/chat.cli.usecases.ts +++ b/cortex-js/src/infrastructure/commanders/usecases/chat.cli.usecases.ts @@ -215,6 +215,11 @@ export class ChatCliUsecases { } } }); + }) + .catch(() => { + stdout.write('Something went wrong! Please check model status.\n'); + if (attach) rl.prompt(); + else rl.close(); }); } } diff --git a/cortex-js/src/utils/logo.ts b/cortex-js/src/utils/logo.ts new file mode 100644 index 000000000..59eeac92a --- /dev/null +++ b/cortex-js/src/utils/logo.ts @@ -0,0 +1,35 @@ +export const printSlogan = () => { + console.log( + ' ___ ___ ___ ___ ___ ', + ); + console.log( + ' / /\\ / /\\ / /\\ ___ / /\\ /__/| ', + ); + console.log( + ' / /:/ / /::\\ / /::\\ / /\\ / /:/_ | |:| ', + ); + console.log( + ' / /:/ / /:/\\:\\ / /:/\\:\\ / /:/ / /:/ /\\ | |:| ', + ); + console.log( + ' / /:/ ___ / /:/ \\:\\ / /:/~/:/ / /:/ / /:/ /:/_ __|__|:| ', + ); + console.log( + ' /__/:/ / /\\ /__/:/ \\__\\:\\ /__/:/ /:/___ / /::\\ /__/:/ /:/ /\\ /__/::::\\____', + ); + console.log( + ' \\ \\:\\ / /:/ \\ \\:\\ / /:/ \\ \\:\\/:::::/ /__/:/\\:\\ \\ \\:\\/:/ /:/ ~\\~~\\::::/', + ); + console.log( + ' \\ \\:\\ /:/ \\ \\:\\ /:/ \\ \\::/~~~~ \\__\\/ \\:\\ \\ \\::/ /:/ |~~|:|~~ ', + ); + console.log( + ' \\ \\:\\/:/ \\ \\:\\/:/ \\ \\:\\ \\ \\:\\ \\ \\:\\/:/ | |:| ', + ); + console.log( + ' \\ \\::/ \\ \\::/ \\ \\:\\ \\__\\/ \\ \\::/ | |:| ', + ); + console.log( + ' \\__\\/ \\__\\/ \\__\\/ \\__\\/ |__|/ ', + ); +};