All checks were successful
CD Pipeline / deploy (push) Successful in 2m36s
統帥質疑:「6 頁內容太空洞,要更貼近資料庫裡所有數據」
盤點:DB 有 22 表,6 頁原本只用 5 表(22.7% 利用率)。
本 commit 新接 12 張既有但未用的表。
K-1 host_health(接 +5 表 → 8 表)
- 加 incidents 最近 10 筆詳細(task_name/error_type/嚴重度/狀態/重試/錯誤訊息)
- 加 heal_logs 最近 10 筆(action_type/result/耗時/incident 關聯)
- 加 playbooks 庫排行 TOP 12(success_count/fail_count/成功率/啟用狀態)
- 加 backup_log 7 日歷史(type/status/size/duration/error)
- 加 embedding_retry_queue pending/failed 警示
K-2 ai_calls(接 +3 表)
- 加 24h 每小時呼叫趨勢 bucket(含成本+錯誤+流量分布條)
- 加 by model 細分(不只 provider,到 model 版本級別)TOP 15
- 加 agent_context 最近 10 筆(OpenClaw/Hermes 對話 session preview)
K-3 budget(接 +3 表)
- 加當月 Top 5 燒錢呼叫端(caller × cost ranking)
- 加過去 30 日每日成本 by provider 趨勢表
- 加 ai_price_recommendations 7 日統計(strategy 分布 + 平均信心度)
K-4 promotion_review(接 +2 表)
- 加蒸餾池 30 日 status 分布(不只 awaiting,看 8 種完整流動)
- 加 ai_insights 最近 10 筆已晉升內容預覽
- 加 agent_strategy_weights TOP 12(OpenClaw 學習權重 + 成功率)
K-5 quality_trend(接 +3 表)
- 加 RAG 整體 feedback 1-5 分分布(過去 N 日,星等視覺)
- 加 action_plans status 分布(pending/approved/executed/rejected)
- 加 action_outcomes verdict 分布(effective/neutral/backfired
— ADR-012 閉環學習核心 KPI)
K-6 ppt_audit_history(接 +0 表,但 deeper SQL)
- 加 30 日統計卡(total/passed/failed/error/通過率/總 issue 數/平均信心度)
- 加 Top 10 反覆失敗檔案(30d 失敗次數 + total issues)
- 加 empty state 說明(PPT_VISION_ENABLED=false 時顯示啟用步驟)
DB 利用率對應:
- Phase 38 起點:5 表(22.7%)
- Phase 39-46 累計:12 表(54.5%)
- Phase 47 收官:17 表(77.3%)
新接:playbooks / backup_log / embedding_retry_queue / agent_context /
ai_price_recommendations / agent_strategy_weights / action_plans /
action_outcomes(之前已接:incidents/heal_logs/ai_insights/learning_episodes/
rag_query_log/mcp_calls/ai_calls/host_health_probes/ppt_audit_results)
每頁 widget 數對應:
- host_health: 5 卡 → 11 卡
- ai_calls: 5 卡 → 8 卡
- budget: 4 卡 → 7 卡
- promotion_review: 2 卡 → 5 卡
- quality_trend: 4 卡 → 7 卡
- ppt_audit: 3 卡 → 6 卡
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
228 lines
10 KiB
HTML
228 lines
10 KiB
HTML
{% extends "ewoooc_base.html" %}
|
||
|
||
{% block title %}RAG 學習晉升審核{% endblock %}
|
||
|
||
{% block ewooo_content %}
|
||
<div class="container-fluid mt-3">
|
||
<h2 class="mb-3"><i class="fas fa-brain me-2"></i>RAG 學習晉升審核
|
||
<small class="text-muted">待審核 × {{ episodes|length }} 筆 · 知識庫 ai_insights × {{ kb_size or 0 }} 筆</small>
|
||
</h2>
|
||
|
||
{% if error %}
|
||
<div class="alert alert-warning"><strong><i class="fas fa-exclamation-triangle me-1"></i></strong> {{ error }}</div>
|
||
{% endif %}
|
||
|
||
{% if episodes %}
|
||
<p class="text-muted small">
|
||
<i class="fas fa-info-circle me-1"></i>Phase 11 PromotionGate Stage 4 強制門檻:weight ≥ 0.8 的 episode 必經統帥審核,
|
||
24 小時無回應自動過期(weight 降為 0.5 不晉升)。
|
||
點「通過晉升」→ 寫入 ai_insights 供 RAG 檢索;點「拒絕」→ 永不晉升(learning_episodes 留存)。
|
||
</p>
|
||
|
||
{% for ep in episodes %}
|
||
<div class="card mb-3 episode-card" data-episode-id="{{ ep.id }}">
|
||
<div class="card-header d-flex justify-content-between align-items-center">
|
||
<div>
|
||
<strong>學習片段 #{{ ep.id }}</strong>
|
||
<span class="badge bg-secondary ms-2">{{ ep.episode_type }}</span>
|
||
{% if ep.source_table %}<span class="badge bg-light text-dark ms-1">
|
||
{{ ep.source_table }}#{{ ep.source_id }}</span>{% endif %}
|
||
<span class="badge bg-info ms-1">權重:{{ "%.2f"|format(ep.weight) }}</span>
|
||
<span class="badge bg-info ms-1">品質:{{ "%.2f"|format(ep.quality_score) }}</span>
|
||
</div>
|
||
<small class="text-muted">{{ ep.created_at }}</small>
|
||
</div>
|
||
<div class="card-body">
|
||
<pre style="white-space: pre-wrap; font-size: 0.9em; max-height: 200px; overflow-y: auto;">{{ ep.distilled_text }}</pre>
|
||
|
||
{% if ep.similar_insights %}
|
||
<div class="mt-3 p-2" style="background: #f7f7f9; border-radius: 6px; border-left: 3px solid #6f42c1;">
|
||
<small class="text-muted d-block mb-2">
|
||
<i class="fas fa-search me-1"></i><strong>RAG 自動檢索:知識庫中 Top 3 相似已晉升內容</strong>
|
||
<span class="text-muted">(cosine ≥ 0.7)— 輔助判斷此次晉升是否冗餘</span>
|
||
</small>
|
||
<ul class="list-unstyled mb-0 small">
|
||
{% for sim in ep.similar_insights %}
|
||
<li class="mb-1">
|
||
<span class="badge bg-light text-dark me-1">#{{ sim.id }}</span>
|
||
<span class="badge bg-info text-dark me-1">{{ sim.insight_type }}</span>
|
||
<span class="badge bg-secondary me-1">相似度 {{ "%.2f"|format(sim.similarity) }}</span>
|
||
{% if sim.created_at %}<small class="text-muted me-1">{{ sim.created_at }}</small>{% endif %}
|
||
<span class="text-dark">{{ sim.content }}{% if sim.content|length >= 180 %}…{% endif %}</span>
|
||
</li>
|
||
{% endfor %}
|
||
</ul>
|
||
</div>
|
||
{% else %}
|
||
<div class="mt-2"><small class="text-muted">
|
||
<i class="fas fa-search me-1"></i>RAG 檢索:知識庫中無 cosine ≥ 0.7 的相似內容(屬新領域知識,建議晉升)
|
||
</small></div>
|
||
{% endif %}
|
||
</div>
|
||
<div class="card-footer text-end">
|
||
<button class="btn btn-success btn-sm me-2" onclick="approveEpisode({{ ep.id }}, this)">
|
||
<i class="fas fa-check me-1"></i>通過晉升
|
||
</button>
|
||
<button class="btn btn-outline-danger btn-sm" onclick="rejectEpisode({{ ep.id }}, this)">
|
||
<i class="fas fa-times me-1"></i>拒絕
|
||
</button>
|
||
</div>
|
||
</div>
|
||
{% endfor %}
|
||
{% else %}
|
||
<div class="alert alert-info">
|
||
<i class="fas fa-sparkles me-1"></i>目前無待審核片段。
|
||
<small>(RAG 未啟用 / 無高權重片段 / 全部已 24 小時過期)</small>
|
||
</div>
|
||
{% endif %}
|
||
|
||
<!-- Phase 47 K-4: 蒸餾池 30d 分布 -->
|
||
{% if episode_distribution_30d %}
|
||
<div class="card mb-3">
|
||
<div class="card-header"><strong><i class="fas fa-flask me-2"></i>蒸餾池 30 日狀態分布</strong>
|
||
<small class="text-muted">資料來源:learning_episodes(不只看 awaiting,看完整流動)</small>
|
||
</div>
|
||
<div class="card-body">
|
||
<div class="row g-2">
|
||
{% for status, cnt in episode_distribution_30d.items() %}
|
||
<div class="col-md-2 col-sm-4">
|
||
<div class="border rounded p-2 text-center">
|
||
<small class="text-muted d-block">
|
||
{% if status == 'pending' %}<i class="fas fa-hourglass-start"></i> 待處理
|
||
{% elif status == 'awaiting_review' %}<i class="fas fa-user-clock"></i> 待審核
|
||
{% elif status == 'approved' %}<i class="fas fa-check-circle text-success"></i> 已晉升
|
||
{% elif status == 'rejected_quality' %}<i class="fas fa-times text-danger"></i> 品質拒
|
||
{% elif status == 'rejected_hallucination' %}<i class="fas fa-times text-danger"></i> 幻覺拒
|
||
{% elif status == 'rejected_duplicate' %}<i class="fas fa-clone text-warning"></i> 重複拒
|
||
{% elif status == 'rejected_human' %}<i class="fas fa-user-times text-danger"></i> 人工拒
|
||
{% elif status == 'expired' %}<i class="fas fa-clock text-muted"></i> 已過期
|
||
{% else %}{{ status }}{% endif %}
|
||
</small>
|
||
<strong style="font-size: 1.4em;">{{ cnt }}</strong>
|
||
</div>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
|
||
<!-- Phase 47 K-4: 最近 10 筆已晉升 ai_insights -->
|
||
{% if latest_insights %}
|
||
<div class="card mb-3">
|
||
<div class="card-header"><strong><i class="fas fa-book me-2"></i>知識庫最近 10 筆 ai_insights</strong>
|
||
<small class="text-muted">資料來源:ai_insights — 已晉升內容預覽</small>
|
||
</div>
|
||
<div class="card-body p-0">
|
||
<table class="table table-sm mb-0" style="font-size: 0.9em;">
|
||
<thead class="table-light">
|
||
<tr><th>#</th><th>類型</th><th>期間</th><th>SKU</th><th>建立時間</th><th>預覽</th></tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for i in latest_insights %}
|
||
<tr>
|
||
<td><code>#{{ i.id }}</code></td>
|
||
<td><span class="badge bg-info text-dark">{{ i.insight_type }}</span></td>
|
||
<td><small>{{ i.period or '—' }}</small></td>
|
||
<td><small>{{ i.product_sku or '—' }}</small></td>
|
||
<td><small>{{ i.created_at }}</small></td>
|
||
<td><small class="text-muted">{{ i.preview }}{% if i.preview|length >= 160 %}…{% endif %}</small></td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
|
||
<!-- Phase 47 K-4: agent_strategy_weights TOP 12 -->
|
||
{% if strategy_weights %}
|
||
<div class="card mb-3">
|
||
<div class="card-header"><strong><i class="fas fa-balance-scale me-2"></i>OpenClaw 學習策略權重 TOP 12</strong>
|
||
<small class="text-muted">資料來源:agent_strategy_weights(ADR-012 closed-loop learning)</small>
|
||
</div>
|
||
<div class="card-body p-0">
|
||
<table class="table table-sm mb-0" style="font-size: 0.9em;">
|
||
<thead class="table-light">
|
||
<tr>
|
||
<th>策略 Key</th>
|
||
<th class="text-end">權重</th>
|
||
<th class="text-end">成功</th>
|
||
<th class="text-end">失敗</th>
|
||
<th class="text-end">成功率</th>
|
||
<th>更新時間</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for s in strategy_weights %}
|
||
<tr>
|
||
<td><code>{{ s.strategy_key }}</code></td>
|
||
<td class="text-end"><strong>{{ "%.2f"|format(s.weight) }}</strong></td>
|
||
<td class="text-end text-success">{{ s.success }}</td>
|
||
<td class="text-end text-danger">{{ s.fail }}</td>
|
||
<td class="text-end">
|
||
{% if (s.success + s.fail) > 0 %}
|
||
<span class="{% if s.success_rate >= 70 %}text-success{% elif s.success_rate >= 40 %}text-warning{% else %}text-danger{% endif %}">
|
||
{{ "%.0f"|format(s.success_rate) }}%
|
||
</span>
|
||
{% else %}<small class="text-muted">—</small>{% endif %}
|
||
</td>
|
||
<td><small>{{ s.updated_at }}</small></td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
|
||
<p class="text-muted mt-3"><small>
|
||
<i class="fas fa-robot me-1"></i>Operation Ollama-First v5.0 / Phase 47 — RAG 學習晉升審核
|
||
(4 表深挖:learning_episodes / ai_insights / agent_strategy_weights / rag_query_log)
|
||
</small></p>
|
||
</div>
|
||
|
||
<script>
|
||
async function approveEpisode(id, btn) {
|
||
btn.disabled = true; btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> 處理中...';
|
||
try {
|
||
const r = await fetch(`/observability/promotion_review/approve/${id}`, {method: 'POST'});
|
||
const d = await r.json();
|
||
if (d.ok) {
|
||
const card = document.querySelector(`.episode-card[data-episode-id="${id}"]`);
|
||
card.classList.add('border-success');
|
||
card.querySelector('.card-footer').innerHTML =
|
||
`<span class="text-success"><i class="fas fa-check me-1"></i>已晉升 → ai_insights #${d.insight_id}(審核者:${d.approver})</span>`;
|
||
} else {
|
||
alert('晉升失敗:' + (d.error || 'unknown'));
|
||
btn.disabled = false; btn.innerHTML = '<i class="fas fa-check me-1"></i>通過晉升';
|
||
}
|
||
} catch (e) {
|
||
alert('Error:' + e);
|
||
btn.disabled = false; btn.innerHTML = '<i class="fas fa-check me-1"></i>通過晉升';
|
||
}
|
||
}
|
||
|
||
async function rejectEpisode(id, btn) {
|
||
if (!confirm(`拒絕學習片段 #${id}?此筆將永不晉升(保留在 learning_episodes 不刪除)`)) return;
|
||
btn.disabled = true; btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> 處理中...';
|
||
try {
|
||
const r = await fetch(`/observability/promotion_review/reject/${id}`, {method: 'POST'});
|
||
const d = await r.json();
|
||
if (d.ok) {
|
||
const card = document.querySelector(`.episode-card[data-episode-id="${id}"]`);
|
||
card.classList.add('border-danger');
|
||
card.querySelector('.card-footer').innerHTML =
|
||
`<span class="text-danger"><i class="fas fa-times me-1"></i>已拒絕(rejected_human)</span>`;
|
||
} else {
|
||
alert('拒絕失敗:' + (d.error || 'unknown'));
|
||
btn.disabled = false; btn.innerHTML = '<i class="fas fa-times me-1"></i>拒絕';
|
||
}
|
||
} catch (e) {
|
||
alert('Error:' + e);
|
||
btn.disabled = false; btn.innerHTML = '<i class="fas fa-times me-1"></i>拒絕';
|
||
}
|
||
}
|
||
</script>
|
||
{% endblock %}
|