45 lines
8.5 KiB
HTML
45 lines
8.5 KiB
HTML
{% extends "ewoooc_base.html" %}
|
|
|
|
{% block title %}知識召回雷達{% endblock %}
|
|
|
|
{% block ewooo_content %}
|
|
<style>
|
|
.qa-hero,.qa-panel,.qa-table-shell{border:1px solid var(--obs-line);border-radius:26px;background:var(--obs-card);box-shadow:0 16px 38px rgba(70,46,28,.08)}
|
|
.qa-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))}
|
|
.qa-kicker{color:var(--obs-accent);font-size:.76rem;letter-spacing:.13em;text-transform:uppercase;font-weight:850}.qa-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}.qa-subtitle{color:var(--obs-muted);max-width:860px;line-height:1.7}
|
|
.qa-filter{display:flex;flex-wrap:wrap;gap:.55rem;margin-top:1rem;padding:.8rem;border:1px solid var(--obs-line);border-radius:20px;background:rgba(255,255,255,.58)}
|
|
.qa-command{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:.75rem;margin-top:1rem}.qa-signal{padding:.95rem;border:1px solid var(--obs-line);border-radius:20px;background:rgba(255,255,255,.62)}.qa-label{color:var(--obs-muted);font-size:.72rem;letter-spacing:.1em;text-transform:uppercase}.qa-value{display:block;margin-top:.28rem;font-size:var(--obs-value-size);font-weight:880;letter-spacing: 0}.qa-note{color:var(--obs-muted);font-size:.8rem;margin-top:.25rem}
|
|
.qa-grid{display:grid;grid-template-columns:minmax(0,1.2fr) minmax(330px,.8fr);gap:1rem;margin-top:1rem}.qa-stack{display:grid;gap:1rem}.qa-panel-head,.qa-table-title{display:flex;justify-content:space-between;align-items:flex-start;gap:1rem;padding:1.05rem 1.1rem .25rem}.qa-panel-title,.qa-table-title h3{margin:.15rem 0 0;font-size:1.1rem;font-weight:850;letter-spacing: 0}.qa-panel-body{padding:1rem 1.1rem 1.1rem}.qa-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)}
|
|
.caller-card{padding:.85rem;border:1px solid var(--obs-line);border-radius:18px;background:rgba(255,255,255,.58);margin-bottom:.65rem}.caller-top{display:flex;justify-content:space-between;gap:.8rem}.caller-meter{height:7px;border-radius:999px;background:rgba(86,64,48,.1);overflow:hidden;margin-top:.55rem}.caller-meter span{display:block;height:100%;background:var(--obs-accent)}
|
|
@media(max-width:1100px){.qa-command{grid-template-columns:repeat(2,minmax(0,1fr))}.qa-grid{grid-template-columns:1fr}}@media(max-width:720px){.qa-command{grid-template-columns:1fr}}
|
|
</style>
|
|
|
|
{% set total = summary.total if summary else 0 %}
|
|
{% import "admin/_observability_labels.html" as obs_label %}
|
|
|
|
<div class="container-fluid mt-3">
|
|
<section class="qa-hero">
|
|
<div class="qa-kicker"><i class="fas fa-magnifying-glass-chart me-1"></i> 知識召回雷達 · {{ hours }} 小時視窗</div>
|
|
<h1 class="qa-title">知識召回雷達</h1>
|
|
<p class="qa-subtitle">追蹤知識是否命中、是否省下模型呼叫,避免業績建議缺少根據。</p>
|
|
<form method="get" class="qa-filter"><select name="hours" class="form-select form-select-sm" onchange="this.form.submit()">{% for h in [1,6,24,72,168] %}<option value="{{ h }}" {% if hours == h %}selected{% endif %}>{% if h < 24 %}{{ h }} 小時{% else %}{{ h//24 }} 天{% endif %}</option>{% endfor %}</select><select name="caller" class="form-select form-select-sm" onchange="this.form.submit()"><option value="">全部使用情境</option>{% for c in callers %}<option value="{{ c }}" {% if caller_filter == c %}selected{% endif %}>{{ obs_label.caller(c) }}</option>{% endfor %}</select><label class="form-check-label small d-flex align-items-center gap-2"><input class="form-check-input" type="checkbox" name="saved_only" value="1" {% if saved_only %}checked{% endif %} onchange="this.form.submit()">僅看已省下模型呼叫</label></form>
|
|
{% if summary and summary.total > 0 %}<div class="qa-command"><div class="qa-signal"><div class="qa-label">查詢數</div><span class="qa-value">{{ "{:,}".format(summary.total) }}</span><div class="qa-note">{{ summary.distinct_callers }} 個使用情境</div></div><div class="qa-signal"><div class="qa-label">命中率</div><span class="qa-value {% if summary.hit_rate >= 70 %}status-good{% elif summary.hit_rate >= 40 %}status-warn{% else %}status-bad{% endif %}">{{ "%.1f"|format(summary.hit_rate) }}%</span><div class="qa-note">{{ summary.with_hits }} 次命中 · {{ summary.no_hits }} 次未命中</div></div><div class="qa-signal"><div class="qa-label">省下模型</div><span class="qa-value status-blue">{{ "%.1f"|format(summary.saved_rate) }}%</span><div class="qa-note">{{ summary.saved }} 次省下模型呼叫</div></div><div class="qa-signal"><div class="qa-label">反饋分</div><span class="qa-value {% if summary.avg_score >= 4 %}status-good{% elif summary.avg_score >= 3 %}status-warn{% else %}status-bad{% endif %}">{{ "%.2f"|format(summary.avg_score) }}</span><div class="qa-note">{{ summary.feedback_count }} 筆 · 平均 {{ summary.avg_hits }} 次命中</div></div></div>{% endif %}
|
|
</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="qa-grid">
|
|
<div class="qa-stack">
|
|
<article class="qa-table-shell"><div class="qa-table-title"><div><div class="qa-label">查詢串流</div><h3>最近 50 筆查詢詳情</h3></div></div><div class="table-responsive">{% if queries %}<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">命中</th><th>已省下</th><th>反饋</th><th>動作</th></tr></thead><tbody>{% for q in queries %}<tr><td><small>{{ q.queried_at }}</small></td><td><span>{{ obs_label.caller(q.caller) }}</span></td><td><small>{{ q.query_text }}{% if q.query_text|length >= 200 %}…{% endif %}</small></td><td class="text-end">{{ q.take_count }}</td><td class="text-end">{{ q.threshold }}</td><td class="text-end">{% if q.hit_count > 0 %}<strong class="status-good">{{ q.hit_count }}</strong>{% else %}<small class="text-muted">0</small>{% endif %}</td><td>{% if q.saved_call %}<span class="badge bg-success">已省下</span>{% else %}<small class="text-muted">—</small>{% endif %}</td><td>{% if q.feedback_score is not none %}<span class="badge {% if q.feedback_score >= 4 %}bg-success{% elif q.feedback_score >= 3 %}bg-warning{% else %}bg-danger{% endif %}">{{ q.feedback_score }}/5</span>{% else %}<small class="text-muted">—</small>{% endif %}</td><td>{% if q.hit_count > 0 %}<button class="btn btn-sm btn-outline-info" onclick="showHits({{ q.id }})"><i class="fas fa-eye me-1"></i>查命中</button>{% endif %}</td></tr>{% endfor %}</tbody></table>{% else %}<div class="alert alert-info m-3">過去 {{ hours }} 小時無符合條件的知識查詢紀錄。</div>{% endif %}</div></article>
|
|
</div>
|
|
<aside class="qa-stack">
|
|
{% if by_caller %}<article class="qa-panel"><div class="qa-panel-head"><div><div class="qa-label">使用情境品質</div><h2 class="qa-panel-title">各使用情境知識表現</h2></div></div><div class="qa-panel-body">{% for c in by_caller %}<div class="caller-card"><div class="caller-top"><span>{{ obs_label.caller(c.caller) }}</span><strong class="{% if c.hit_rate >= 70 %}status-good{% elif c.hit_rate >= 40 %}status-warn{% else %}text-muted{% endif %}">{{ "%.1f"|format(c.hit_rate) }}%</strong></div><div class="caller-meter"><span style="width: {{ c.hit_rate|round|int }}%"></span></div><small class="text-muted">{{ c.total }} 次查詢 · 省下 {{ "%.1f"|format(c.saved_rate) }}% · 反饋 {{ c.fb_count }} 筆</small></div>{% endfor %}</div></article>{% endif %}
|
|
</aside>
|
|
</section>
|
|
|
|
<p class="text-muted mt-3"><small><i class="fas fa-robot me-1"></i>知識召回雷達</small></p>
|
|
</div>
|
|
|
|
<div class="modal fade" id="hitsModal" tabindex="-1"><div class="modal-dialog modal-lg"><div class="modal-content"><div class="modal-header"><h5 class="modal-title"><i class="fas fa-eye me-2"></i>知識命中內容</h5><button type="button" class="btn-close" data-bs-dismiss="modal"></button></div><div class="modal-body" id="hitsModalBody"><div class="text-center"><i class="fas fa-spinner fa-spin"></i> 載入中...</div></div></div></div></div>
|
|
<script src="{{ url_for('static', filename='js/observability-charts.js') }}"></script>
|
|
{% endblock %}
|