capsule-ai-cli
Version:
The AI Model Orchestrator - Intelligent multi-model workflows with device-locked licensing
263 lines • 9.18 kB
JavaScript
import ora from 'ora';
import chalk from 'chalk';
import { terminalController } from './terminal-controller.js';
export class AnimationController {
spinner = null;
startTime = 0;
tokenCount = 0;
customMessage = null;
messageIndex = 0;
updateInterval = null;
renderInterval = null;
currentProvider = 'default';
currentModel = '';
renderPosition = null;
providerThemes = {
openai: {
color: chalk.hex('#10A37F'),
messages: ['Thinking...', 'Processing...', 'Analyzing...', 'Computing...']
},
anthropic: {
color: chalk.hex('#D97706'),
messages: ['Pondering...', 'Contemplating...', 'Reasoning...', 'Reflecting...']
},
google: {
color: chalk.hex('#4285F4'),
messages: ['Searching...', 'Organizing...', 'Synthesizing...', 'Assembling...']
},
cohere: {
color: chalk.hex('#FF6B6B'),
messages: ['Generating...', 'Creating...', 'Crafting...', 'Building...']
},
mistral: {
color: chalk.hex('#FF7000'),
messages: ['Formulating...', 'Constructing...', 'Developing...', 'Preparing...']
},
deepseek: {
color: chalk.hex('#00D4FF'),
messages: ['Diving deep...', 'Exploring...', 'Discovering...', 'Uncovering...']
},
default: {
color: chalk.white,
messages: ['Loading...', 'Working...', 'Processing...', 'Thinking...']
}
};
modelMessages = {
'o3': 'Deep reasoning...',
'o4-mini': 'Quick thinking...',
'gpt-4o': 'Optimizing response...',
'gpt-4.1': 'Advanced processing...'
};
setProvider(provider) {
this.currentProvider = provider;
}
setModel(model) {
this.currentModel = model;
}
setRenderPosition(row) {
this.renderPosition = { row };
}
start(options) {
if (this.spinner) {
this.stop();
}
if (options?.provider) {
this.currentProvider = options.provider;
}
if (options?.model) {
this.currentModel = options.model;
}
if (options?.initialTokens !== undefined) {
this.tokenCount = options.initialTokens;
}
if (options?.customMessage !== undefined) {
this.customMessage = options.customMessage;
}
this.startTime = Date.now();
this.messageIndex = 0;
const theme = this.providerThemes[this.currentProvider] || this.providerThemes.default;
this.spinner = ora({
spinner: {
interval: 200,
frames: ['✶', '✷', '✸', '✹']
},
color: 'cyan',
hideCursor: false,
stream: process.stdout
});
if (this.renderPosition) {
this.startManualRendering();
}
else {
this.updateInterval = setInterval(() => {
this.updateSpinnerText();
}, 200);
this.updateSpinnerText();
this.spinner.start();
}
}
updateSpinnerText() {
if (!this.spinner)
return;
const theme = this.providerThemes[this.currentProvider] || this.providerThemes.default;
const elapsed = Math.floor((Date.now() - this.startTime) / 1000);
let message;
if (this.customMessage) {
message = this.customMessage;
}
else if (this.modelMessages[this.currentModel]) {
message = this.modelMessages[this.currentModel];
}
else {
const messages = theme.messages;
const msgIdx = Math.floor(this.messageIndex / 20) % messages.length;
message = messages[msgIdx];
this.messageIndex++;
}
const text = `${theme.color(message)} ${chalk.dim('(')}${chalk.dim(`${elapsed}s`)} ${chalk.dim('•')} ${chalk.dim(`${this.tokenCount} tokens in`)} ${chalk.dim('• esc to interrupt')}${chalk.dim(')')}`;
this.spinner.text = text;
}
updateMessage(message) {
this.customMessage = message;
if (this.spinner) {
this.updateSpinnerText();
}
}
updateTokens(count) {
this.tokenCount = count;
if (this.spinner) {
this.updateSpinnerText();
}
}
startManualRendering() {
if (!this.renderPosition || !this.spinner)
return;
if (this.renderInterval) {
clearInterval(this.renderInterval);
}
this.updateInterval = setInterval(() => {
this.messageIndex++;
}, 200);
this.renderInterval = setInterval(() => {
if (!this.spinner || !this.renderPosition) {
if (this.renderInterval) {
clearInterval(this.renderInterval);
this.renderInterval = null;
}
return;
}
terminalController.saveCursorPosition();
terminalController.moveCursorTo(1, this.renderPosition.row);
terminalController.clearLine();
const frames = ['✶', '✷', '✸', '✹'];
const frameIndex = Math.floor((Date.now() - this.startTime) / 200) % frames.length;
const theme = this.providerThemes[this.currentProvider] || this.providerThemes.default;
const elapsed = Math.floor((Date.now() - this.startTime) / 1000);
let message;
if (this.customMessage) {
message = this.customMessage;
}
else if (this.modelMessages[this.currentModel]) {
message = this.modelMessages[this.currentModel];
}
else {
const messages = theme.messages;
const msgIdx = Math.floor(this.messageIndex / 20) % messages.length;
message = messages[msgIdx];
}
const animationText = ` ${theme.color(frames[frameIndex])} ${theme.color(message)} ${chalk.dim('(')}${chalk.dim(`${elapsed}s`)} ${chalk.dim('•')} ${chalk.dim(`${this.tokenCount} tokens in`)} ${chalk.dim('• esc to interrupt')}${chalk.dim(')')}`;
process.stdout.write(animationText);
terminalController.restoreCursorPosition();
}, 200);
}
stop() {
if (this.updateInterval) {
clearInterval(this.updateInterval);
this.updateInterval = null;
}
if (this.renderInterval) {
clearInterval(this.renderInterval);
this.renderInterval = null;
}
if (this.renderPosition && this.spinner) {
terminalController.saveCursorPosition();
terminalController.moveCursorTo(1, this.renderPosition.row);
terminalController.clearLine();
terminalController.restoreCursorPosition();
}
if (this.spinner) {
if (!this.renderPosition) {
this.spinner.stop();
}
this.spinner = null;
}
this.customMessage = null;
this.tokenCount = 0;
}
isRunning() {
if (this.renderPosition && this.renderInterval) {
return true;
}
return this.spinner !== null && this.spinner.isSpinning;
}
getStatus() {
if (!this.spinner || !this.spinner.isSpinning) {
return null;
}
const theme = this.providerThemes[this.currentProvider] || this.providerThemes.default;
const elapsed = Math.floor((Date.now() - this.startTime) / 1000);
let message;
if (this.customMessage) {
message = this.customMessage;
}
else if (this.modelMessages[this.currentModel]) {
message = this.modelMessages[this.currentModel];
}
else {
const messages = theme.messages;
const msgIdx = Math.floor(this.messageIndex / 20) % messages.length;
message = messages[msgIdx];
}
const frames = ['✶', '✷', '✸', '✹'];
const frameIndex = Math.floor((Date.now() - this.startTime) / 200) % frames.length;
return {
star: theme.color(frames[frameIndex]),
message: theme.color(message),
elapsed: `${elapsed}s`,
tokens: `${this.tokenCount} tokens in`
};
}
simpleLoading(text) {
return ora({
text,
spinner: 'dots',
hideCursor: true
}).start();
}
succeed(text) {
if (this.spinner) {
this.spinner.succeed(text);
this.stop();
}
}
fail(text) {
if (this.spinner) {
this.spinner.fail(text);
this.stop();
}
}
warn(text) {
if (this.spinner) {
this.spinner.warn(text);
this.stop();
}
}
info(text) {
if (this.spinner) {
this.spinner.info(text);
this.stop();
}
}
}
export const animationController = new AnimationController();
//# sourceMappingURL=animation-controller.js.map