142 lines
7.9 KiB
HTML
142 lines
7.9 KiB
HTML
{% extends "base.html" %}
|
|
{% block content %}
|
|
|
|
<div class="info-bar">
|
|
<span style="font-weight:600">📜 Enrichissement Lore — Venice of Wasteland</span>
|
|
<div class="lore-stat-bar" style="margin:0">
|
|
<span class="lore-stat">En attente : <span style="color:var(--amber)">{{ lore_stats.pending }}</span></span>
|
|
<span class="lore-stat">Acceptées : <span style="color:var(--green)">{{ lore_stats.accepted }}</span></span>
|
|
<span class="lore-stat">Rejetées : <span style="color:var(--red)">{{ lore_stats.rejected }}</span></span>
|
|
<span class="lore-stat">Modifiées : <span style="color:var(--blue)">{{ lore_stats.modified }}</span></span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="lore-filters">
|
|
<label>Faction :</label>
|
|
<select onchange="location.href='/lore?faction='+this.value+'&status='+document.getElementById('filt-status').value">
|
|
<option value="" {{ 'selected' if not filt_faction else '' }}>— toutes —</option>
|
|
{% for f in all_factions %}
|
|
<option value="{{ f }}" {{ 'selected' if filt_faction==f else '' }}>{{ f }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
<label>Statut :</label>
|
|
<select id="filt-status" onchange="location.href='/lore?faction='+document.querySelector('[onchange]').value+'&status='+this.value">
|
|
<option value="pending" {{ 'selected' if filt_status=='pending' else '' }}>pending</option>
|
|
<option value="accepted" {{ 'selected' if filt_status=='accepted' else '' }}>accepted</option>
|
|
<option value="rejected" {{ 'selected' if filt_status=='rejected' else '' }}>rejected</option>
|
|
<option value="modified" {{ 'selected' if filt_status=='modified' else '' }}>modified</option>
|
|
<option value="" {{ 'selected' if not filt_status else '' }}>— tous —</option>
|
|
</select>
|
|
<span style="font-size:12px;color:var(--dim)">{{ proposals|length }} proposition(s) affichée(s)</span>
|
|
</div>
|
|
|
|
{% if not proposals %}
|
|
<div class="panel" style="color:var(--dim);text-align:center;padding:24px">
|
|
Aucune proposition lore.<br>
|
|
<small style="color:var(--border)">Lancer : <code>python3 lore_enricher.py --faction grand_krewe</code> sur Ampère</small>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% for p in proposals %}
|
|
<div class="lore-card">
|
|
<div class="lore-meta">
|
|
<span>#{{ p.id }}</span>
|
|
<span class="badge-{{ p.status }}">{{ p.status | upper }}</span>
|
|
<span class="change-type-badge">{{ p.change_type }}</span>
|
|
<span style="color:var(--amber)">{{ p.faction_slug }}</span>
|
|
{% if p.field_path %}<span>{{ p.field_path }}</span>{% endif %}
|
|
<span style="color:var(--dim)">{{ p.created_at.strftime('%d/%m %H:%M') if p.created_at else '' }}</span>
|
|
</div>
|
|
|
|
<h3>{{ p.rationale or '(pas de justification)' }}</h3>
|
|
|
|
<div class="lore-diff">
|
|
<div>
|
|
<div class="diff-label">Original</div>
|
|
<div class="diff-old">{{ p.original_text or '(ajout — pas de texte original)' }}</div>
|
|
</div>
|
|
<div>
|
|
<div class="diff-label">Proposé</div>
|
|
<div class="diff-new">{{ p.proposed_text }}</div>
|
|
</div>
|
|
</div>
|
|
|
|
{% if p.status == 'pending' %}
|
|
<div class="lore-actions">
|
|
<form method="POST" action="/lore/action" style="display:inline">
|
|
<input type="hidden" name="id" value="{{ p.id }}">
|
|
<input type="hidden" name="action" value="accept">
|
|
<input type="hidden" name="filt_faction" value="{{ filt_faction }}">
|
|
<input type="hidden" name="filt_status" value="{{ filt_status }}">
|
|
<button type="submit" class="btn btn-green" onclick="return confirm('Accepter cette proposition ?')">✓ Accepter</button>
|
|
</form>
|
|
<form method="POST" action="/lore/action" style="display:inline">
|
|
<input type="hidden" name="id" value="{{ p.id }}">
|
|
<input type="hidden" name="action" value="reject">
|
|
<input type="hidden" name="filt_faction" value="{{ filt_faction }}">
|
|
<input type="hidden" name="filt_status" value="{{ filt_status }}">
|
|
<button type="submit" class="btn btn-red">✗ Rejeter</button>
|
|
</form>
|
|
<form method="POST" action="/lore/action" style="width:100%;margin-top:6px">
|
|
<input type="hidden" name="id" value="{{ p.id }}">
|
|
<input type="hidden" name="action" value="modify">
|
|
<input type="hidden" name="filt_faction" value="{{ filt_faction }}">
|
|
<input type="hidden" name="filt_status" value="{{ filt_status }}">
|
|
<textarea name="modified_text" placeholder="Texte modifié…">{{ p.proposed_text }}</textarea>
|
|
<button type="submit" class="btn" style="margin-top:6px">✎ Modifier & Accepter</button>
|
|
</form>
|
|
</div>
|
|
{% elif p.status == 'modified' and p.modified_text %}
|
|
<div style="margin-top:8px">
|
|
<div class="diff-label">Texte modifié accepté</div>
|
|
<div class="diff-new" style="border-color:rgba(96,165,250,.3)">{{ p.modified_text }}</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if p.status in ('modified', 'accepted') %}
|
|
<button type="button" class="btn" onclick="toggleDiscuss({{ p.id }})" style="font-size:12px;margin-top:8px">💬 Discuter / Modifier</button>
|
|
<div id="discuss-{{ p.id }}" style="display:none;margin-top:12px;border-top:1px solid var(--border);padding-top:10px">
|
|
{% for msg in proposal_threads.get(p.id, []) %}
|
|
<div style="margin-bottom:8px;padding:6px 10px;border-radius:4px;{% if msg.role=='user' %}background:#0d1117;border-left:2px solid var(--amber){% else %}background:#050d05;border-left:2px solid var(--green){% endif %}">
|
|
<span style="font-size:10px;color:var(--dim);display:block;margin-bottom:3px">{{ '👤 Toi' if msg.role=='user' else '🤖 LLM' }} — {{ msg.created_at.strftime('%d/%m %H:%M') if msg.created_at else '' }}</span>
|
|
<span style="font-size:12px;white-space:pre-wrap">{{ msg.content }}</span>
|
|
</div>
|
|
{% endfor %}
|
|
<div style="display:flex;flex-direction:column;gap:6px;margin-top:8px">
|
|
<textarea id="discuss-msg-{{ p.id }}" placeholder="Ex: Garde l'idée mais réduis l'impact de 50%…" style="background:var(--bg);border:1px solid var(--border);color:var(--text);font-size:12px;padding:6px;border-radius:3px;height:54px;resize:vertical"></textarea>
|
|
<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap">
|
|
<span style="font-size:11px;color:var(--dim)">temp: <input type="range" id="discuss-temp-{{ p.id }}" min="0" max="1" step="0.05" value="0.75" style="width:70px;vertical-align:middle" oninput="document.getElementById('discuss-temp-val-{{ p.id }}').textContent=this.value"> <span id="discuss-temp-val-{{ p.id }}">0.75</span></span>
|
|
<span style="font-size:11px;color:var(--dim)">tokens: <input type="number" id="discuss-np-{{ p.id }}" value="512" min="128" max="2048" step="128" style="width:58px;background:var(--bg);border:1px solid var(--border);color:var(--text);font-size:11px;padding:2px 4px"></span>
|
|
<button class="btn btn-green" onclick="submitDiscuss({{ p.id }})" style="font-size:12px">▶ Envoyer</button>
|
|
<span id="discuss-spinner-{{ p.id }}" style="display:none;color:var(--amber);font-size:12px">⏳ Génération…</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
{% endfor %}
|
|
|
|
<script>
|
|
function toggleDiscuss(id) {
|
|
var el = document.getElementById('discuss-' + id);
|
|
el.style.display = el.style.display === 'none' ? 'block' : 'none';
|
|
}
|
|
function submitDiscuss(id) {
|
|
var msg = document.getElementById('discuss-msg-' + id).value.trim();
|
|
var temp = parseFloat(document.getElementById('discuss-temp-' + id).value);
|
|
var np = parseInt(document.getElementById('discuss-np-' + id).value);
|
|
if (!msg) { alert('Message vide'); return; }
|
|
var spin = document.getElementById('discuss-spinner-' + id);
|
|
spin.style.display = 'inline';
|
|
fetch('/lore/' + id + '/discuss', {
|
|
method: 'POST', headers: {'Content-Type':'application/json'},
|
|
body: JSON.stringify({message: msg, temperature: temp, num_predict: np})
|
|
}).then(function(r){ return r.json(); }).then(function(d){
|
|
spin.style.display = 'none';
|
|
if (d.error) { alert(d.error); return; }
|
|
location.reload();
|
|
}).catch(function(e){ spin.style.display = 'none'; alert('Erreur réseau : ' + e); });
|
|
}
|
|
</script>
|
|
{% endblock %}
|