All checks were successful
CD Pipeline / deploy (push) Successful in 2m37s
統帥質疑:「那六頁的視覺方格 UI/UX 搞好了嗎?還有新增頁面嗎?」
回答:沒有,從 Phase 38 開始一直推遲。本 commit 補做。
I-1: 6 頁 base.html → ewoooc_base.html
- host_health / ai_calls_dashboard / budget / promotion_review /
quality_trend / ppt_audit_history 全改
- {% extends "base.html" %} → {% extends "ewoooc_base.html" %}
- {% block content %} → {% block ewooo_content %}
- 自動繼承:sidebar 240px / topbar 64px / fonts (Inter+JetBrains+Noto Sans TC)
/ ewoooc-tokens.css / ewoooc-shell.css / search box / 米色背景
I-2: _ewoooc_shell.html 加「AI 觀測」nav group
- 7 個項目:觀測台總覽 / 主機健康 / AI 呼叫 / 預算控管 /
RAG 晉升審核 / 反饋趨勢 / PPT 視覺審核
- 對應 active_page='obs_*',正確高亮
- 編號 07-13(系統管理改 14)
I-3: 新增頁面 /observability/ + /observability/overview
- routes/admin_observability_routes.py::observability_overview
- 單頁聚合 8 表跨 JOIN 的 KPI:
• 三主機 24h 在線率(host_health_probes,per host card)
• AI 呼叫 24h(ai_calls:total/tokens/cost/error rate/RAG hit/cache hit)
• 當月成本累計
• 預算告警(ratio ≥ alert_pct 自動列表)
• AIOps 7d(incidents + heal_logs:自癒成功率)
• MCP 24h(mcp_calls:tool 呼叫 + cache 率 + cost)
• RAG 學習 30d(learning_episodes:待審 + 晉升率)
• PPT 視覺審核 7d(ppt_audit_results:通過率)
• 6 大子頁入口卡(含一行說明)
- 對應 Phase 44 daily Telegram summary 的 web 版本
- 全部失敗安全(個別 query 失敗只跳過該卡,不擋整頁)
升級對應:
- UI 框架:base.html → ewoooc_base.html ✅(sidebar + topbar + token css 已生效)
- 設計憲法:8 卡片 + 8 表跨 JOIN 全景 + 一頁式總覽
- 入口:sidebar 7 項 + 觀測台首頁
- 資料表覆蓋:4 表(Phase 38)→ 8 表(Phase 45)
注意:完整 design token 重塑(Bootstrap class → --momo-* token / 焦糖橘)
留待後續 phase;本 commit 重點是「框架升級 + 新總覽頁」。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
158 lines
6.7 KiB
HTML
158 lines
6.7 KiB
HTML
{% extends "ewoooc_base.html" %}
|
||
|
||
{% block title %}Caller 反饋趨勢{% endblock %}
|
||
|
||
{% block ewooo_content %}
|
||
<div class="container-fluid mt-3">
|
||
<h2 class="mb-3"><i class="fas fa-comments me-2"></i>Caller 反饋趨勢
|
||
<small class="text-muted">過去 {{ days }} 日</small>
|
||
</h2>
|
||
|
||
{% if error %}
|
||
<div class="alert alert-warning"><strong><i class="fas fa-exclamation-triangle me-1"></i></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 episode_distribution %}
|
||
<div class="card mb-3">
|
||
<div class="card-header"><strong><i class="fas fa-flask me-2"></i>蒸餾池狀態(learning_episodes 過去 {{ days }} 日)</strong>
|
||
<small class="text-muted">資料來源:learning_episodes — 展現 RAG 學習鏈路飽和度</small>
|
||
</div>
|
||
<div class="card-body">
|
||
<div class="row g-2">
|
||
{% for status, cnt in episode_distribution.items() %}
|
||
<div class="col-md-2 col-sm-4">
|
||
<div class="border rounded p-2 text-center">
|
||
<small class="text-muted d-block">
|
||
{% if status == 'pending' %}<i class="fas fa-hourglass-start"></i> 待處理
|
||
{% elif status == 'awaiting_review' %}<i class="fas fa-user-clock"></i> 待審核
|
||
{% elif status == 'approved' %}<i class="fas fa-check-circle text-success"></i> 已晉升
|
||
{% elif status == 'rejected_quality' %}<i class="fas fa-times text-danger"></i> 品質拒
|
||
{% elif status == 'rejected_hallucination' %}<i class="fas fa-times text-danger"></i> 幻覺拒
|
||
{% elif status == 'rejected_duplicate' %}<i class="fas fa-clone text-warning"></i> 重複拒
|
||
{% elif status == 'rejected_human' %}<i class="fas fa-user-times text-danger"></i> 人工拒
|
||
{% elif status == 'expired' %}<i class="fas fa-clock text-muted"></i> 已過期
|
||
{% else %}{{ status }}{% endif %}
|
||
</small>
|
||
<strong style="font-size: 1.4em;">{{ cnt }}</strong>
|
||
</div>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
|
||
{% if rag_root_causes %}
|
||
<div class="card mb-3" style="border-left: 4px solid #6f42c1;">
|
||
<div class="card-header bg-light">
|
||
<strong><i class="fas fa-stethoscope me-2"></i>RAG 自動根因建議</strong>
|
||
<small class="text-muted">— 對最差 3 名 caller 自動從 ai_insights 召回相似案例</small>
|
||
</div>
|
||
<div class="card-body p-2">
|
||
{% for rc in rag_root_causes %}
|
||
<div class="mb-3 p-2" style="background: #fafafa; border-radius: 6px;">
|
||
<strong><code>{{ rc.caller }}</code></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 text-dark me-1">{{ 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>
|
||
</div>
|
||
{% endif %}
|
||
|
||
{% if recommendations %}
|
||
<div class="card mb-3">
|
||
<div class="card-header bg-warning"><strong><i class="fas fa-lightbulb me-2"></i>智能建議</strong></div>
|
||
<div class="card-body">
|
||
<ul class="mb-0">
|
||
{% for rec in recommendations %}
|
||
<li>
|
||
{% if rec.action == 'review' %}<i class="fas fa-exclamation-triangle text-warning me-1"></i>{% else %}<i class="fas fa-check text-success me-1"></i>{% endif %}
|
||
<code>{{ rec.caller }}</code>:{{ rec.reason }}
|
||
</li>
|
||
{% endfor %}
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
|
||
<div class="card">
|
||
<div class="card-header"><strong>呼叫端 × 反饋分佈</strong>
|
||
<small class="text-muted">(平均分數升序排列,最差先看)</small>
|
||
</div>
|
||
<div class="card-body p-0">
|
||
<table class="table table-sm mb-0">
|
||
<thead class="table-light">
|
||
<tr>
|
||
<th>呼叫端</th><th class="text-end">平均</th>
|
||
<th class="text-end"><i class="fas fa-thumbs-up text-success"></i></th>
|
||
<th class="text-end"><i class="fas fa-thumbs-down text-danger"></i></th>
|
||
<th class="text-end">總數</th><th>趨勢</th><th>分布</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"><i class="fas fa-arrow-up me-1"></i>正向</span>
|
||
{% elif info.trend == 'negative' %}
|
||
<span class="badge bg-danger"><i class="fas fa-arrow-down me-1"></i>負向</span>
|
||
{% elif info.trend == 'neutral' %}
|
||
<span class="badge bg-secondary"><i class="fas fa-minus me-1"></i>中性</span>
|
||
{% else %}
|
||
<span class="badge bg-light text-dark"><i class="fas fa-question me-1"></i>無資料</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>
|
||
<i class="fas fa-robot me-1"></i>Operation Ollama-First v5.0 / Phase 29 — Caller 反饋趨勢
|
||
</small></p>
|
||
</div>
|
||
{% endblock %}
|