45 lines
11 KiB
HTML
45 lines
11 KiB
HTML
{% extends "ewoooc_base.html" %}
|
||
|
||
{% block title %}AI 品質診斷台{% endblock %}
|
||
|
||
{% block ewooo_content %}
|
||
<style>
|
||
.quality-hero,.quality-panel,.quality-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)}
|
||
.quality-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))}
|
||
.quality-kicker{color:var(--obs-accent);font-size:.76rem;letter-spacing:.13em;text-transform:uppercase;font-weight:850}.quality-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}.quality-subtitle{color:var(--obs-muted);max-width:860px;line-height:1.7}
|
||
.quality-filter{display:flex;gap:.55rem;flex-wrap:wrap;margin-top:1rem;padding:.8rem;border:1px solid var(--obs-line);border-radius:20px;background:rgba(255,255,255,.58)}.quality-command{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:.75rem;margin-top:1rem}.quality-signal{padding:.95rem;border:1px solid var(--obs-line);border-radius:20px;background:rgba(255,255,255,.62)}.quality-label{color:var(--obs-muted);font-size:.72rem;letter-spacing:.1em;text-transform:uppercase}.quality-value{display:block;margin-top:.28rem;font-size:var(--obs-value-size);font-weight:880;letter-spacing: 0}
|
||
.quality-grid{display:grid;grid-template-columns:minmax(0,1.18fr) minmax(330px,.82fr);gap:1rem;margin-top:1rem}.quality-stack{display:grid;gap:1rem}.quality-panel-head,.quality-table-title{display:flex;justify-content:space-between;align-items:flex-start;gap:1rem;padding:1.05rem 1.1rem .25rem}.quality-panel-title,.quality-table-title h3{margin:.15rem 0 0;font-size:1.1rem;font-weight:850;letter-spacing: 0}.quality-panel-body{padding:1rem 1.1rem 1.1rem}.quality-table-shell{overflow:hidden;margin-top:1rem}.quality-mini-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:.7rem}.quality-mini{padding:.85rem;border:1px solid var(--obs-line);border-radius:18px;background:rgba(255,255,255,.58)}.quality-mini strong{display:block;margin-top:.24rem;font-size:1.35rem;letter-spacing: 0}.root-card{padding:.85rem;border:1px solid var(--obs-line);border-radius:18px;background:rgba(255,255,255,.58);margin-bottom:.7rem}.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){.quality-command{grid-template-columns:repeat(2,minmax(0,1fr))}.quality-grid{grid-template-columns:1fr}}@media(max-width:720px){.quality-command,.quality-mini-grid{grid-template-columns:1fr}}
|
||
</style>
|
||
|
||
{% import "admin/_observability_labels.html" as obs_label %}
|
||
{% set total_feedback = namespace(value=0) %}{% set worst_avg = namespace(value=5) %}{% for caller, info in trends %}{% set total_feedback.value = total_feedback.value + (info.total_feedback or 0) %}{% if info.avg_score < worst_avg.value %}{% set worst_avg.value = info.avg_score %}{% endif %}{% endfor %}
|
||
{% set episode_total = (episode_distribution.values() | sum) if episode_distribution else 0 %}
|
||
{% set rag_total = (rag_overall_dist | sum(attribute='count')) if rag_overall_dist else 0 %}
|
||
|
||
<div class="container-fluid mt-3">
|
||
<section class="quality-hero"><div class="quality-kicker"><i class="fas fa-comments me-1"></i> 品質診斷 · {{ days }} 日視窗</div><h1 class="quality-title">AI 品質診斷台</h1><p class="quality-subtitle">用反饋、知識分數與行動閉環檢查 AI 建議是否可靠。</p><form method="get" class="quality-filter"><select name="days" class="form-select form-select-sm">{% for d in [7,14,30,90] %}<option value="{{ d }}" {% if days == d %}selected{% endif %}>{{ d }} 日</option>{% endfor %}</select><button class="btn btn-primary btn-sm">查詢</button></form><div class="quality-command"><div class="quality-signal"><div class="quality-label">反饋總量</div><span class="quality-value">{{ total_feedback.value }}</span><small class="text-muted">使用情境反饋總量</small></div><div class="quality-signal"><div class="quality-label">最低均分</div><span class="quality-value {% if worst_avg.value >= 4 %}status-good{% elif worst_avg.value >= 3 %}status-warn{% else %}status-bad{% endif %}">{{ "%.2f"|format(worst_avg.value) }}</span><small class="text-muted">最低使用情境平均分</small></div><div class="quality-signal"><div class="quality-label">蒸餾樣本</div><span class="quality-value status-blue">{{ episode_total }}</span><small class="text-muted">蒸餾池 {{ days }} 日</small></div><div class="quality-signal"><div class="quality-label">知識評分</div><span class="quality-value">{{ rag_total }}</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="quality-grid">
|
||
<div class="quality-stack">
|
||
<article class="quality-table-shell"><div class="quality-table-title"><div><div class="quality-label">使用情境反饋</div><h3>使用情境反饋分佈</h3></div></div><div class="table-responsive"><table class="table table-sm mb-0"><thead class="table-light"><tr><th>使用情境</th><th class="text-end">平均</th><th class="text-end">讚</th><th class="text-end">倒讚</th><th class="text-end">總數</th><th>趨勢</th><th>分布</th></tr></thead><tbody>{% for caller, info in trends %}<tr><td><span>{{ obs_label.caller(caller) }}</span></td><td class="text-end"><strong class="{% if info.avg_score >= 4 %}status-good{% elif info.avg_score >= 3 %}status-warn{% else %}status-bad{% endif %}">{{ "%.2f"|format(info.avg_score) }}</strong>/5</td><td class="text-end status-good">{{ info.thumbs_up }}</td><td class="text-end status-bad">{{ info.thumbs_down }}</td><td class="text-end">{{ info.total_feedback }}</td><td>{% if info.trend == 'positive' %}<span class="badge bg-success">正向</span>{% elif info.trend == 'negative' %}<span class="badge bg-danger">負向</span>{% elif info.trend == 'neutral' %}<span class="badge bg-secondary">中性</span>{% else %}<span class="badge bg-light text-dark">無資料</span>{% endif %}</td><td class="quality-distribution-cell"><div class="progress obs-progress-sm"><div class="progress-bar" style="width:{{ (info.avg_score / 5 * 100)|int }}%"></div></div></td></tr>{% else %}<tr><td colspan="7" class="text-center text-muted">無反饋資料</td></tr>{% endfor %}</tbody></table></div></article>
|
||
{% if action_plans_status %}<article class="quality-table-shell"><div class="quality-table-title"><div><div class="quality-label">行動計畫</div><h3>行動計畫狀態分布</h3></div></div><div class="table-responsive"><table class="table table-sm mb-0"><thead class="table-light"><tr><th>狀態</th><th>計畫類型</th><th class="text-end">數量</th></tr></thead><tbody>{% for a in action_plans_status %}<tr><td><span class="badge {% if a.status == 'approved' %}bg-success{% elif a.status == 'pending' %}bg-warning{% elif a.status == 'rejected' %}bg-danger{% else %}bg-secondary{% endif %}">{{ obs_label.status(a.status) }}</span></td><td><code>{{ obs_label.plan_type(a.plan_type) }}</code></td><td class="text-end">{{ a.count }}</td></tr>{% endfor %}</tbody></table></div></article>{% endif %}
|
||
</div>
|
||
<aside class="quality-stack">
|
||
{% if rag_overall_dist %}<article class="quality-panel"><div class="quality-panel-head"><div><div class="quality-label">知識反饋總量</div><h2 class="quality-panel-title">知識分數分布</h2></div></div><div class="quality-panel-body"><div class="obs-chart-frame"><canvas id="ragFeedbackPieChart"></canvas></div></div></article>{% endif %}
|
||
{% if episode_distribution %}<article class="quality-panel"><div class="quality-panel-head"><div><div class="quality-label">學習池</div><h2 class="quality-panel-title">蒸餾池狀態</h2></div></div><div class="quality-panel-body"><div class="quality-mini-grid">{% for status, cnt in episode_distribution.items() %}<div class="quality-mini"><span class="quality-label">{{ obs_label.status(status) }}</span><strong>{{ cnt }}</strong></div>{% endfor %}</div></div></article>{% endif %}
|
||
</aside>
|
||
</section>
|
||
|
||
{% if rag_root_causes %}<section class="quality-panel mt-3"><div class="quality-panel-head"><div><div class="quality-label">根因分析</div><h2 class="quality-panel-title">知識根因建議</h2></div></div><div class="quality-panel-body">{% for rc in rag_root_causes %}<div class="root-card"><strong>{{ obs_label.caller(rc.caller) }}</strong><span class="badge bg-danger ms-1">{{ "%.2f"|format(rc.avg_score) }}/5</span><span class="badge bg-secondary ms-1">{{ rc.feedback_n }} 筆</span><ul class="list-unstyled mt-2 mb-0 small">{% for h in rc.hits %}<li class="mb-1"><span class="badge bg-info me-1">{{ obs_label.insight(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></section>{% endif %}
|
||
{% if recommendations %}<section class="quality-panel mt-3"><div class="quality-panel-head"><div><div class="quality-label">智能建議</div><h2 class="quality-panel-title">智能建議</h2></div></div><div class="quality-panel-body"><ul class="mb-0">{% for rec in recommendations %}<li>{% if rec.action == 'review' %}<i class="fas fa-triangle-exclamation status-warn me-1"></i>{% else %}<i class="fas fa-check status-good me-1"></i>{% endif %}<strong>{{ obs_label.caller(rec.caller) }}</strong>:{{ rec.reason }}</li>{% endfor %}</ul></div></section>{% endif %}
|
||
{% if action_outcomes_stats %}<section class="quality-panel mt-3"><div class="quality-panel-head"><div><div class="quality-label">動作成效</div><h2 class="quality-panel-title">實際動作成效</h2></div></div><div class="quality-panel-body"><div class="quality-mini-grid">{% set total_ao = (action_outcomes_stats | sum(attribute='count')) or 1 %}{% for r in action_outcomes_stats %}<div class="quality-mini"><span class="quality-label">{{ obs_label.verdict(r.verdict) }}</span><strong class="{% if r.verdict == 'effective' %}status-good{% elif r.verdict == 'backfired' %}status-bad{% endif %}">{{ r.count }}</strong><small class="text-muted">{{ "%.1f"|format(r.count / total_ao * 100) }}%</small></div>{% endfor %}</div></div></section>{% endif %}
|
||
<p class="text-muted mt-3"><small><i class="fas fa-robot me-1"></i>AI 品質診斷台</small></p>
|
||
</div>
|
||
|
||
<template id="obs-quality-trend-data">{{ rag_overall_dist | 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 %}
|