All checks were successful
CD Pipeline / deploy (push) Successful in 2m37s
統帥質疑:「那六頁的視覺方格 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>
159 lines
6.7 KiB
HTML
159 lines
6.7 KiB
HTML
{% 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 %}
|