All checks were successful
CD Pipeline / deploy (push) Successful in 2m25s
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
105 lines
3.8 KiB
HTML
105 lines
3.8 KiB
HTML
{% 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 %}
|