This commit is contained in:
@@ -559,6 +559,42 @@
|
||||
padding: 0.36em 0.62em;
|
||||
}
|
||||
|
||||
.momo-observability-mode .obs-chart-frame {
|
||||
height: 250px;
|
||||
min-height: 220px;
|
||||
}
|
||||
|
||||
.momo-observability-mode .obs-chart-frame-tall {
|
||||
height: 260px;
|
||||
}
|
||||
|
||||
.momo-observability-mode .obs-chart-frame-slim {
|
||||
height: 86px;
|
||||
min-height: 72px;
|
||||
}
|
||||
|
||||
.momo-observability-mode .obs-progress-xs {
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
.momo-observability-mode .obs-progress-sm {
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
.momo-observability-mode .quality-distribution-cell {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.momo-observability-mode .budget-input {
|
||||
width: 110px;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.momo-observability-mode .alert-input {
|
||||
width: 80px;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* Cross-page layout hardening */
|
||||
.momo-observability-mode .momo-content {
|
||||
overflow-x: hidden;
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
<div class="gov-stack">
|
||||
<article class="gov-table-shell">
|
||||
<div class="gov-table-title"><div><div class="gov-label">Budget Lines</div><h3>預算線與節流狀態</h3></div></div>
|
||||
<div class="table-responsive"><table class="table table-hover mb-0"><thead class="table-light"><tr><th>週期</th><th>供應商</th><th class="text-end">已花費</th><th>預算</th><th>閾值</th><th class="text-end">使用率</th><th>狀態</th><th>動作</th></tr></thead><tbody>{% for r in rows %}<tr {% if r.throttled %}class="table-danger"{% elif r.ratio >= 0.8 %}class="table-warning"{% endif %}><td><span class="badge bg-secondary">{{ r.period }}</span></td><td><code>{{ r.provider }}</code></td><td class="text-end">${{ "%.2f"|format(r.spent) }}</td><td><input type="number" step="0.01" min="0.01" value="{{ "%.2f"|format(r.budget_usd) }}" class="form-control form-control-sm budget-input" data-budget-id="{{ r.id }}" style="width:110px;"></td><td><input type="number" min="1" max="100" value="{{ r.alert_pct }}" class="form-control form-control-sm alert-input" data-budget-id="{{ r.id }}" style="width:80px;"></td><td class="text-end"><strong class="{% if r.ratio >= 1.10 %}status-bad{% elif r.ratio >= 0.8 %}status-warn{% else %}status-good{% endif %}">{{ "%.0f"|format(r.ratio * 100) }}%</strong></td><td>{% if r.throttled %}<span class="badge bg-danger">已節流</span>{% elif r.ratio >= 0.8 %}<span class="badge bg-warning">接近上限</span>{% else %}<span class="badge bg-success">正常</span>{% endif %}</td><td><button class="btn btn-primary btn-sm save-budget-btn" data-budget-id="{{ r.id }}" onclick="saveBudget({{ r.id }})"><i class="fas fa-save me-1"></i>儲存</button></td></tr>{% else %}<tr><td colspan="8" class="text-center text-muted">無預算資料(需先跑 migrations/025)</td></tr>{% endfor %}</tbody></table></div>
|
||||
<div class="table-responsive"><table class="table table-hover mb-0"><thead class="table-light"><tr><th>週期</th><th>供應商</th><th class="text-end">已花費</th><th>預算</th><th>閾值</th><th class="text-end">使用率</th><th>狀態</th><th>動作</th></tr></thead><tbody>{% for r in rows %}<tr {% if r.throttled %}class="table-danger"{% elif r.ratio >= 0.8 %}class="table-warning"{% endif %}><td><span class="badge bg-secondary">{{ r.period }}</span></td><td><code>{{ r.provider }}</code></td><td class="text-end">${{ "%.2f"|format(r.spent) }}</td><td><input type="number" step="0.01" min="0.01" value="{{ "%.2f"|format(r.budget_usd) }}" class="form-control form-control-sm budget-input" data-budget-id="{{ r.id }}" ></td><td><input type="number" min="1" max="100" value="{{ r.alert_pct }}" class="form-control form-control-sm alert-input" data-budget-id="{{ r.id }}" ></td><td class="text-end"><strong class="{% if r.ratio >= 1.10 %}status-bad{% elif r.ratio >= 0.8 %}status-warn{% else %}status-good{% endif %}">{{ "%.0f"|format(r.ratio * 100) }}%</strong></td><td>{% if r.throttled %}<span class="badge bg-danger">已節流</span>{% elif r.ratio >= 0.8 %}<span class="badge bg-warning">接近上限</span>{% else %}<span class="badge bg-success">正常</span>{% endif %}</td><td><button class="btn btn-primary btn-sm save-budget-btn" data-budget-id="{{ r.id }}" onclick="saveBudget({{ r.id }})"><i class="fas fa-save me-1"></i>儲存</button></td></tr>{% else %}<tr><td colspan="8" class="text-center text-muted">無預算資料(需先跑 migrations/025)</td></tr>{% endfor %}</tbody></table></div>
|
||||
</article>
|
||||
|
||||
{% if cost_trend_30d %}
|
||||
@@ -65,11 +65,11 @@
|
||||
|
||||
<aside class="gov-stack">
|
||||
{% if provider_cost_month %}
|
||||
<article class="gov-panel"><div class="gov-panel-head"><div><div class="gov-label">Provider Mix</div><h2 class="gov-panel-title">當月成本分布</h2></div></div><div class="gov-panel-body"><div style="height:250px;"><canvas id="providerCostPieChart"></canvas></div></div></article>
|
||||
<article class="gov-panel"><div class="gov-panel-head"><div><div class="gov-label">Provider Mix</div><h2 class="gov-panel-title">當月成本分布</h2></div></div><div class="gov-panel-body"><div class="obs-chart-frame"><canvas id="providerCostPieChart"></canvas></div></div></article>
|
||||
{% endif %}
|
||||
|
||||
{% if top_cost_callers %}
|
||||
<article class="gov-panel"><div class="gov-panel-head"><div><div class="gov-label">Burn Rate</div><h2 class="gov-panel-title">Top 5 燒錢 caller</h2></div></div><div class="gov-panel-body">{% set max_cost = (top_cost_callers | map(attribute='cost') | max) or 1 %}{% for c in top_cost_callers %}<div class="gov-mini mb-2"><div class="d-flex justify-content-between"><code>{{ c.caller }}</code><strong>${{ "%.2f"|format(c.cost) }}</strong></div><div class="progress mt-2" style="height:6px;"><div class="progress-bar" style="width: {{ (c.cost / max_cost * 100) | round | int }}%; background: var(--obs-accent);"></div></div><small class="text-muted">{{ "{:,}".format(c.calls) }} calls · {{ "{:,}".format(c.tokens) }} tokens</small></div>{% endfor %}</div></article>
|
||||
<article class="gov-panel"><div class="gov-panel-head"><div><div class="gov-label">Burn Rate</div><h2 class="gov-panel-title">Top 5 燒錢 caller</h2></div></div><div class="gov-panel-body">{% set max_cost = (top_cost_callers | map(attribute='cost') | max) or 1 %}{% for c in top_cost_callers %}<div class="gov-mini mb-2"><div class="d-flex justify-content-between"><code>{{ c.caller }}</code><strong>${{ "%.2f"|format(c.cost) }}</strong></div><div class="progress mt-2 obs-progress-xs"><div class="progress-bar" style="width: {{ (c.cost / max_cost * 100) | round | int }}%;"></div></div><small class="text-muted">{{ "{:,}".format(c.calls) }} calls · {{ "{:,}".format(c.tokens) }} tokens</small></div>{% endfor %}</div></article>
|
||||
{% endif %}
|
||||
</aside>
|
||||
</section>
|
||||
|
||||
@@ -121,7 +121,7 @@
|
||||
<div class="runtime-mini"><span class="runtime-label">P0/P1</span><strong class="{% if (aiops_summary.incidents_p0 + aiops_summary.incidents_p1) > 0 %}status-bad{% else %}status-good{% endif %}">{{ aiops_summary.incidents_p0 + aiops_summary.incidents_p1 }}</strong></div>
|
||||
<div class="runtime-mini"><span class="runtime-label">平均耗時</span><strong>{{ aiops_summary.heals_avg_ms }}ms</strong></div>
|
||||
</div>
|
||||
{% if aiops_summary.heal_sparkline %}<div class="mt-3" style="height: 86px;"><canvas id="healSparkline"></canvas></div>{% endif %}
|
||||
{% if aiops_summary.heal_sparkline %}<div class="mt-3" class="obs-chart-frame obs-chart-frame-slim"><canvas id="healSparkline"></canvas></div>{% endif %}
|
||||
</div>
|
||||
</article>
|
||||
{% endif %}
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
margin: 0.85rem 0 0.4rem;
|
||||
max-width: 820px;
|
||||
font-family:'Noto Sans TC','Inter',sans-serif;
|
||||
font-size: clamp(2rem, 4.6vw, 4.45rem);
|
||||
font-size: clamp(1.9rem, 3.2vw, 2.75rem);
|
||||
line-height: 0.95;
|
||||
letter-spacing: -0.055em;
|
||||
}
|
||||
@@ -284,7 +284,7 @@
|
||||
|
||||
.obs-link-button:hover {
|
||||
transform: translateY(-1px);
|
||||
background: #fff;
|
||||
background: rgba(255, 252, 247, 0.96);
|
||||
box-shadow: 0 10px 24px rgba(201, 100, 66, 0.14);
|
||||
color: var(--obs-accent);
|
||||
}
|
||||
@@ -331,7 +331,7 @@
|
||||
|
||||
.obs-route-card:hover {
|
||||
border-color: rgba(201, 100, 66, 0.28);
|
||||
background: #fff;
|
||||
background: rgba(255, 252, 247, 0.96);
|
||||
color: var(--obs-ink);
|
||||
transform: translateX(3px);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<article class="ppt-table-shell"><div class="ppt-table-title"><div><div class="ppt-label">Generated Files</div><h3>過去 7 日 PPT 檔案</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">KB</th><th>修改時間</th><th>狀態</th></tr></thead><tbody>{% for f in files %}<tr><td><code>{{ f.name }}</code></td><td class="text-end">{{ f.size_kb }}</td><td><small>{{ f.mtime }}</small></td><td><small class="text-muted">22:00 cron 自動審核</small></td></tr>{% else %}<tr><td colspan="4" class="text-center text-muted">過去 7 日無 PPT 生成</td></tr>{% endfor %}</tbody></table></div></article>
|
||||
</div>
|
||||
<aside class="ppt-stack">
|
||||
{% if audit_30d_stats and audit_30d_stats.total > 0 %}<article class="ppt-panel"><div class="ppt-panel-head"><div><div class="ppt-label">30d Audit Mix</div><h2 class="ppt-panel-title">審核結果分布</h2></div></div><div class="ppt-panel-body"><div style="height:250px"><canvas id="pptAuditPieChart"></canvas></div><div class="ppt-mini-grid mt-3"><div class="ppt-mini"><span class="ppt-label">Passed</span><strong class="status-good">{{ audit_30d_stats.passed }}</strong></div><div class="ppt-mini"><span class="ppt-label">Failed</span><strong class="{% if audit_30d_stats.failed > 0 %}status-warn{% endif %}">{{ audit_30d_stats.failed }}</strong></div><div class="ppt-mini"><span class="ppt-label">Error</span><strong class="{% if audit_30d_stats.error > 0 %}status-bad{% endif %}">{{ audit_30d_stats.error }}</strong></div><div class="ppt-mini"><span class="ppt-label">Confidence</span><strong>{{ "%.2f"|format(audit_30d_stats.avg_confidence) }}</strong></div></div></div></article>{% endif %}
|
||||
{% if audit_30d_stats and audit_30d_stats.total > 0 %}<article class="ppt-panel"><div class="ppt-panel-head"><div><div class="ppt-label">30d Audit Mix</div><h2 class="ppt-panel-title">審核結果分布</h2></div></div><div class="ppt-panel-body"><div class="obs-chart-frame"><canvas id="pptAuditPieChart"></canvas></div><div class="ppt-mini-grid mt-3"><div class="ppt-mini"><span class="ppt-label">Passed</span><strong class="status-good">{{ audit_30d_stats.passed }}</strong></div><div class="ppt-mini"><span class="ppt-label">Failed</span><strong class="{% if audit_30d_stats.failed > 0 %}status-warn{% endif %}">{{ audit_30d_stats.failed }}</strong></div><div class="ppt-mini"><span class="ppt-label">Error</span><strong class="{% if audit_30d_stats.error > 0 %}status-bad{% endif %}">{{ audit_30d_stats.error }}</strong></div><div class="ppt-mini"><span class="ppt-label">Confidence</span><strong>{{ "%.2f"|format(audit_30d_stats.avg_confidence) }}</strong></div></div></div></article>{% endif %}
|
||||
{% if top_failure_files %}<article class="ppt-panel"><div class="ppt-panel-head"><div><div class="ppt-label">Failure Hotspots</div><h2 class="ppt-panel-title">Top 失敗檔案</h2></div></div><div class="ppt-panel-body">{% for f in top_failure_files %}<div class="fix-card"><code>{{ f.filename }}</code><div class="d-flex justify-content-between mt-1"><small class="text-muted">{{ f.last_audit }}</small><span class="badge bg-warning">{{ f.attempts }} 次</span></div><small class="text-muted">issues {{ f.total_issues }}</small></div>{% endfor %}</div></article>{% endif %}
|
||||
</aside>
|
||||
</section>
|
||||
|
||||
@@ -83,7 +83,7 @@
|
||||
|
||||
<aside class="gate-stack">
|
||||
{% if episode_distribution_30d %}
|
||||
<article class="gate-panel"><div class="gate-panel-head"><div><div class="gate-label">Distillation Pool</div><h2 class="gate-panel-title">30 日狀態分布</h2></div></div><div class="gate-panel-body"><div style="height:260px;"><canvas id="episodeDistChart"></canvas></div></div></article>
|
||||
<article class="gate-panel"><div class="gate-panel-head"><div><div class="gate-label">Distillation Pool</div><h2 class="gate-panel-title">30 日狀態分布</h2></div></div><div class="gate-panel-body"><div class="obs-chart-frame obs-chart-frame-tall"><canvas id="episodeDistChart"></canvas></div></div></article>
|
||||
{% endif %}
|
||||
{% if strategy_weights %}
|
||||
<article class="gate-panel"><div class="gate-panel-head"><div><div class="gate-label">OpenClaw Weights</div><h2 class="gate-panel-title">策略權重 Top</h2></div></div><div class="gate-panel-body"><div class="gate-mini-grid">{% for s in strategy_weights[:6] %}<div class="gate-mini"><span class="gate-label">{{ s.strategy_key[:22] }}</span><strong>{{ "%.2f"|format(s.weight) }}</strong><small class="text-muted">成功 {{ s.success }} · 失敗 {{ s.fail }}</small></div>{% endfor %}</div></div></article>
|
||||
|
||||
@@ -22,11 +22,11 @@
|
||||
|
||||
<section class="quality-grid">
|
||||
<div class="quality-stack">
|
||||
<article class="quality-table-shell"><div class="quality-table-title"><div><div class="quality-label">Caller Feedback</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><code>{{ caller }}</code></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 style="width:200px"><div class="progress" style="height:8px"><div class="progress-bar" style="width:{{ (info.avg_score / 5 * 100)|int }}%;background:var(--obs-accent)"></div></div></td></tr>{% else %}<tr><td colspan="7" class="text-center text-muted">無反饋資料</td></tr>{% endfor %}</tbody></table></div></article>
|
||||
<article class="quality-table-shell"><div class="quality-table-title"><div><div class="quality-label">Caller Feedback</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><code>{{ caller }}</code></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">Action Plans</div><h3>Action Plans 狀態分布</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 %}">{{ a.status }}</span></td><td><code>{{ 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">RAG Feedback</div><h2 class="quality-panel-title">RAG 分數分布</h2></div></div><div class="quality-panel-body"><div style="height:250px"><canvas id="ragFeedbackPieChart"></canvas></div></div></article>{% endif %}
|
||||
{% if rag_overall_dist %}<article class="quality-panel"><div class="quality-panel-head"><div><div class="quality-label">RAG Feedback</div><h2 class="quality-panel-title">RAG 分數分布</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">Learning Pool</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">{{ status }}</span><strong>{{ cnt }}</strong></div>{% endfor %}</div></div></article>{% endif %}
|
||||
</aside>
|
||||
</section>
|
||||
|
||||
Reference in New Issue
Block a user