Spaces:
Build error
Build error
File size: 6,511 Bytes
bd2463e 40f299d 84ea3a0 bd2463e 84ea3a0 53561cf bd2463e 40f299d 9389ef1 bd2463e 84ea3a0 bd2463e 40f299d 254b9d0 bd2463e 40f299d bd2463e 84ea3a0 9389ef1 40f299d 84ea3a0 40f299d 254b9d0 40f299d 254b9d0 40f299d 9389ef1 c18ee50 40f299d 254b9d0 40f299d 254b9d0 40f299d 254b9d0 40f299d 9389ef1 40f299d 9389ef1 40f299d 9389ef1 40f299d bd2463e 40f299d 9389ef1 40f299d 9389ef1 bd2463e 40f299d 9389ef1 40f299d 9389ef1 40f299d 9389ef1 40f299d 9389ef1 40f299d 9389ef1 bd2463e 84ea3a0 40f299d 9389ef1 40f299d 9389ef1 40f299d 84ea3a0 c18ee50 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 | import express from 'express';
import multer from 'multer';
import AdmZip from 'adm-zip';
import { pipeline, env } from '@xenova/transformers';
import fs from 'fs';
env.allowLocalModels = false;
env.useBrowserCache = false;
const app = express();
const upload = multer({ storage: multer.memoryStorage() });
const LANG_MAP = {
'fr_fr': 'fra_Latn', 'es_es': 'spa_Latn', 'de_de': 'deu_Latn', 'it_it': 'ita_Latn',
'pt_pt': 'por_Latn', 'ru_ru': 'rus_Cyrl', 'ja_jp': 'jpn_Jpan', 'zh_cn': 'zho_Hans',
'ko_kr': 'kor_Hang', 'pl_pl': 'pol_Latn', 'uk_ua': 'ukr_Cyrl', 'tr_tr': 'tur_Latn',
'nl_nl': 'nld_Latn', 'sv_se': 'swe_Latn', 'da_dk': 'dan_Latn', 'no_no': 'nob_Latn'
};
let translator;
// Stockage temporaire des tâches de traduction en cours
const jobs = {};
pipeline('translation', 'Xenova/nllb-200-distilled-600M').then(model => {
translator = model;
console.log("✅ Modèle d'IA prêt !");
});
app.use(express.static('public'));
app.use(express.json());
// Petite pause pour laisser respirer l'événement de Node.js (évite de figer le serveur)
const breathe = () => new Promise(resolve => setImmediate(resolve));
// API : Lecture des limites réelles du conteneur Docker (cgroups Linux)
app.get('/api/stats', (req, res) => {
let usedRam = 0;
let totalRam = 16; // Valeur par défaut sur HF Basic CPU
try {
// Lecture de la RAM consommée par le Docker
if (fs.existsSync('/sys/fs/cgroup/memory/memory.usage_in_bytes')) {
usedRam = parseInt(fs.readFileSync('/sys/fs/cgroup/memory/memory.usage_in_bytes', 'utf8'), 10);
totalRam = parseInt(fs.readFileSync('/sys/fs/cgroup/memory/memory.limit_in_bytes', 'utf8'), 10);
} else if (fs.existsSync('/sys/fs/cgroup/memory.current')) { // cgroups v2
usedRam = parseInt(fs.readFileSync('/sys/fs/cgroup/memory.current', 'utf8'), 10);
totalRam = parseInt(fs.readFileSync('/sys/fs/cgroup/memory.max', 'utf8'), 10);
}
usedRam = usedRam / (1024 ** 3);
totalRam = totalRam / (1024 ** 3);
} catch (e) {
usedRam = 3.5; // Fallback visuel si hors-linux
}
// Si l'IA tourne, on simule l'usage CPU alloué au conteneur
const activeJobs = Object.values(jobs).some(j => !j.done && !j.error);
const cpuPercent = activeJobs ? Math.floor(Math.random() * 20) + 75 : 1;
res.json({
cpu: cpuPercent,
usedRam: usedRam.toFixed(1),
totalRam: Math.round(totalRam),
ramPercent: Math.round((usedRam / totalRam) * 100)
});
});
// Route 1 : Soumission du fichier (Réponse immédiate)
app.post('/api/translate', upload.array('mods'), (req, res) => {
if (!translator) return res.status(503).send("L'IA charge encore son modèle.");
let targetLangs = req.body.langs;
if (!targetLangs) return res.status(400).send("Aucune langue sélectionnée.");
if (!Array.isArray(targetLangs)) targetLangs = [targetLangs];
const jobId = Date.now().toString();
jobs[jobId] = { percent: 0, status: "Fichier reçu, analyse...", done: false, error: null, base64: null };
res.json({ jobId });
// Lancement asynchrone en tâche de fond
processTranslation(jobId, req.files, targetLangs);
});
// Route 2 : Le navigateur demande l'état de sa tâche
app.get('/api/status/:id', (req, res) => {
const job = jobs[req.params.id];
if (!job) return res.status(404).json({ error: "Tâche introuvable" });
res.json(job);
// Nettoyage de la mémoire si c'est fini pour éviter de saturer le Docker
if (job.done || job.error) {
setTimeout(() => { delete jobs[req.params.id]; }, 60000);
}
});
async function processTranslation(jobId, files, targetLangs) {
const job = jobs[jobId];
try {
const outZip = new AdmZip();
outZip.addFile('pack.mcmeta', Buffer.from(JSON.stringify({
pack: { pack_format: 15, description: "Traductions générées par IA" }
}, null, 2)));
let totalTasks = files.length * targetLangs.length;
let taskCount = 0;
for (const file of files) {
const modZip = new AdmZip(file.buffer);
const entries = modZip.getEntries();
const enEntries = entries.filter(e => e.entryName.match(/^assets\/([^\/]+)\/lang\/en_us\.json$/));
if (enEntries.length === 0) {
taskCount += targetLangs.length;
continue;
}
for (const enEntry of enEntries) {
const namespace = enEntry.entryName.split('/')[1];
const enJson = JSON.parse(modZip.readAsText(enEntry));
const keys = Object.keys(enJson);
const texts = Object.values(enJson);
for (const lang of targetLangs) {
taskCount++;
job.percent = Math.min(95, Math.floor((taskCount / totalTasks) * 90));
job.status = `Traduction [${lang}] de ${namespace}...`;
await breathe();
const expectedPath = `assets/${namespace}/lang/${lang}.json`;
if (entries.some(e => e.entryName === expectedPath)) continue;
const translatedJson = {};
const batchSize = 4;
for (let i = 0; i < texts.length; i += batchSize) {
const batchTexts = texts.slice(i, i + batchSize);
const batchKeys = keys.slice(i, i + batchSize);
const promises = batchTexts.map(text =>
translator(text, { src_lang: 'eng_Latn', tgt_lang: LANG_MAP[lang] || 'fra_Latn' })
);
const batchResults = await Promise.all(promises);
batchResults.forEach((res, index) => {
translatedJson[batchKeys[index]] = res[0].translation_text;
});
await breathe();
}
outZip.addFile(expectedPath, Buffer.from(JSON.stringify(translatedJson, null, 2), 'utf-8'));
}
}
}
job.percent = 100;
job.status = "Terminé !";
job.base64 = outZip.toBuffer().toString('base64');
job.done = true;
} catch (err) {
job.error = err.message;
}
}
app.listen(7860, '0.0.0.0', () => console.log('🚀 Serveur prêt sur le port 7860'));
|