inference-server
Version:
Libraries and server to build AI applications. Adapters to various native bindings allowing local inference. Integrate it with your application, or use as a microservice.
133 lines • 4.43 kB
JavaScript
import path from 'node:path';
import fs from 'node:fs/promises';
import readline from 'node:readline/promises';
import chalk from 'chalk';
import micromatch from 'micromatch';
import { indexModelCache } from '../cli/lib/indexModelCache.js';
import { loadConfig } from '../cli/lib/loadConfig.js';
import { getCacheDirPath } from '../lib/getCacheDirPath.js';
import { renderTreeView } from '../cli/lib/renderTreeView.js';
import prettyBytes from 'pretty-bytes';
function matchCachedModels(cacheInfo, pattern) {
const matches = [];
const isMatch = micromatch.matcher(pattern);
const visit = (node, nodePath) => {
if (isMatch(nodePath)) {
matches.push(node);
}
if (node.type === 'directory') {
for (const child of node.children) {
visit(child, nodePath + '/' + child.name);
}
}
};
for (const node of cacheInfo.fileTree) {
visit(node, node.name);
}
return matches;
}
function calculateTreeAggs(models) {
let totalSize = 0;
let fileCount = 0;
const visit = (node) => {
if (node.type === 'directory') {
for (const child of node.children) {
visit(child);
}
}
else {
fileCount++;
totalSize += node.size;
}
};
for (const node of models) {
visit(node);
}
return { totalSize, fileCount };
}
async function deleteFiles(files) {
for (const file of files) {
if (file.type === 'directory') {
await fs.rm(file.absPath, { recursive: true });
}
if (file.type === 'file') {
await fs.unlink(file.absPath);
}
}
}
async function promptConfirmation(message) {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
try {
const answer = await rl.question(`${message} (y/N): `);
return answer.toLowerCase() === 'y';
}
finally {
rl.close();
}
}
async function removeModels(pattern, skipPrompt) {
const config = await loadConfig();
let modelsCachePath = getCacheDirPath('models');
if (config?.options.cachePath) {
modelsCachePath = path.join(config.options.cachePath, 'models');
}
const cacheInfo = await indexModelCache(modelsCachePath, {
includeFiles: true,
includeUnused: true,
});
const matches = matchCachedModels(cacheInfo, pattern);
if (matches.length === 0) {
console.error(chalk.red(`No models found matching the given pattern`));
return;
}
if (!skipPrompt) {
const treeLines = renderTreeView(matches);
console.log(treeLines.join('\n'));
const { totalSize, fileCount } = calculateTreeAggs(matches);
// console.log(chalk.cyan(`Total size: ${prettyBytes(totalSize)} bytes`))
const fileText = fileCount === 1 ? `one file` : `${fileCount} files`;
const modelText = matches.length === 1 ? chalk.bold(matches[0].relPath) : `${matches.length} models`;
console.log(`\nThis will remove ${fileText} freeing ${prettyBytes(totalSize)} total.`);
const confirmed = await promptConfirmation(`Delete ${modelText} from disk?`);
if (!confirmed) {
console.log('Aborted');
return;
}
}
try {
// const subject = matches.length === 1 ? 'model' : 'models'
const modelText = matches.length === 1 ? matches[0].name : `${matches.length} models`;
console.log(`Deleting ${modelText} ...`);
await deleteFiles(matches);
console.log(chalk.green('Done'));
}
catch (error) {
console.error(chalk.red(`Error during removal: ${error.message}`));
}
}
export const removeCommand = {
command: 'remove <pattern>',
aliases: ['rm', 'del'],
describe: 'Delete models matching the pattern',
builder: (yargs) => {
return yargs
.positional('pattern', {
type: 'string',
describe: 'Glob pattern to match model paths',
})
.demandOption('pattern')
.option('yes', {
type: 'boolean',
alias: 'y',
describe: 'Skip confirmation prompt',
default: false,
});
},
handler: async (argv) => {
await removeModels(argv.pattern, argv.yes);
},
};
//# sourceMappingURL=removeCommand.js.map