refacto: dashboard Flask Blueprints + templates Jinja2, engine run/lore_enricher mis a jour
This commit is contained in:
@@ -0,0 +1,329 @@
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
|
||||
<script>
|
||||
function copyText(elementId) {
|
||||
var el = document.getElementById(elementId);
|
||||
navigator.clipboard.writeText(el.textContent.trim()).then(function() {
|
||||
var btn = el.nextElementSibling;
|
||||
if (btn) { btn.textContent = '✓ Copié'; setTimeout(function(){ btn.textContent = 'Copier'; }, 2000); }
|
||||
});
|
||||
}
|
||||
|
||||
function genStressCmd() {
|
||||
var mode = document.querySelector('input[name="stress_mode"]:checked').value;
|
||||
var rounds = document.querySelector('input[name="stress_rounds"]:checked').value;
|
||||
var pnjRow = document.getElementById('stress_pnj_row');
|
||||
var pnj = '1';
|
||||
if (pnjRow.style.display !== 'none') {
|
||||
pnj = document.querySelector('input[name="stress_pnj"]:checked').value;
|
||||
}
|
||||
var cmd = 'cd /home/ubuntu/fallout-venice/tools && python3 llm_crash_test.py --mode ' + mode + ' --rounds ' + rounds;
|
||||
if (mode !== 'solo-mj' && mode !== 'solo-pnj') cmd += ' --pnj-count ' + pnj;
|
||||
document.getElementById('stress_cmd_text').textContent = cmd;
|
||||
document.getElementById('stress_cmd_out').style.display = 'block';
|
||||
}
|
||||
|
||||
function togglePnjRow() {
|
||||
var mode = document.querySelector('input[name="stress_mode"]:checked').value;
|
||||
document.getElementById('stress_pnj_row').style.display =
|
||||
(mode === 'solo-mj' || mode === 'solo-pnj') ? 'none' : 'flex';
|
||||
}
|
||||
|
||||
function loadStressResults() {
|
||||
var area = document.getElementById('stress_results');
|
||||
area.innerHTML = '<p style="color:var(--muted);padding:8px 0">Chargement…</p>';
|
||||
fetch('/api/stress-test/results')
|
||||
.then(function(r){ return r.json(); })
|
||||
.then(function(data) {
|
||||
if (data.error) { area.innerHTML = '<p style="color:var(--red);padding:8px 0">' + data.error + '</p>'; return; }
|
||||
renderStressResults(area, data);
|
||||
});
|
||||
}
|
||||
|
||||
function renderStressResults(area, data) {
|
||||
var html = '<div class="results-meta">Fichier du ' + (data.file_date || '?') + '</div>';
|
||||
html += '<table><tr><th>Modèle</th><th>Appels</th><th>Erreurs</th><th>Moy tok/s</th><th>Min tok/s</th><th>Max tok/s</th><th>Moy durée</th></tr>';
|
||||
var models = data.models || {};
|
||||
for (var model in models) {
|
||||
var m = models[model];
|
||||
var avgTok = parseFloat(m.avg_tok_s || 0);
|
||||
var threshold = model.indexOf('14b') !== -1 ? 8 : 15;
|
||||
var cls = avgTok >= threshold ? 'diag-good' : 'diag-bad';
|
||||
html += '<tr><td class="' + cls + '">' + model + '</td><td>' + (m.calls||0) + '</td>';
|
||||
html += '<td style="color:' + ((m.errors||0) > 0 ? 'var(--red)' : 'var(--muted)') + '">' + (m.errors||0) + '</td>';
|
||||
html += '<td class="' + cls + '">' + avgTok.toFixed(1) + '</td>';
|
||||
html += '<td style="color:var(--muted)">' + parseFloat(m.min_tok_s||0).toFixed(1) + '</td>';
|
||||
html += '<td style="color:var(--muted)">' + parseFloat(m.max_tok_s||0).toFixed(1) + '</td>';
|
||||
html += '<td style="color:var(--muted)">' + parseFloat(m.avg_duration||0).toFixed(2) + 's</td></tr>';
|
||||
}
|
||||
html += '</table>';
|
||||
if (data.pipeline) {
|
||||
var p = data.pipeline;
|
||||
html += '<div style="margin-top:12px"><strong style="color:var(--amber)">Pipeline (décalé)</strong></div>';
|
||||
html += '<table><tr><th>Latence moy</th><th>Min</th><th>Max</th></tr>';
|
||||
html += '<tr><td>' + (p.avg_latency||'—') + '</td><td>' + (p.min_latency||'—') + '</td><td>' + (p.max_latency||'—') + '</td></tr></table>';
|
||||
}
|
||||
area.innerHTML = html;
|
||||
}
|
||||
|
||||
var _stressPollTimer = null;
|
||||
var _stressLastLine = 0;
|
||||
|
||||
function runStress() {
|
||||
var mode = document.querySelector('input[name="stress_mode"]:checked').value;
|
||||
var rounds = document.querySelector('input[name="stress_rounds"]:checked').value;
|
||||
var pnj = 2;
|
||||
if (mode !== 'solo-mj' && mode !== 'solo-pnj')
|
||||
pnj = parseInt(document.querySelector('input[name="stress_pnj"]:checked').value);
|
||||
fetch('/api/stress-test/run', {
|
||||
method: 'POST', headers: {'Content-Type':'application/json'},
|
||||
body: JSON.stringify({mode:mode, rounds:parseInt(rounds), pnj_count:pnj})
|
||||
}).then(function(r){ return r.json(); }).then(function(d) {
|
||||
if (d.error) { alert(d.error); return; }
|
||||
_stressLastLine = 0;
|
||||
document.getElementById('stress_console').textContent = '';
|
||||
document.getElementById('stress_console_wrap').style.display = 'block';
|
||||
document.getElementById('stress_status_bar').style.display = 'block';
|
||||
document.getElementById('stress_status_text').textContent = 'Test ' + mode + ' en cours (' + rounds + ' rounds)…';
|
||||
document.getElementById('btn-run-stress').disabled = true;
|
||||
document.getElementById('btn-stop-stress').style.display = 'inline-block';
|
||||
document.getElementById('stress_results').innerHTML = '';
|
||||
_stressPollTimer = setInterval(function() {
|
||||
fetch('/api/stress-test/status').then(function(r){ return r.json(); }).then(function(d) {
|
||||
var cons = document.getElementById('stress_console');
|
||||
var lines = d.output || [];
|
||||
if (lines.length > _stressLastLine) {
|
||||
cons.textContent += lines.slice(_stressLastLine).join('\n') + '\n';
|
||||
cons.scrollTop = cons.scrollHeight;
|
||||
_stressLastLine = lines.length;
|
||||
}
|
||||
if (!d.running) {
|
||||
clearInterval(_stressPollTimer);
|
||||
document.getElementById('stress_status_text').textContent =
|
||||
d.returncode === 0 ? '✓ Terminé avec succès' : '✗ Erreur (code ' + d.returncode + ')';
|
||||
document.getElementById('stress_spinner').textContent = d.returncode === 0 ? '' : '⚠';
|
||||
document.getElementById('btn-run-stress').disabled = false;
|
||||
document.getElementById('btn-stop-stress').style.display = 'none';
|
||||
if (d.returncode === 0) loadStressResults();
|
||||
}
|
||||
});
|
||||
}, 1500);
|
||||
});
|
||||
}
|
||||
|
||||
var _procPolls = {};
|
||||
var _procLastLine = {};
|
||||
|
||||
function procRun(name, extra) {
|
||||
fetch('/api/proc/' + name + '/run', {
|
||||
method:'POST', headers:{'Content-Type':'application/json'},
|
||||
body: JSON.stringify(extra)
|
||||
}).then(function(r){ return r.json(); }).then(function(d){
|
||||
if (d.error) { alert(d.error); return; }
|
||||
_procLastLine[name] = 0;
|
||||
document.getElementById('console-' + name).textContent = '';
|
||||
document.getElementById('console-' + name).style.display = 'block';
|
||||
document.getElementById('btn-run-' + name).disabled = true;
|
||||
document.getElementById('btn-stop-' + name).style.display = 'inline-block';
|
||||
document.getElementById('dot-' + name).className = 'dot dot-green';
|
||||
if (_procPolls[name]) clearInterval(_procPolls[name]);
|
||||
_procPolls[name] = setInterval(function(){ procPoll(name); }, 1500);
|
||||
});
|
||||
}
|
||||
|
||||
function procPoll(name) {
|
||||
fetch('/api/proc/' + name + '/status').then(function(r){ return r.json(); }).then(function(d){
|
||||
var cons = document.getElementById('console-' + name);
|
||||
var lines = d.output || [];
|
||||
if (lines.length > (_procLastLine[name]||0)) {
|
||||
cons.textContent += lines.slice(_procLastLine[name]||0).join('\n') + '\n';
|
||||
cons.scrollTop = cons.scrollHeight;
|
||||
_procLastLine[name] = lines.length;
|
||||
}
|
||||
var iterEl = document.getElementById('iter-' + name);
|
||||
if (iterEl && d.iteration) {
|
||||
iterEl.textContent = 'Itération ' + d.iteration + ' / ' + (d.total === 0 ? '∞' : d.total);
|
||||
iterEl.style.display = 'block';
|
||||
}
|
||||
if (!d.running) {
|
||||
clearInterval(_procPolls[name]);
|
||||
document.getElementById('btn-run-' + name).disabled = false;
|
||||
document.getElementById('btn-stop-' + name).style.display = 'none';
|
||||
document.getElementById('dot-' + name).className = 'dot ' + (d.returncode === 0 ? 'dot-green' : 'dot-red');
|
||||
if (iterEl) iterEl.style.display = 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function procStop(name) {
|
||||
fetch('/api/proc/' + name + '/stop', {method:'POST'}).then(function(){ procPoll(name); });
|
||||
}
|
||||
|
||||
(function(){
|
||||
['sim_enricher','lore_enricher','embed_fallout','embed_sim','mix'].forEach(function(name){
|
||||
fetch('/api/proc/' + name + '/status').then(function(r){ return r.json(); }).then(function(d){
|
||||
if (d.running) {
|
||||
document.getElementById('btn-run-' + name).disabled = true;
|
||||
document.getElementById('btn-stop-' + name).style.display = 'inline-block';
|
||||
document.getElementById('dot-' + name).className = 'dot dot-green';
|
||||
document.getElementById('console-' + name).style.display = 'block';
|
||||
_procLastLine[name] = (d.output||[]).length;
|
||||
_procPolls[name] = setInterval(function(){ procPoll(name); }, 1500);
|
||||
}
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
<!-- Stress Test -->
|
||||
<div class="tools-section">
|
||||
<h2>⚡ Stress Test LLM</h2>
|
||||
<div class="tool-row">
|
||||
<label>Mode</label>
|
||||
<div class="radio-group">
|
||||
<label><input type="radio" name="stress_mode" value="simultane" checked onchange="togglePnjRow()"><span>simultane</span></label>
|
||||
<label><input type="radio" name="stress_mode" value="decale" onchange="togglePnjRow()"><span>decale</span></label>
|
||||
<label><input type="radio" name="stress_mode" value="solo-mj" onchange="togglePnjRow()"><span>solo-mj</span></label>
|
||||
<label><input type="radio" name="stress_mode" value="solo-pnj" onchange="togglePnjRow()"><span>solo-pnj</span></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tool-row">
|
||||
<label>Rounds</label>
|
||||
<div class="radio-group">
|
||||
<label><input type="radio" name="stress_rounds" value="3" checked><span>3</span></label>
|
||||
<label><input type="radio" name="stress_rounds" value="5"><span>5</span></label>
|
||||
<label><input type="radio" name="stress_rounds" value="10"><span>10</span></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tool-row" id="stress_pnj_row">
|
||||
<label>PNJ count</label>
|
||||
<div class="radio-group">
|
||||
<label><input type="radio" name="stress_pnj" value="1" checked><span>1</span></label>
|
||||
<label><input type="radio" name="stress_pnj" value="2"><span>2</span></label>
|
||||
<label><input type="radio" name="stress_pnj" value="3"><span>3</span></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tool-row" style="gap:8px;flex-wrap:wrap">
|
||||
<button class="btn btn-green" id="btn-run-stress" onclick="runStress()">▶ Exécuter maintenant</button>
|
||||
<button class="btn btn-amber" onclick="genStressCmd()">Voir commande SSH</button>
|
||||
<button class="btn" onclick="loadStressResults()">Charger derniers résultats</button>
|
||||
<button class="btn btn-red" id="btn-stop-stress" style="display:none" onclick="clearInterval(_stressPollTimer);document.getElementById('btn-run-stress').disabled=false;this.style.display='none'">■ Arrêter le suivi</button>
|
||||
</div>
|
||||
<div id="stress_status_bar" style="display:none;margin-top:8px;padding:6px 10px;background:#0d1117;border:1px solid var(--border);border-radius:4px;font-size:13px">
|
||||
<span id="stress_status_text" style="color:var(--amber)">En cours…</span>
|
||||
<span id="stress_spinner" style="margin-left:8px">⏳</span>
|
||||
</div>
|
||||
<div class="cmd-output" id="stress_cmd_out" style="display:none">
|
||||
<div class="code-block"><pre id="stress_cmd_text"></pre><button class="copy-btn" onclick="copyText('stress_cmd_text')">Copier</button></div>
|
||||
</div>
|
||||
<div id="stress_console_wrap" style="display:none;margin-top:10px">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:4px">
|
||||
<span style="font-size:12px;font-weight:600;color:var(--dim)">SORTIE EN DIRECT</span>
|
||||
<button class="btn" style="padding:2px 8px;font-size:11px" onclick="document.getElementById('stress_console').innerHTML=''">Effacer</button>
|
||||
</div>
|
||||
<pre id="stress_console" style="background:#020408;border:1px solid var(--border);padding:10px;max-height:320px;overflow-y:auto;font-size:12px;color:#a8d5a2;white-space:pre-wrap;border-radius:4px"></pre>
|
||||
</div>
|
||||
<div class="results-area" id="stress_results"></div>
|
||||
</div>
|
||||
|
||||
<!-- Enrichissement LLM -->
|
||||
<div class="tools-section">
|
||||
<h2>🧠 Enrichissement LLM</h2>
|
||||
<div id="enrich-cards" style="display:flex;flex-direction:column;gap:12px">
|
||||
|
||||
<div class="enrich-card" id="card-sim_enricher">
|
||||
<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap">
|
||||
<span style="flex:1;font-weight:600;color:var(--amber)">🎲 Règles sim → sim_proposals</span>
|
||||
<select id="sim_enricher_type" style="width:148px">
|
||||
<option value="all">all</option>
|
||||
<option value="nouveau_mode">nouveau_mode</option>
|
||||
<option value="evenement_special">evenement_special</option>
|
||||
<option value="regle_maison">regle_maison</option>
|
||||
<option value="scenario">scenario</option>
|
||||
</select>
|
||||
<select id="count-sim_enricher" style="width:62px">
|
||||
<option value="1">1×</option><option value="5">5×</option>
|
||||
<option value="10" selected>10×</option><option value="50">50×</option>
|
||||
<option value="100">100×</option><option value="0">∞</option>
|
||||
</select>
|
||||
<button class="btn btn-green" id="btn-run-sim_enricher" onclick="procRun('sim_enricher',{type:document.getElementById('sim_enricher_type').value,count:parseInt(document.getElementById('count-sim_enricher').value)})">▶ Lancer</button>
|
||||
<button class="btn btn-red" id="btn-stop-sim_enricher" style="display:none" onclick="procStop('sim_enricher')">■ Stop</button>
|
||||
<span class="dot dot-red" id="dot-sim_enricher" style="margin-left:4px"></span>
|
||||
</div>
|
||||
<div id="iter-sim_enricher" style="font-size:11px;color:var(--dim);margin-top:4px;display:none"></div>
|
||||
<div id="console-sim_enricher" class="mini-console" style="display:none"></div>
|
||||
</div>
|
||||
|
||||
<div class="enrich-card" id="card-lore_enricher">
|
||||
<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap">
|
||||
<span style="flex:1;font-weight:600;color:var(--amber)">📜 Lore → lore_proposals</span>
|
||||
<select id="lore_enricher_faction" style="width:148px">
|
||||
<option value="all">all</option>
|
||||
<option value="union">union</option><option value="cda">cda</option>
|
||||
<option value="ecumeurs">ecumeurs</option><option value="grand_krewe">grand_krewe</option>
|
||||
<option value="dynaste_oak">dynaste_oak</option><option value="regie">regie</option>
|
||||
<option value="syndicat_capitole">syndicat_capitole</option><option value="consortium">consortium</option>
|
||||
</select>
|
||||
<select id="count-lore_enricher" style="width:62px">
|
||||
<option value="1">1×</option><option value="5">5×</option>
|
||||
<option value="10" selected>10×</option><option value="50">50×</option>
|
||||
<option value="100">100×</option><option value="0">∞</option>
|
||||
</select>
|
||||
<button class="btn btn-green" id="btn-run-lore_enricher" onclick="procRun('lore_enricher',{faction:document.getElementById('lore_enricher_faction').value,count:parseInt(document.getElementById('count-lore_enricher').value)})">▶ Lancer</button>
|
||||
<button class="btn btn-red" id="btn-stop-lore_enricher" style="display:none" onclick="procStop('lore_enricher')">■ Stop</button>
|
||||
<span class="dot dot-red" id="dot-lore_enricher" style="margin-left:4px"></span>
|
||||
</div>
|
||||
<div id="iter-lore_enricher" style="font-size:11px;color:var(--dim);margin-top:4px;display:none"></div>
|
||||
<div id="console-lore_enricher" class="mini-console" style="display:none"></div>
|
||||
</div>
|
||||
|
||||
<div class="enrich-card" id="card-mix" style="border-color:var(--green)">
|
||||
<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap">
|
||||
<span style="flex:1;font-weight:600;color:var(--green)">🔄 Mode MIX — sim + lore en alternance</span>
|
||||
<select id="count-mix" style="width:80px">
|
||||
<option value="5">5× chaque</option><option value="10" selected>10× chaque</option>
|
||||
<option value="50">50× chaque</option><option value="0">∞ chaque</option>
|
||||
</select>
|
||||
<button class="btn btn-green" id="btn-run-mix" onclick="procRun('mix',{count:parseInt(document.getElementById('count-mix').value)})">▶ Lancer</button>
|
||||
<button class="btn btn-red" id="btn-stop-mix" style="display:none" onclick="procStop('mix')">■ Stop</button>
|
||||
<span class="dot dot-red" id="dot-mix" style="margin-left:4px"></span>
|
||||
</div>
|
||||
<div id="iter-mix" style="font-size:11px;color:var(--dim);margin-top:4px;display:none"></div>
|
||||
<div id="console-mix" class="mini-console" style="display:none"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Re-embedding -->
|
||||
<div class="tools-section" style="opacity:0.82">
|
||||
<h2 style="color:var(--dim)">🔁 Re-embedding <small style="font-size:11px;font-weight:400;margin-left:8px">Opération rare — seulement si nouveaux PDFs ou configs modifiées</small></h2>
|
||||
<div style="display:flex;flex-direction:column;gap:10px">
|
||||
|
||||
<div class="enrich-card" id="card-embed_fallout" style="border-color:var(--border)">
|
||||
<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap">
|
||||
<span style="flex:1;font-weight:600;color:var(--dim)">PDFs Fallout → fallout_lore</span>
|
||||
<select id="count-embed_fallout" style="width:62px"><option value="1" selected>1×</option><option value="5">5×</option></select>
|
||||
<button class="btn btn-green" id="btn-run-embed_fallout" onclick="procRun('embed_fallout',{count:parseInt(document.getElementById('count-embed_fallout').value)})">▶ Lancer</button>
|
||||
<button class="btn btn-red" id="btn-stop-embed_fallout" style="display:none" onclick="procStop('embed_fallout')">■ Stop</button>
|
||||
<span class="dot dot-red" id="dot-embed_fallout" style="margin-left:4px"></span>
|
||||
</div>
|
||||
<div id="iter-embed_fallout" style="font-size:11px;color:var(--dim);margin-top:4px;display:none"></div>
|
||||
<div id="console-embed_fallout" class="mini-console" style="display:none"></div>
|
||||
</div>
|
||||
|
||||
<div class="enrich-card" id="card-embed_sim" style="border-color:var(--border)">
|
||||
<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap">
|
||||
<span style="flex:1;font-weight:600;color:var(--dim)">Configs sim → fallout_sim_rules</span>
|
||||
<select id="count-embed_sim" style="width:62px"><option value="1" selected>1×</option><option value="5">5×</option></select>
|
||||
<button class="btn btn-green" id="btn-run-embed_sim" onclick="procRun('embed_sim',{count:parseInt(document.getElementById('count-embed_sim').value)})">▶ Lancer</button>
|
||||
<button class="btn btn-red" id="btn-stop-embed_sim" style="display:none" onclick="procStop('embed_sim')">■ Stop</button>
|
||||
<span class="dot dot-red" id="dot-embed_sim" style="margin-left:4px"></span>
|
||||
</div>
|
||||
<div id="iter-embed_sim" style="font-size:11px;color:var(--dim);margin-top:4px;display:none"></div>
|
||||
<div id="console-embed_sim" class="mini-console" style="display:none"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user