#!/usr/bin/env node import { fileURLToPath } from 'url'; import path from 'path'; import fs from 'fs/promises'; import chalk from 'chalk'; import readline from 'readline'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const RESULTS_DIR = path.join(__dirname, 'results'); const VISUALIZATIONS_DIR = path.join(__dirname, 'visualizations'); const PUBLIC_DATA_DIR = path.join(__dirname, '..', '..', '..', '..', 'public', 'data'); /** * Interface de ligne de commande interactive */ function createInterface() { return readline.createInterface({ input: process.stdin, output: process.stdout }); } /** * Pose une question et retourne la réponse */ function askQuestion(rl, question) { return new Promise((resolve) => { rl.question(question, resolve); }); } /** * Liste tous les résultats de tests disponibles */ async function listTestResults() { try { const files = await fs.readdir(RESULTS_DIR); const resultFiles = files.filter(f => f.endsWith('.json')); const results = []; for (const file of resultFiles) { const filepath = path.join(RESULTS_DIR, file); const content = await fs.readFile(filepath, 'utf8'); const data = JSON.parse(content); results.push({ filename: file, filepath, testName: data.metadata.test_name, config: data.metadata.config, stats: data.metadata.stats, generatedAt: data.metadata.generated_at }); } // Trier par date de génération (plus récent en premier) results.sort((a, b) => new Date(b.generatedAt) - new Date(a.generatedAt)); return results; } catch (error) { console.error(chalk.red('❌ Erreur lors de la lecture des résultats:'), error.message); return []; } } /** * Affiche la liste des résultats disponibles */ function displayResults(results) { console.log(chalk.blue.bold('\n📋 Tests UMAP disponibles:\n')); if (results.length === 0) { console.log(chalk.yellow('⚠️ Aucun résultat de test trouvé.')); console.log(chalk.gray(' Exécutez d\'abord: npm run batch-test')); return; } results.forEach((result, index) => { const date = new Date(result.generatedAt).toLocaleString('fr-FR'); console.log(chalk.cyan(` ${index + 1}. ${result.testName}`)); console.log(chalk.gray(` 📊 ${result.stats.totalFonts} polices`)); console.log(chalk.gray(` 📏 Plage: [${result.stats.xRange[0].toFixed(1)}, ${result.stats.xRange[1].toFixed(1)}] x [${result.stats.yRange[0].toFixed(1)}, ${result.stats.yRange[1].toFixed(1)}]`)); console.log(chalk.gray(` ⚙️ nNeighbors: ${result.config.nNeighbors}, minDist: ${result.config.minDist}`)); console.log(chalk.gray(` 🎯 Weights: ${result.config.embeddingWeight}/${result.config.categoryWeight}`)); console.log(chalk.gray(` 🔗 Fusion: ${result.config.enableFontFusion ? 'ON' : 'OFF'}`)); console.log(chalk.gray(` 📅 ${date}`)); console.log(''); }); } /** * Affiche les détails d'un résultat */ function displayResultDetails(result) { console.log(chalk.blue.bold(`\n📊 Détails du test: ${result.testName}\n`)); console.log(chalk.yellow('⚙️ Configuration:')); console.log(chalk.gray(` nNeighbors: ${result.config.nNeighbors}`)); console.log(chalk.gray(` minDist: ${result.config.minDist}`)); console.log(chalk.gray(` metric: ${result.config.metric}`)); console.log(chalk.gray(` embeddingWeight: ${result.config.embeddingWeight}`)); console.log(chalk.gray(` categoryWeight: ${result.config.categoryWeight}`)); console.log(chalk.gray(` enableFontFusion: ${result.config.enableFontFusion}`)); console.log(chalk.gray(` fusionPrefixLength: ${result.config.fusionPrefixLength}`)); console.log(chalk.yellow('\n📈 Statistiques:')); console.log(chalk.gray(` Total polices: ${result.stats.totalFonts}`)); console.log(chalk.gray(` Dimensions embedding: ${result.stats.embeddingDimensions}`)); console.log(chalk.gray(` Dimensions catégorie: ${result.stats.categoryDimensions}`)); console.log(chalk.gray(` Plage X: [${result.stats.xRange[0].toFixed(2)}, ${result.stats.xRange[1].toFixed(2)}]`)); console.log(chalk.gray(` Plage Y: [${result.stats.yRange[0].toFixed(2)}, ${result.stats.yRange[1].toFixed(2)}]`)); console.log(chalk.yellow('\n📅 Métadonnées:')); console.log(chalk.gray(` Généré le: ${new Date(result.generatedAt).toLocaleString('fr-FR')}`)); console.log(chalk.gray(` Fichier: ${result.filename}`)); } /** * Déploie un résultat vers l'application */ async function deployResult(result) { try { console.log(chalk.blue('🔄 Déploiement vers l\'application...')); // Lire les données du résultat const content = await fs.readFile(result.filepath, 'utf8'); const data = JSON.parse(content); // Créer le dossier de destination s'il n'existe pas await fs.mkdir(PUBLIC_DATA_DIR, { recursive: true }); // Copier vers public/data/typography_data.json const targetPath = path.join(PUBLIC_DATA_DIR, 'typography_data.json'); await fs.writeFile(targetPath, content); console.log(chalk.green('✅ Données déployées avec succès !')); console.log(chalk.gray(` Source: ${result.filename}`)); console.log(chalk.gray(` Destination: public/data/typography_data.json`)); // Vérifier s'il y a une visualisation correspondante const vizFilename = result.filename.replace('.json', '.html'); const vizPath = path.join(VISUALIZATIONS_DIR, vizFilename); try { await fs.access(vizPath); console.log(chalk.blue('📊 Visualisation disponible:')); console.log(chalk.gray(` ${vizPath}`)); } catch { console.log(chalk.yellow('⚠️ Aucune visualisation trouvée pour ce test.')); } return true; } catch (error) { console.error(chalk.red('❌ Erreur lors du déploiement:'), error.message); return false; } } /** * Ouvre une visualisation dans le navigateur */ async function openVisualization(result) { try { const vizFilename = result.filename.replace('.json', '.html'); const vizPath = path.join(VISUALIZATIONS_DIR, vizFilename); await fs.access(vizPath); console.log(chalk.blue('🌐 Ouverture de la visualisation...')); console.log(chalk.gray(` Fichier: ${vizPath}`)); // Ouvrir dans le navigateur par défaut const { exec } = await import('child_process'); const { promisify } = await import('util'); const execAsync = promisify(exec); await execAsync(`open "${vizPath}"`); console.log(chalk.green('✅ Visualisation ouverte dans le navigateur !')); } catch (error) { console.error(chalk.red('❌ Erreur lors de l\'ouverture:'), error.message); } } /** * Menu principal interactif */ async function showMenu(results) { const rl = createInterface(); while (true) { console.log(chalk.blue.bold('\n🎛️ Menu de sélection UMAP\n')); console.log(chalk.gray('1. Lister tous les tests')); console.log(chalk.gray('2. Voir les détails d\'un test')); console.log(chalk.gray('3. Déployer un test vers l\'application')); console.log(chalk.gray('4. Ouvrir la visualisation d\'un test')); console.log(chalk.gray('5. Quitter')); const choice = await askQuestion(rl, '\n👉 Votre choix (1-5): '); switch (choice.trim()) { case '1': displayResults(results); break; case '2': if (results.length === 0) { console.log(chalk.yellow('⚠️ Aucun test disponible.')); break; } const detailIndex = await askQuestion(rl, `Numéro du test (1-${results.length}): `); const detailIdx = parseInt(detailIndex) - 1; if (detailIdx >= 0 && detailIdx < results.length) { displayResultDetails(results[detailIdx]); } else { console.log(chalk.red('❌ Numéro invalide.')); } break; case '3': if (results.length === 0) { console.log(chalk.yellow('⚠️ Aucun test disponible.')); break; } const deployIndex = await askQuestion(rl, `Numéro du test à déployer (1-${results.length}): `); const deployIdx = parseInt(deployIndex) - 1; if (deployIdx >= 0 && deployIdx < results.length) { const confirm = await askQuestion(rl, `Déployer "${results[deployIdx].testName}" ? (y/N): `); if (confirm.toLowerCase() === 'y' || confirm.toLowerCase() === 'yes') { await deployResult(results[deployIdx]); } else { console.log(chalk.yellow('❌ Déploiement annulé.')); } } else { console.log(chalk.red('❌ Numéro invalide.')); } break; case '4': if (results.length === 0) { console.log(chalk.yellow('⚠️ Aucun test disponible.')); break; } const vizIndex = await askQuestion(rl, `Numéro du test à visualiser (1-${results.length}): `); const vizIdx = parseInt(vizIndex) - 1; if (vizIdx >= 0 && vizIdx < results.length) { await openVisualization(results[vizIdx]); } else { console.log(chalk.red('❌ Numéro invalide.')); } break; case '5': console.log(chalk.green('👋 Au revoir !')); rl.close(); return; default: console.log(chalk.red('❌ Choix invalide.')); } } } /** * Mode ligne de commande direct */ async function directMode() { const args = process.argv.slice(2); if (args.length === 0) { console.log(chalk.red('❌ Usage: npm run select [test-name]')); console.log(chalk.gray(' ou: npm run select --list')); return; } const results = await listTestResults(); if (args[0] === '--list' || args[0] === '-l') { displayResults(results); return; } const testName = args[0]; const result = results.find(r => r.testName === testName); if (!result) { console.log(chalk.red(`❌ Test "${testName}" non trouvé.`)); console.log(chalk.gray('Utilisez --list pour voir les tests disponibles.')); return; } displayResultDetails(result); const rl = createInterface(); const deploy = await askQuestion(rl, 'Déployer ce test ? (y/N): '); rl.close(); if (deploy.toLowerCase() === 'y' || deploy.toLowerCase() === 'yes') { await deployResult(result); } } /** * Fonction principale */ async function main() { try { console.log(chalk.blue.bold('🎯 Sélection et déploiement UMAP\n')); const results = await listTestResults(); // Mode interactif si pas d'arguments if (process.argv.length === 2) { await showMenu(results); } else { // Mode ligne de commande await directMode(); } } catch (error) { console.error(chalk.red('💥 Erreur fatale:'), error.message); process.exit(1); } } // Exécuter si appelé directement if (import.meta.url === `file://${process.argv[1]}`) { main(); }