Files

330 lines
18 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% 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 %}