ollama-code-qwen
Version:
Un assistant IA en ligne de commande utilisant Ollama et le modèle qwen2.5-coder pour aider au développement, avec des capacités MCP améliorées et détection d'intentions en français et anglais
333 lines (287 loc) • 11.5 kB
JavaScript
/**
* Mode interactif léger pour les terminaux distants ou à faible performance
* Cette version utilise des interfaces textuelles simples au lieu des widgets TUI complexes
*/
import readline from 'readline';
import chalk from 'chalk';
import { marked } from 'marked';
import TerminalRenderer from 'marked-terminal';
import { PromptService } from '../services/prompt-service.js';
// Configuration du renderer pour Markdown
marked.setOptions({
renderer: new TerminalRenderer({
code: chalk.cyan,
blockquote: chalk.gray.italic,
table: chalk.white,
listitem: chalk.yellow,
strong: chalk.bold.green,
em: chalk.italic.cyan,
heading: chalk.bold.blueBright,
})
});
export class LightMode {
/**
* Initialise le mode interactif léger
* @param {OllamaService} ollamaService - Service Ollama
* @param {ContextManager} contextManager - Gestionnaire de contexte
*/
constructor(ollamaService, contextManager) {
this.ollamaService = ollamaService;
this.contextManager = contextManager;
this.config = contextManager.config;
this.promptService = new PromptService(this.config);
// Interface readline pour entrées utilisateur
this.rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
prompt: chalk.blue('> '),
terminal: true
});
// Historique de conversation
this.conversation = [];
this.projectContext = '';
this.modelName = ollamaService.modelName;
// Etat de chargement
this.isLoading = false;
this.loadingMessage = '';
this.loadingInterval = null;
this.projectInfo = null;
}
/**
* Démarrer le mode interactif léger
*/
async start() {
console.clear();
this._printHeader();
try {
this._startLoading('Chargement du contexte du projet...');
this.projectContext = await this.contextManager.getContext();
this.projectInfo = await this.contextManager.detectProjectType();
this._stopLoading();
// Initialiser la conversation avec le contexte du projet
const projectTypeInfo = this.projectInfo?.language ?
`Project type: ${this.projectInfo.language}${this.projectInfo.framework ? ' with ' + this.projectInfo.framework : ''}` :
'';
this.conversation = [
{
role: 'system',
content: `You are an AI coding assistant. You help with programming tasks and questions.\n\n${projectTypeInfo}\n\nCurrent project context:\n${this.projectContext}`
}
];
console.log(chalk.green('\nContexte du projet chargé. Utilisez /help pour voir les commandes disponibles.'));
console.log(chalk.yellow('Mode léger activé pour les terminaux distants.'));
console.log(projectTypeInfo ? chalk.blue(`\nDétecté: ${projectTypeInfo}`) : '');
console.log('\n' + chalk.gray('Entrez votre question ou commande:'));
this.rl.prompt();
this.rl.on('line', async (line) => {
const input = line.trim();
if (input.startsWith('/')) {
await this._handleCommand(input);
} else if (input) {
await this._handleQuestion(input);
}
if (!this.isLoading) {
this.rl.prompt();
}
});
this.rl.on('close', () => {
console.log(chalk.green('\nAu revoir !'));
process.exit(0);
});
} catch (error) {
this._stopLoading();
console.error(chalk.red(`Erreur de démarrage: ${error.message}`));
process.exit(1);
}
}
/**
* Affiche l'en-tête de l'application
*/
_printHeader() {
console.log('\n' + chalk.bgGreen.black(' Ollama Code ') + chalk.green(' - Mode léger pour terminaux distants\n'));
console.log(`Modèle: ${chalk.yellow(this.modelName)}`);
console.log(chalk.gray('Utilisez /exit pour quitter, /help pour l\'aide\n'));
}
/**
* Traite les questions de l'utilisateur
* @param {string} question - Question de l'utilisateur
*/
async _handleQuestion(question) {
try {
// Ajouter la question à la conversation
this.conversation.push({ role: 'user', content: question });
// Optimiser le prompt système en fonction de la question
const promptConfig = this.promptService.generateSystemPrompt(
question,
this.projectContext
);
// Mettre à jour le message système
if (this.conversation.length > 1 && this.conversation[0].role === 'system') {
this.conversation[0].content = promptConfig.systemPrompt;
}
// Afficher l'attente
this._startLoading('Réflexion en cours...');
// Obtenir la réponse
const response = await this.ollamaService.chat(
this.conversation,
promptConfig.temperature,
this.config.get('maxTokens') || 4096
);
this._stopLoading();
// Ajouter la réponse à la conversation
this.conversation.push({ role: 'assistant', content: response });
// Afficher la réponse
console.log('\n' + chalk.green('Réponse:'));
console.log(marked(response));
console.log(''); // Ligne vide pour la lisibilité
} catch (error) {
this._stopLoading();
console.error(chalk.red(`Erreur: ${error.message}`));
}
}
/**
* Gère les commandes spéciales
* @param {string} command - Commande entrée par l'utilisateur
*/
async _handleCommand(command) {
const cmd = command.toLowerCase().trim();
try {
if (cmd === '/help') {
this._showHelp();
}
else if (cmd === '/exit' || cmd === '/quit') {
console.log(chalk.green('Au revoir !'));
process.exit(0);
}
else if (cmd === '/clear') {
console.clear();
this._printHeader();
}
else if (cmd === '/reset' || cmd === '/init') {
this._resetConversation();
console.log(chalk.green('Conversation réinitialisée. Le contexte du projet est conservé.'));
}
else if (cmd === '/refresh') {
await this._refreshContext();
}
else if (cmd === '/context') {
console.log(chalk.yellow('\nContexte du projet:'));
// Limiter l'affichage du contexte pour éviter de surcharger le terminal
const contextPreview = this.projectContext.substring(0, 500);
console.log(contextPreview + (this.projectContext.length > 500 ? '...(tronqué)' : ''));
console.log(chalk.gray(`\nTaille totale: ${this.projectContext.length} caractères`));
}
else if (cmd === '/project-info') {
this._showProjectInfo();
}
else {
console.log(chalk.yellow(`Commande inconnue: ${command}. Tapez /help pour voir les commandes disponibles.`));
}
} catch (error) {
console.error(chalk.red(`Erreur d'exécution de la commande: ${error.message}`));
}
}
/**
* Affiche l'aide des commandes disponibles
*/
_showHelp() {
const helpText = `
${chalk.green('Commandes disponibles:')}
${chalk.cyan('/help')} - Affiche ce message d'aide
${chalk.cyan('/exit')}, ${chalk.cyan('/quit')} - Quitte l'application
${chalk.cyan('/clear')} - Efface l'écran
${chalk.cyan('/reset')}, ${chalk.cyan('/init')} - Réinitialise la conversation mais garde le contexte
${chalk.cyan('/refresh')} - Actualise le contexte du projet
${chalk.cyan('/context')} - Affiche le contexte du projet actuel
${chalk.cyan('/project-info')} - Affiche les informations détectées sur le projet
${chalk.yellow('Note: Ce mode léger offre des performances améliorées mais moins de fonctionnalités.')}
${chalk.yellow('Pour les fonctionnalités avancées, utilisez ollama-code avec l\'option --standard-mode.')}
`;
console.log(helpText);
}
/**
* Réinitialise la conversation mais conserve le contexte du projet
*/
_resetConversation() {
const projectTypeInfo = this.projectInfo?.language ?
`Project type: ${this.projectInfo.language}${this.projectInfo.framework ? ' with ' + this.projectInfo.framework : ''}` :
'';
this.conversation = [
{
role: 'system',
content: `You are an AI coding assistant. You help with programming tasks and questions.\n\n${projectTypeInfo}\n\nCurrent project context:\n${this.projectContext}`
}
];
}
/**
* Actualise le contexte du projet
*/
async _refreshContext() {
try {
this._startLoading('Actualisation du contexte du projet...');
this.projectContext = await this.contextManager.getContext();
// Mettre à jour le message système avec le nouveau contexte
const projectTypeInfo = this.projectInfo?.language ?
`Project type: ${this.projectInfo.language}${this.projectInfo.framework ? ' with ' + this.projectInfo.framework : ''}` :
'';
if (this.conversation.length > 0 && this.conversation[0].role === 'system') {
this.conversation[0].content = `You are an AI coding assistant. You help with programming tasks and questions.\n\n${projectTypeInfo}\n\nCurrent project context:\n${this.projectContext}`;
}
this._stopLoading();
console.log(chalk.green('\nContexte du projet actualisé.'));
} catch (error) {
this._stopLoading();
throw new Error(`Échec d'actualisation du contexte: ${error.message}`);
}
}
/**
* Affiche les informations détectées sur le projet
*/
_showProjectInfo() {
if (!this.projectInfo) {
console.log(chalk.yellow('\nAucune information disponible sur le projet. Essayez /refresh pour actualiser.'));
return;
}
console.log(chalk.green('\nInformations sur le projet:'));
console.log(chalk.yellow(`\n- Langage: ${this.projectInfo.language || 'Non détecté'}`));
console.log(chalk.yellow(`- Framework: ${this.projectInfo.framework || 'Aucun détecté'}`));
console.log(chalk.yellow(`- Type: ${this.projectInfo.type || 'Non détecté'}`));
if (this.projectInfo.buildTools && this.projectInfo.buildTools.length > 0) {
console.log(chalk.yellow(`- Outils de build: ${this.projectInfo.buildTools.join(', ')}`));
}
if (this.projectInfo.testing && this.projectInfo.testing.length > 0) {
console.log(chalk.yellow(`- Tests: ${this.projectInfo.testing.join(', ')}`));
}
if (this.projectInfo.database) {
console.log(chalk.yellow(`- Base de données: ${this.projectInfo.database}`));
}
}
/**
* Démarre un indicateur de chargement
* @param {string} message - Message à afficher
*/
_startLoading(message) {
this.isLoading = true;
this.loadingMessage = message || 'Chargement';
let dots = 0;
// Afficher le message initial
process.stdout.write(`\r${this.loadingMessage}... `);
// Mettre à jour les points périodiquement
this.loadingInterval = setInterval(() => {
dots = (dots + 1) % 4;
const dotsStr = '.'.repeat(dots);
process.stdout.write(`\r${this.loadingMessage}${dotsStr.padEnd(4)}`);
}, 500);
}
/**
* Arrête l'indicateur de chargement
*/
_stopLoading() {
if (this.loadingInterval) {
clearInterval(this.loadingInterval);
this.loadingInterval = null;
// Effacer la ligne
process.stdout.write(`\r${' '.repeat(this.loadingMessage.length + 10)}\r`);
this.isLoading = false;
}
}