Files
ewoooc/templates/admin/ppt_audit_history.html
OoO 65f236da2d
All checks were successful
CD Pipeline / deploy (push) Successful in 2m36s
feat(p40): 觀測台收官 — 4 頁升 L2 + RAG 根因 + 蒸餾池監控
接續 Phase 39 (commit 79cf08c),本 commit 完成 Phase D 最後 4 項:

D-6: quality_trend 蒸餾池 + RAG 根因
- 新「蒸餾池狀態」card:learning_episodes 各 promotion_status 分布
  (pending / awaiting_review / approved / rejected_quality /
   rejected_hallucination / rejected_duplicate / rejected_human / expired)
- 對最差 3 名 caller (avg_score < 3 且反饋 ≥ 3) 自動 RAG 根因建議
- RAG 從 ai_insights 召回相似低品質案例

D-7: ai_calls 一鍵 Code Review (L2)
- 新 POST /observability/ai_calls/trigger_code_review
  讀 git rev-parse HEAD + diff-tree 取最新變更檔案
  在 daemon thread 跑 CodeReviewPipeline.run() (5 step Hermes→
  OpenClaw→EA→NemoTron)
- 頁面新增「觸發 Code Review Pipeline」按鈕

D-8: ppt_audit 失敗 row 一鍵 AiderHeal (L2)
- 新 POST /observability/ppt_audit/trigger_aider_heal
  接收 pptx_filename + error_msg,呼叫 services/aider_heal_executor::
  execute_code_fix 自動修 services/ppt_generator.py
  AiderHeal 修完會 git push 觸發 CD
- audit_records 表中 status='failed'/'error' 的 row 自動顯示按鈕

D-9: host_health 一鍵 AutoHeal (L2)
- 新 POST /observability/host_health/trigger_autoheal
  接收 host_label,白名單對應 OLLAMA_HOST_PRIMARY/SECONDARY/FALLBACK
  防 SSRF。已標記 unhealthy 的 host 才允許觸發
  呼叫 auto_heal_service.handle_exception(error_type='ollama_unhealthy')
  跑 ADR-013 playbook(DOCKER_RESTART / SSH_CMD / ALERT_ONLY)
- 三主機 row 中 unhealthy / down 的 host 自動顯示按鈕

升級對應:
- AI 自動化:L2 從 1 個 → 4 個(budget force_throttle / Code Review /
  AiderHeal / AutoHeal)
- DB 利用率 ~60%:新增 learning_episodes 分布查詢
- RAG 整合 4/6(promotion_review + budget + quality_trend + 待 ppt_audit)

Phase 38+39+40 累計:6 commits 完成觀測台從 raw dashboard
升級到 AI 自動化專業舞台。

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

132 lines
5.6 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 "base.html" %}
{% block title %}PPT 視覺審核歷史{% endblock %}
{% block 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>
<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 %}