Files
ewoooc/templates/admin/ppt_audit_history.html
OoO 849e189b60
All checks were successful
CD Pipeline / deploy (push) Successful in 2m37s
feat(p45): UI/UX 升級 ewoooc_base.html + sidebar AI 觀測 7 項 + 新增總覽頁
統帥質疑:「那六頁的視覺方格 UI/UX 搞好了嗎?還有新增頁面嗎?」
回答:沒有,從 Phase 38 開始一直推遲。本 commit 補做。

I-1: 6 頁 base.html → ewoooc_base.html
- host_health / ai_calls_dashboard / budget / promotion_review /
  quality_trend / ppt_audit_history 全改
- {% extends "base.html" %} → {% extends "ewoooc_base.html" %}
- {% block content %} → {% block ewooo_content %}
- 自動繼承:sidebar 240px / topbar 64px / fonts (Inter+JetBrains+Noto Sans TC)
  / ewoooc-tokens.css / ewoooc-shell.css / search box / 米色背景

I-2: _ewoooc_shell.html 加「AI 觀測」nav group
- 7 個項目:觀測台總覽 / 主機健康 / AI 呼叫 / 預算控管 /
  RAG 晉升審核 / 反饋趨勢 / PPT 視覺審核
- 對應 active_page='obs_*',正確高亮
- 編號 07-13(系統管理改 14)

I-3: 新增頁面 /observability/ + /observability/overview
- routes/admin_observability_routes.py::observability_overview
- 單頁聚合 8 表跨 JOIN 的 KPI:
  • 三主機 24h 在線率(host_health_probes,per host card)
  • AI 呼叫 24h(ai_calls:total/tokens/cost/error rate/RAG hit/cache hit)
  • 當月成本累計
  • 預算告警(ratio ≥ alert_pct 自動列表)
  • AIOps 7d(incidents + heal_logs:自癒成功率)
  • MCP 24h(mcp_calls:tool 呼叫 + cache 率 + cost)
  • RAG 學習 30d(learning_episodes:待審 + 晉升率)
  • PPT 視覺審核 7d(ppt_audit_results:通過率)
  • 6 大子頁入口卡(含一行說明)
- 對應 Phase 44 daily Telegram summary 的 web 版本
- 全部失敗安全(個別 query 失敗只跳過該卡,不擋整頁)

升級對應:
- UI 框架:base.html → ewoooc_base.html (sidebar + topbar + token css 已生效)
- 設計憲法:8 卡片 + 8 表跨 JOIN 全景 + 一頁式總覽
- 入口:sidebar 7 項 + 觀測台首頁
- 資料表覆蓋:4 表(Phase 38)→ 8 表(Phase 45)

注意:完整 design token 重塑(Bootstrap class → --momo-* token / 焦糖橘)
留待後續 phase;本 commit 重點是「框架升級 + 新總覽頁」。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 19:34:18 +08:00

159 lines
6.7 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 視覺審核歷史{% endblock %}
{% block ewooo_content %}
<div class="container-fluid mt-3">
<h2 class="mb-3"><i class="fas fa-search me-2"></i>PPT 視覺審核歷史
<small class="text-muted">reports/ 目錄過去 7 日 .pptx</small>
</h2>
{% if error %}<div class="alert alert-warning"><strong><i class="fas fa-exclamation-triangle me-1"></i></strong> {{ error }}</div>{% endif %}
<div class="alert {% if vision_enabled %}alert-success{% else %}alert-secondary{% endif %} small">
<strong>PPT_VISION_ENABLED</strong>
{% if vision_enabled %}
<i class="fas fa-check me-1"></i>已啟用 — 每日 22:00 cron 自動跑 minicpm-v 視覺檢查,發現問題推送 Telegram
{% else %}
<i class="fas fa-pause me-1"></i>未啟用 — 設 PPT_VISION_ENABLED=true 並在 188 主機安裝 LibreOffice 即生效
{% endif %}
</div>
<div class="card mb-3">
<div class="card-header"><strong><i class="fas fa-folder-open me-2"></i>過去 7 日 PPT 檔案</strong></div>
<div class="card-body p-0">
<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">由 audit cron 22:00 自動執行</small>
</td>
</tr>
{% else %}
<tr><td colspan="4" class="text-center text-muted">過去 7 日無 PPT 生成</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="card mb-3">
<div class="card-header"><strong><i class="fas fa-history me-2"></i>視覺審核歷史紀錄(最近 100 筆)</strong></div>
<div class="card-body p-0">
<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">耗時 ms</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"><i class="fas fa-check me-1"></i>通過</span>
{% elif r.audit_status == 'failed' %}
<span class="badge bg-warning"><i class="fas fa-exclamation me-1"></i>有問題</span>
{% elif r.audit_status == 'skipped' %}
<span class="badge bg-secondary"><i class="fas fa-pause me-1"></i>跳過</span>
{% elif r.audit_status == 'error' %}
<span class="badge bg-danger"><i class="fas fa-times me-1"></i>錯誤</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">尚無審核紀錄migration 030 跑過後即會累積)</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% if rag_fixes %}
<div class="card mb-3" style="border-left: 4px solid #6f42c1;">
<div class="card-header bg-light">
<strong><i class="fas fa-magic me-2"></i>RAG 自動修法建議</strong>
<small class="text-muted">— 對最近 3 筆 failed audit 從 ai_insights 召回相似案例修法</small>
</div>
<div class="card-body p-2">
{% for fix in rag_fixes %}
<div class="mb-2 p-2" style="background: #fafafa; border-radius: 6px;">
<strong><code>{{ fix.pptx_filename }}</code></strong>
<small class="text-muted ms-2">{{ fix.audited_at }}</small>
<div class="small text-danger 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 text-dark 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>
</div>
{% endif %}
<p class="text-muted mt-2 small">
審核結果:<strong>有問題才推 Telegram</strong>(避免靜默無問題洗版)。
手動觸發單檔審核需 SSH 到 188 主機執行:
<code>python3 -c "from services.ppt_vision_service import ppt_vision_service; print(ppt_vision_service.check_ppt_file('reports/xxx.pptx'))"</code>
</p>
<p class="text-muted mt-3"><small>
<i class="fas fa-robot me-1"></i>Operation Ollama-First v5.0 / Phase 40 — PPT 視覺審核歷史(含 AiderHeal L2
</small></p>
</div>
<script>
async function triggerAiderHeal(pptxFilename, errorMsg) {
if (!confirm(`觸發 AiderHeal 自動修復?\n\n檔案:${pptxFilename}\n錯誤:${(errorMsg || '').substring(0, 200)}\n\nAiderHeal 會嘗試修 services/ppt_generator.py 並 git push 到 main 觸發 CD。`)) 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) {
alert('Error: ' + e);
}
}
</script>
{% endblock %}