Files
ewoooc/templates/admin/ppt_audit_history.html
OoO b21b40cae2
All checks were successful
CD Pipeline / deploy (push) Successful in 1m2s
fix(observability): soften frontend error copy
2026-05-05 21:58:49 +08:00

35 lines
11 KiB
HTML
Raw 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 "ewoooc_base.html" %}
{% block title %}PPT 視覺 QA 產線{% endblock %}
{% block ewooo_content %}
<style>
.ppt-hero,.ppt-panel,.ppt-table-shell{border:1px solid var(--obs-line);border-radius:26px;background:var(--obs-card);box-shadow:0 16px 38px rgba(70,46,28,.08)}
.ppt-hero{padding:clamp(1.2rem,2.4vw,2rem);background:radial-gradient(circle at 12% 14%,rgba(201,100,66,.18),transparent 24rem),radial-gradient(circle at 88% 8%,rgba(79,111,143,.14),transparent 22rem),linear-gradient(135deg,rgba(255,248,239,.98),rgba(255,255,255,.74))}.ppt-kicker{color:var(--obs-accent);font-size:.76rem;letter-spacing:.13em;text-transform:uppercase;font-weight:850}.ppt-title{margin:.45rem 0 .25rem;font-family:'Noto Sans TC','Inter',sans-serif;font-size:var(--obs-title-size);letter-spacing:-.055em;line-height:.98}.ppt-subtitle{color:var(--obs-muted);max-width:860px;line-height:1.7}.ppt-command{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:.75rem;margin-top:1rem}.ppt-signal{padding:.95rem;border:1px solid var(--obs-line);border-radius:20px;background:rgba(255,255,255,.62)}.ppt-label{color:var(--obs-muted);font-size:.72rem;letter-spacing:.1em;text-transform:uppercase}.ppt-value{display:block;margin-top:.28rem;font-size:var(--obs-value-size);font-weight:880;letter-spacing:-.045em}.ppt-grid{display:grid;grid-template-columns:minmax(0,1.2fr) minmax(330px,.8fr);gap:1rem;margin-top:1rem}.ppt-stack{display:grid;gap:1rem}.ppt-panel-head,.ppt-table-title{display:flex;justify-content:space-between;align-items:flex-start;gap:1rem;padding:1.05rem 1.1rem .25rem}.ppt-panel-title,.ppt-table-title h3{margin:.15rem 0 0;font-size:1.1rem;font-weight:850;letter-spacing:-.025em}.ppt-panel-body{padding:1rem 1.1rem 1.1rem}.ppt-table-shell{overflow:hidden;margin-top:1rem}.ppt-mini-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:.7rem}.ppt-mini{padding:.85rem;border:1px solid var(--obs-line);border-radius:18px;background:rgba(255,255,255,.58)}.ppt-mini strong{display:block;margin-top:.24rem;font-size:1.35rem;letter-spacing:-.04em}.fix-card{padding:.85rem;border:1px solid var(--obs-line);border-radius:18px;background:rgba(255,255,255,.58);margin-bottom:.7rem}.status-good{color:var(--obs-green)}.status-warn{color:var(--obs-amber)}.status-bad{color:var(--obs-red)}.status-blue{color:var(--obs-blue)}@media(max-width:1100px){.ppt-command{grid-template-columns:repeat(2,minmax(0,1fr))}.ppt-grid{grid-template-columns:1fr}}@media(max-width:720px){.ppt-command,.ppt-mini-grid{grid-template-columns:1fr}}
</style>
<div class="container-fluid mt-3">
<section class="ppt-hero"><div class="ppt-kicker"><i class="fas fa-search me-1"></i> PPT Visual QA Pipeline · minicpm-v / AiderHeal / RAG Fixes</div><h1 class="ppt-title">PPT 視覺 QA 產線</h1><p class="ppt-subtitle">這頁追蹤每份自動簡報是否通過視覺審核檔案產出、minicpm-v audit、Telegram 推送、RAG 修法建議與 AiderHeal 自動修 generator。</p><div class="ppt-command"><div class="ppt-signal"><div class="ppt-label">Vision</div><span class="ppt-value {% if vision_enabled %}status-good{% else %}status-warn{% endif %}">{{ 'ON' if vision_enabled else 'OFF' }}</span><small class="text-muted">PPT_VISION_ENABLED</small></div><div class="ppt-signal"><div class="ppt-label">30d Total</div><span class="ppt-value">{{ audit_30d_stats.total if audit_30d_stats else 0 }}</span><small class="text-muted">audit records</small></div><div class="ppt-signal"><div class="ppt-label">Pass Rate</div><span class="ppt-value {% if audit_30d_stats and audit_30d_stats.pass_rate >= 80 %}status-good{% elif audit_30d_stats and audit_30d_stats.pass_rate >= 60 %}status-warn{% else %}status-bad{% endif %}">{{ "%.0f"|format(audit_30d_stats.pass_rate) if audit_30d_stats else '—' }}{% if audit_30d_stats %}%{% endif %}</span><small class="text-muted">過去 30 日</small></div><div class="ppt-signal"><div class="ppt-label">Issues</div><span class="ppt-value {% if audit_30d_stats and audit_30d_stats.total_issues > 0 %}status-warn{% else %}status-good{% endif %}">{{ audit_30d_stats.total_issues if audit_30d_stats else 0 }}</span><small class="text-muted">視覺問題數</small></div></div></section>
{% if error %}<div class="alert alert-warning mt-3"><strong><i class="fas fa-triangle-exclamation me-1"></i></strong>{{ error }}</div>{% endif %}
<section class="ppt-grid">
<div class="ppt-stack">
<article class="ppt-table-shell"><div class="ppt-table-title"><div><div class="ppt-label">Audit History</div><h3>視覺審核歷史 100 筆</h3></div></div><div class="table-responsive"><table class="table table-sm mb-0"><thead class="table-light"><tr><th>時間</th><th>檔名</th><th>結果</th><th class="text-end">問題</th><th class="text-end">信心</th><th class="text-end">耗時</th><th>錯誤</th><th>動作</th></tr></thead><tbody>{% for r in audit_records %}<tr><td><small>{{ r.audited_at }}</small></td><td><code>{{ r.pptx_filename }}</code></td><td>{% if r.audit_status == 'passed' %}<span class="badge bg-success">通過</span>{% elif r.audit_status == 'failed' %}<span class="badge bg-warning">有問題</span>{% elif r.audit_status == 'error' %}<span class="badge bg-danger">錯誤</span>{% elif r.audit_status == 'skipped' %}<span class="badge bg-secondary">跳過</span>{% else %}<span class="badge bg-light text-dark">{{ r.audit_status }}</span>{% endif %}</td><td class="text-end">{{ r.issues_count }}</td><td class="text-end">{{ "%.2f"|format(r.confidence) }}</td><td class="text-end">{{ r.duration_ms }}</td><td><small class="text-muted">{{ (r.error_msg or '')[:80] }}</small></td><td>{% if r.audit_status in ('failed','error') %}<button class="btn btn-sm btn-outline-warning" onclick="triggerAiderHeal({{ r.pptx_filename|tojson }}, {{ (r.error_msg or '')|tojson }})"><i class="fas fa-wrench me-1"></i>AiderHeal</button>{% endif %}</td></tr>{% else %}<tr><td colspan="8" class="text-center text-muted">尚無審核紀錄</td></tr>{% endfor %}</tbody></table></div></article>
<article class="ppt-table-shell"><div class="ppt-table-title"><div><div class="ppt-label">Generated Files</div><h3>過去 7 日 PPT 檔案</h3></div></div><div class="table-responsive"><table class="table table-sm mb-0"><thead class="table-light"><tr><th>檔名</th><th class="text-end">KB</th><th>修改時間</th><th>狀態</th></tr></thead><tbody>{% for f in files %}<tr><td><code>{{ f.name }}</code></td><td class="text-end">{{ f.size_kb }}</td><td><small>{{ f.mtime }}</small></td><td><small class="text-muted">22:00 cron 自動審核</small></td></tr>{% else %}<tr><td colspan="4" class="text-center text-muted">過去 7 日無 PPT 生成</td></tr>{% endfor %}</tbody></table></div></article>
</div>
<aside class="ppt-stack">
{% if audit_30d_stats and audit_30d_stats.total > 0 %}<article class="ppt-panel"><div class="ppt-panel-head"><div><div class="ppt-label">30d Audit Mix</div><h2 class="ppt-panel-title">審核結果分布</h2></div></div><div class="ppt-panel-body"><div class="obs-chart-frame"><canvas id="pptAuditPieChart"></canvas></div><div class="ppt-mini-grid mt-3"><div class="ppt-mini"><span class="ppt-label">Passed</span><strong class="status-good">{{ audit_30d_stats.passed }}</strong></div><div class="ppt-mini"><span class="ppt-label">Failed</span><strong class="{% if audit_30d_stats.failed > 0 %}status-warn{% endif %}">{{ audit_30d_stats.failed }}</strong></div><div class="ppt-mini"><span class="ppt-label">Error</span><strong class="{% if audit_30d_stats.error > 0 %}status-bad{% endif %}">{{ audit_30d_stats.error }}</strong></div><div class="ppt-mini"><span class="ppt-label">Confidence</span><strong>{{ "%.2f"|format(audit_30d_stats.avg_confidence) }}</strong></div></div></div></article>{% endif %}
{% if top_failure_files %}<article class="ppt-panel"><div class="ppt-panel-head"><div><div class="ppt-label">Failure Hotspots</div><h2 class="ppt-panel-title">Top 失敗檔案</h2></div></div><div class="ppt-panel-body">{% for f in top_failure_files %}<div class="fix-card"><code>{{ f.filename }}</code><div class="d-flex justify-content-between mt-1"><small class="text-muted">{{ f.last_audit }}</small><span class="badge bg-warning">{{ f.attempts }} 次</span></div><small class="text-muted">issues {{ f.total_issues }}</small></div>{% endfor %}</div></article>{% endif %}
</aside>
</section>
{% if rag_fixes %}<section class="ppt-panel mt-3"><div class="ppt-panel-head"><div><div class="ppt-label">RAG Fix Suggestions</div><h2 class="ppt-panel-title">RAG 自動修法建議</h2></div></div><div class="ppt-panel-body">{% for fix in rag_fixes %}<div class="fix-card"><strong><code>{{ fix.pptx_filename }}</code></strong><small class="text-muted ms-2">{{ fix.audited_at }}</small><div class="small status-bad mt-1">{{ fix.error_msg }}</div><ul class="list-unstyled mt-2 mb-0 small">{% for h in fix.hits %}<li class="mb-1"><span class="badge bg-info me-1">{{ h.insight_type }}</span><span class="badge bg-light text-dark me-1">相似度 {{ "%.2f"|format(h.similarity) }}</span>{{ h.content }}{% if h.content|length >= 200 %}…{% endif %}</li>{% endfor %}</ul></div>{% endfor %}</div></section>{% endif %}
{% if (not audit_30d_stats or audit_30d_stats.total == 0) and not vision_enabled %}<div class="alert alert-info mt-3"><strong>為什麼這頁空?</strong><ul class="mb-0 small mt-2"><li>PPT_VISION_ENABLED=false</li><li>188 主機需安裝 LibreOffice</li><li>需 Ollama 拉取 minicpm-v 模型</li><li>啟用後每日 22:00 cron 寫入 ppt_audit_results</li></ul></div>{% endif %}
<p class="text-muted mt-3"><small><i class="fas fa-robot me-1"></i>Operation Ollama-First v5.0 — PPT 視覺 QA 產線</small></p>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script><script>(function(){const stats={{ audit_30d_stats | default({}) | tojson }};const el=document.getElementById('pptAuditPieChart');if(!el||!stats.total)return;const data=[{label:'通過',value:stats.passed||0,color:'#4f8a5b'},{label:'失敗',value:stats.failed||0,color:'#b8792f'},{label:'錯誤',value:stats.error||0,color:'#b94b45'},{label:'跳過',value:stats.skipped||0,color:'#8b8077'}].filter(d=>d.value>0);if(!data.length)return;new Chart(el,{type:'doughnut',data:{labels:data.map(d=>d.label),datasets:[{data:data.map(d=>d.value),backgroundColor:data.map(d=>d.color),borderWidth:1,borderColor:'#fff'}]},options:{responsive:true,maintainAspectRatio:false,plugins:{legend:{position:'bottom',labels:{font:{size:12}}}}}});})();
async function triggerAiderHeal(pptxFilename,errorMsg){if(!confirm(`觸發 AiderHeal 自動修復?\n\n檔案:${pptxFilename}\n錯誤:${(errorMsg||'').substring(0,200)}`))return;try{const r=await fetch('/observability/ppt_audit/trigger_aider_heal',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({pptx_filename:pptxFilename,error_msg:errorMsg||''})});const d=await r.json();if(d.ok){alert(`✅ AiderHeal 已派出\n動作:${d.action||'—'}\n訊息:${d.message||''}`);}else{alert('❌ '+(d.error||d.message||'觸發失敗'));}}catch(e){console.warn('ppt_audit_trigger_aider_heal_failed',e);alert('操作暫時無法完成,請稍後再試或查看系統日誌。');}}
</script>
{% endblock %}