|
|
#!/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'); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function createInterface() { |
|
|
return readline.createInterface({ |
|
|
input: process.stdin, |
|
|
output: process.stdout |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function askQuestion(rl, question) { |
|
|
return new Promise((resolve) => { |
|
|
rl.question(question, resolve); |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
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 []; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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(''); |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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}`)); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function deployResult(result) { |
|
|
try { |
|
|
console.log(chalk.blue('🔄 Déploiement vers l\'application...')); |
|
|
|
|
|
|
|
|
const content = await fs.readFile(result.filepath, 'utf8'); |
|
|
const data = JSON.parse(content); |
|
|
|
|
|
|
|
|
await fs.mkdir(PUBLIC_DATA_DIR, { recursive: true }); |
|
|
|
|
|
|
|
|
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`)); |
|
|
|
|
|
|
|
|
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; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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}`)); |
|
|
|
|
|
|
|
|
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); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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.')); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function main() { |
|
|
try { |
|
|
console.log(chalk.blue.bold('🎯 Sélection et déploiement UMAP\n')); |
|
|
|
|
|
const results = await listTestResults(); |
|
|
|
|
|
|
|
|
if (process.argv.length === 2) { |
|
|
await showMenu(results); |
|
|
} else { |
|
|
|
|
|
await directMode(); |
|
|
} |
|
|
|
|
|
} catch (error) { |
|
|
console.error(chalk.red('💥 Erreur fatale:'), error.message); |
|
|
process.exit(1); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (import.meta.url === `file://${process.argv[1]}`) { |
|
|
main(); |
|
|
} |
|
|
|