106 lines
10 KiB
HTML
106 lines
10 KiB
HTML
{% extends "ewoooc_base.html" %}
|
||
|
||
{% block title %}RAG 知識晉升閘{% endblock %}
|
||
|
||
{% block ewooo_content %}
|
||
<style>
|
||
.gate-hero, .gate-panel, .gate-table-shell, .episode-card { border:1px solid var(--obs-line); border-radius:26px; background:var(--obs-card); box-shadow:0 16px 38px rgba(70,46,28,.08); }
|
||
.gate-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)); }
|
||
.gate-kicker { color:var(--obs-accent); font-size:.76rem; letter-spacing:.13em; text-transform:uppercase; font-weight:850; }
|
||
.gate-title { margin:.45rem 0 .25rem; font-family: var(--momo-font-display, "Inter", "Noto Sans TC", system-ui, sans-serif); font-size:var(--obs-title-size); letter-spacing: 0; line-height:.98; }
|
||
.gate-subtitle { color:var(--obs-muted); max-width:880px; line-height:1.7; }
|
||
.gate-command { display:grid; grid-template-columns:repeat(4,minmax(0,1fr)); gap:.75rem; margin-top:1rem; }
|
||
.gate-signal { padding:.95rem; border:1px solid var(--obs-line); border-radius:20px; background:rgba(255,255,255,.62); }
|
||
.gate-label { color:var(--obs-muted); font-size:.72rem; letter-spacing:.1em; text-transform:uppercase; }
|
||
.gate-value { display:block; margin-top:.28rem; font-size:var(--obs-value-size); font-weight:880; letter-spacing: 0; }
|
||
.gate-grid { display:grid; grid-template-columns:minmax(0,1.16fr) minmax(330px,.84fr); gap:1rem; margin-top:1rem; }
|
||
.gate-stack { display:grid; gap:1rem; }
|
||
.gate-panel-head, .gate-table-title { display:flex; justify-content:space-between; align-items:flex-start; gap:1rem; padding:1.05rem 1.1rem .25rem; }
|
||
.gate-panel-title, .gate-table-title h3 { margin:.15rem 0 0; font-size:1.1rem; font-weight:850; letter-spacing: 0; }
|
||
.gate-panel-body { padding:1rem 1.1rem 1.1rem; }
|
||
.gate-mini-grid { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:.7rem; }
|
||
.gate-mini { padding:.85rem; border:1px solid var(--obs-line); border-radius:18px; background:rgba(255,255,255,.58); }
|
||
.gate-mini strong { display:block; margin-top:.24rem; font-size:1.35rem; letter-spacing: 0; }
|
||
.episode-card { overflow:hidden; margin-bottom:1rem; }
|
||
.episode-head { display:flex; justify-content:space-between; gap:1rem; padding:1rem 1.1rem .65rem; border-bottom:1px solid var(--obs-line); background:linear-gradient(90deg,rgba(255,248,239,.92),rgba(255,255,255,.72)); }
|
||
.episode-body { padding:1rem 1.1rem; }
|
||
.episode-text { white-space:pre-wrap; max-height:220px; overflow:auto; padding:1rem; border:1px solid var(--obs-line); border-radius:18px; background:rgba(255,255,255,.6); font-size:.92rem; line-height:1.65; }
|
||
.similar-box { margin-top:1rem; padding:.9rem; border:1px solid rgba(79,111,143,.18); border-left:5px solid var(--obs-blue); border-radius:18px; background:rgba(79,111,143,.08); }
|
||
.gate-table-shell { overflow:hidden; margin-top:1rem; }
|
||
.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){ .gate-command{grid-template-columns:repeat(2,minmax(0,1fr));}.gate-grid{grid-template-columns:1fr;} }
|
||
@media (max-width:720px){ .gate-command,.gate-mini-grid{grid-template-columns:1fr;} .episode-head{display:block;} }
|
||
</style>
|
||
|
||
{% import "admin/_observability_labels.html" as obs_label %}
|
||
{% set total_dist = (episode_distribution_30d.values() | sum) if episode_distribution_30d else 0 %}
|
||
{% set approved_30d = episode_distribution_30d.get('approved', 0) if episode_distribution_30d else 0 %}
|
||
{% set rejected_30d = namespace(value=0) %}
|
||
{% if episode_distribution_30d %}{% for status, cnt in episode_distribution_30d.items() %}{% if status.startswith('rejected') %}{% set rejected_30d.value = rejected_30d.value + cnt %}{% endif %}{% endfor %}{% endif %}
|
||
{% set approval_rate = (approved_30d / total_dist * 100) if total_dist > 0 else 0 %}
|
||
|
||
<div class="container-fluid mt-3">
|
||
<section class="gate-hero">
|
||
<div class="gate-kicker"><i class="fas fa-brain me-1"></i> RAG 知識晉升閘 · 人工審核 / 去重 / 防污染</div>
|
||
<h1 class="gate-title">RAG 知識晉升閘</h1>
|
||
<p class="gate-subtitle">這頁是 RAG 不被污染的最後關卡。高權重 學習片段 不能直接進知識庫,必須先看品質、相似知識、人工拒絕與晉升分布,再決定是否寫入 ai_insights。</p>
|
||
<div class="gate-command">
|
||
<div class="gate-signal"><div class="gate-label">待審核</div><span class="gate-value {% if episodes|length > 0 %}status-warn{% else %}status-good{% endif %}">{{ episodes|length }}</span><small class="text-muted">高權重待審片段</small></div>
|
||
<div class="gate-signal"><div class="gate-label">知識庫</div><span class="gate-value">{{ kb_size or 0 }}</span><small class="text-muted">ai_insights 已晉升</small></div>
|
||
<div class="gate-signal"><div class="gate-label">30 日通過率</div><span class="gate-value status-blue">{{ "%.0f"|format(approval_rate) }}%</span><small class="text-muted">{{ approved_30d }}/{{ total_dist }} 個片段</small></div>
|
||
<div class="gate-signal"><div class="gate-label">30 日拒絕</div><span class="gate-value {% if rejected_30d.value > 0 %}status-bad{% else %}status-good{% endif %}">{{ rejected_30d.value }}</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="gate-grid">
|
||
<div class="gate-stack">
|
||
<article class="gate-panel">
|
||
<div class="gate-panel-head"><div><div class="gate-label">審核佇列</div><h2 class="gate-panel-title">待審核片段</h2></div><span class="badge {% if episodes %}bg-warning{% else %}bg-success{% endif %}">{{ episodes|length }} 筆</span></div>
|
||
<div class="gate-panel-body">
|
||
<p class="text-muted small mb-0"><i class="fas fa-shield-halved me-1"></i>晉升守門第 4 階段:權重 ≥ 0.8 必經統帥審核;24 小時無回應自動降權,不直接污染知識庫。</p>
|
||
</div>
|
||
</article>
|
||
|
||
{% if episodes %}
|
||
{% for ep in episodes %}
|
||
<article class="episode-card" data-episode-id="{{ ep.id }}">
|
||
<div class="episode-head">
|
||
<div><strong>學習片段 #{{ ep.id }}</strong> <span class="badge bg-secondary ms-1">{{ obs_label.insight(ep.episode_type) }}</span>{% if ep.source_table %}<span class="badge bg-light text-dark ms-1">{{ obs_label.source(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="episode-body">
|
||
<div class="episode-text">{{ ep.distilled_text }}</div>
|
||
{% if ep.similar_insights %}<div class="similar-box"><small class="text-muted d-block mb-2"><i class="fas fa-search me-1"></i><strong>Top 3 相似已晉升知識</strong>(用來判斷是否重複)</small><ul class="list-unstyled mb-0 small">{% for sim in ep.similar_insights %}<li class="mb-2"><span class="badge bg-light text-dark me-1">#{{ sim.id }}</span><span class="badge bg-info me-1">{{ obs_label.insight(sim.insight_type) }}</span><span class="badge bg-secondary me-1">相似度 {{ "%.2f"|format(sim.similarity) }}</span><span>{{ sim.content }}{% if sim.content|length >= 180 %}…{% endif %}</span></li>{% endfor %}</ul></div>{% else %}<div class="similar-box"><small><i class="fas fa-seedling me-1"></i>知識庫無相似度 ≥ 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>
|
||
</article>
|
||
{% endfor %}
|
||
{% else %}
|
||
<div class="alert alert-info"><i class="fas fa-sparkles me-1"></i>目前無待審核片段。</div>
|
||
{% endif %}
|
||
</div>
|
||
|
||
<aside class="gate-stack">
|
||
{% if episode_distribution_30d %}
|
||
<article class="gate-panel"><div class="gate-panel-head"><div><div class="gate-label">蒸餾池</div><h2 class="gate-panel-title">30 日狀態分布</h2></div></div><div class="gate-panel-body"><div class="obs-chart-frame obs-chart-frame-tall"><canvas id="episodeDistChart"></canvas></div></div></article>
|
||
{% endif %}
|
||
{% if strategy_weights %}
|
||
<article class="gate-panel"><div class="gate-panel-head"><div><div class="gate-label">OpenClaw 權重</div><h2 class="gate-panel-title">策略權重 Top</h2></div></div><div class="gate-panel-body"><div class="gate-mini-grid">{% for s in strategy_weights[:6] %}<div class="gate-mini"><span class="gate-label">{{ obs_label.strategy(s.strategy_key) }}</span><strong>{{ "%.2f"|format(s.weight) }}</strong><small class="text-muted">成功 {{ s.success }} · 失敗 {{ s.fail }}</small></div>{% endfor %}</div></div></article>
|
||
{% endif %}
|
||
</aside>
|
||
</section>
|
||
|
||
{% if latest_insights %}
|
||
<section class="gate-table-shell"><div class="gate-table-title"><div><div class="gate-label">知識庫</div><h3>最近 10 筆 ai_insights</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>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">{{ 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></section>
|
||
{% endif %}
|
||
|
||
<p class="text-muted mt-3"><small><i class="fas fa-robot me-1"></i>Ollama 優先策略 v5.0 — RAG 知識晉升閘</small></p>
|
||
</div>
|
||
|
||
<template id="obs-promotion-review-data">{{ episode_distribution_30d | default({}) | tojson }}</template>
|
||
<script src="{{ url_for('static', filename='js/analysis-chart-theme.js') }}"></script>
|
||
<script src="{{ url_for('static', filename='js/observability-charts.js') }}"></script>
|
||
{% endblock %}
|