Files
ewoooc/templates/admin/quality_trend.html
OoO 99d2f3c543
All checks were successful
CD Pipeline / deploy (push) Successful in 2m25s
fix(p32): admin URL prefix /admin → /observability — 避開 188 nginx SPA shadow
Root cause(curl 實證):
  prod 188 nginx 對 /admin/* 設 try_files → SPA index.html fallback
  → Phase 27-31 的 6 個 Flask admin 路由全被 nginx 攔截
  → 外部 GET /admin/ai_calls 回 7480 byte 靜態 HTML(同 etag = SPA shell)
  → 我之前說「6 admin 頁 prod 200」是回了 200,但 body 不是 Flask 渲染

修法:
  Blueprint url_prefix /admin → /observability
  → 6 個觀測頁實際生效在 /observability/* 不被 SPA 遮蔽
  → SPA frontend 仍擁有 /admin/* 命名空間(不破壞既有前端)

更新範圍:
  - routes/admin_observability_routes.py: url_prefix + 註解全改
  - 6 templates: 所有 href / fetch() 路徑改 /observability/
  - tests/test_admin_observability_routes.py: client.get/post 路徑改
  - 10/10 smoke tests 仍 PASS

統帥訪問新路徑:
  http://192.168.0.188/observability/ai_calls
  http://192.168.0.188/observability/host_health
  http://192.168.0.188/observability/budget
  http://192.168.0.188/observability/promotion_review
  http://192.168.0.188/observability/quality_trend
  http://192.168.0.188/observability/ppt_audit_history
2026-05-04 14:13:27 +08:00

105 lines
3.8 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 %}Caller Quality Trend{% endblock %}
{% block content %}
<div class="container-fluid mt-3">
<h2 class="mb-3">💬 Caller 反饋趨勢
<small class="text-muted">過去 {{ days }} 日</small>
</h2>
{% if error %}
<div class="alert alert-warning"><strong>⚠️</strong> {{ error }}</div>
{% endif %}
<form method="get" class="row g-2 mb-3">
<div class="col-auto">
<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>
</div>
<div class="col-auto"><button class="btn btn-primary btn-sm">查詢</button></div>
</form>
{% if recommendations %}
<div class="card mb-3">
<div class="card-header bg-warning"><strong>🔮 智能建議</strong></div>
<div class="card-body">
<ul class="mb-0">
{% for rec in recommendations %}
<li>
{% if rec.action == 'review' %}⚠️{% else %}✅{% endif %}
<code>{{ rec.caller }}</code>: {{ rec.reason }}
</li>
{% endfor %}
</ul>
</div>
</div>
{% endif %}
<div class="card">
<div class="card-header"><strong>Caller × 反饋分佈</strong>
<small class="text-muted">avg_score 升序,最差先看)</small>
</div>
<div class="card-body p-0">
<table class="table table-sm mb-0">
<thead class="table-light">
<tr>
<th>Caller</th><th class="text-end">Avg</th>
<th class="text-end">👍</th><th class="text-end">👎</th>
<th class="text-end">N</th><th>Trend</th><th>Bar</th>
</tr>
</thead>
<tbody>
{% for caller, info in trends %}
<tr>
<td><code>{{ caller }}</code></td>
<td class="text-end">
<strong>{{ "%.2f"|format(info.avg_score) }}</strong>/5
</td>
<td class="text-end text-success">{{ info.thumbs_up }}</td>
<td class="text-end text-danger">{{ info.thumbs_down }}</td>
<td class="text-end">{{ info.total_feedback }}</td>
<td>
{% if info.trend == 'positive' %}
<span class="badge bg-success">✅ Positive</span>
{% elif info.trend == 'negative' %}
<span class="badge bg-danger">⚠️ Negative</span>
{% elif info.trend == 'neutral' %}
<span class="badge bg-secondary"> Neutral</span>
{% else %}
<span class="badge bg-light text-dark">❓ No Data</span>
{% endif %}
</td>
<td style="width: 200px;">
<div class="progress" style="height: 8px;">
{% set pct = (info.avg_score / 5 * 100)|int %}
<div class="progress-bar
{% if pct >= 80 %}bg-success
{% elif pct >= 50 %}bg-info
{% else %}bg-danger{% endif %}"
style="width: {{ pct }}%"></div>
</div>
</td>
</tr>
{% else %}
<tr><td colspan="7" class="text-center text-muted">無反饋資料</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<p class="text-muted mt-3"><small>
🤖 Operation Ollama-First v5.0 / Phase 29 — Caller Quality Trend
| <a href="/observability/ai_calls">AI Calls</a>
| <a href="/observability/promotion_review">Promotion Review</a>
| <a href="/observability/host_health">Host Health</a>
| <a href="/observability/budget">Budget</a>
| <a href="/observability/ppt_audit_history">PPT Audit</a>
</small></p>
</div>
{% endblock %}