97 lines
10 KiB
HTML
97 lines
10 KiB
HTML
{% extends "ewoooc_base.html" %}
|
|
|
|
{% block title %}AI 成本治理艙{% endblock %}
|
|
|
|
{% block ewooo_content %}
|
|
<style>
|
|
.gov-hero, .gov-panel, .gov-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); }
|
|
.gov-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 90% 8%, rgba(184,121,47,.16), transparent 22rem), linear-gradient(135deg,rgba(255,248,239,.98),rgba(255,255,255,.74)); }
|
|
.gov-kicker { color:var(--obs-accent); font-size:.76rem; letter-spacing:.13em; text-transform:uppercase; font-weight:850; }
|
|
.gov-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; }
|
|
.gov-subtitle { color:var(--obs-muted); max-width:850px; line-height:1.7; }
|
|
.gov-actions { display:flex; flex-wrap:wrap; gap:.6rem; align-items:center; margin-top:1rem; }
|
|
.gov-command { display:grid; grid-template-columns:repeat(4,minmax(0,1fr)); gap:.75rem; margin-top:1rem; }
|
|
.gov-signal { padding:.95rem; border:1px solid var(--obs-line); border-radius:20px; background:rgba(255,255,255,.62); }
|
|
.gov-label { color:var(--obs-muted); font-size:.72rem; letter-spacing:.1em; text-transform:uppercase; }
|
|
.gov-value { display:block; margin-top:.28rem; font-size:var(--obs-value-size); font-weight:880; letter-spacing: 0; }
|
|
.gov-note { color:var(--obs-muted); font-size:.8rem; margin-top:.25rem; }
|
|
.gov-grid { display:grid; grid-template-columns:minmax(0,1.18fr) minmax(330px,.82fr); gap:1rem; margin-top:1rem; }
|
|
.gov-stack { display:grid; gap:1rem; }
|
|
.gov-panel-head, .gov-table-title { display:flex; justify-content:space-between; align-items:flex-start; gap:1rem; padding:1.05rem 1.1rem .25rem; }
|
|
.gov-panel-title, .gov-table-title h3 { margin:.15rem 0 0; font-size:1.1rem; font-weight:850; letter-spacing: 0; }
|
|
.gov-panel-body { padding:1rem 1.1rem 1.1rem; }
|
|
.gov-mini-grid { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:.7rem; }
|
|
.gov-mini { padding:.85rem; border:1px solid var(--obs-line); border-radius:18px; background:rgba(255,255,255,.58); }
|
|
.gov-mini strong { display:block; margin-top:.24rem; font-size:1.35rem; letter-spacing: 0; }
|
|
.gov-table-shell { overflow:hidden; margin-top:1rem; }
|
|
.gov-chart { height:280px; }
|
|
.strategy-card { padding:.8rem; border:1px solid var(--obs-line); border-radius:18px; background:rgba(255,255,255,.6); margin-bottom:.65rem; }
|
|
.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){ .gov-command{grid-template-columns:repeat(2,minmax(0,1fr));}.gov-grid{grid-template-columns:1fr;} }
|
|
@media (max-width:720px){ .gov-command,.gov-mini-grid{grid-template-columns:1fr;} }
|
|
</style>
|
|
|
|
{% import "admin/_observability_labels.html" as obs_label %}
|
|
{% set total_budget = namespace(value=0) %}{% set total_spent = namespace(value=0) %}{% set warn_count = namespace(value=0) %}{% set throttled_count = namespace(value=0) %}
|
|
{% for r in rows %}{% set total_budget.value = total_budget.value + (r.budget_usd or 0) %}{% set total_spent.value = total_spent.value + (r.spent or 0) %}{% if r.ratio >= 0.8 %}{% set warn_count.value = warn_count.value + 1 %}{% endif %}{% if r.throttled %}{% set throttled_count.value = throttled_count.value + 1 %}{% endif %}{% endfor %}
|
|
{% set total_ratio = (total_spent.value / total_budget.value * 100) if total_budget.value > 0 else 0 %}
|
|
|
|
<div class="container-fluid mt-3">
|
|
<section class="gov-hero">
|
|
<div class="gov-kicker"><i class="fas fa-wallet me-1"></i> AI 成本治理 · 預算 / 節流 / 知識策略</div>
|
|
<h1 class="gov-title">AI 成本治理艙</h1>
|
|
<p class="gov-subtitle">控制 AI 花費與節流狀態,把預算留給能推動業績的任務。</p>
|
|
<div class="gov-actions"><button class="btn btn-warning btn-sm" onclick="forceThrottle()"><i class="fas fa-bolt me-1"></i>立即重算節流狀態</button><span class="text-muted small">預算超線時立即重算節流。</span></div>
|
|
<div class="gov-command">
|
|
<div class="gov-signal"><div class="gov-label">當月花費</div><span class="gov-value">${{ "%.2f"|format(total_spent.value) }}</span><div class="gov-note">預算 ${{ "%.2f"|format(total_budget.value) }}</div></div>
|
|
<div class="gov-signal"><div class="gov-label">預算使用率</div><span class="gov-value {% if total_ratio >= 110 %}status-bad{% elif total_ratio >= 80 %}status-warn{% else %}status-good{% endif %}">{{ "%.0f"|format(total_ratio) }}%</span><div class="gov-note">全供應商加總</div></div>
|
|
<div class="gov-signal"><div class="gov-label">預警</div><span class="gov-value {% if warn_count.value > 0 %}status-warn{% else %}status-good{% endif %}">{{ warn_count.value }}</span><div class="gov-note">使用率 ≥ 80%</div></div>
|
|
<div class="gov-signal"><div class="gov-label">已節流</div><span class="gov-value {% if throttled_count.value > 0 %}status-bad{% else %}status-good{% endif %}">{{ throttled_count.value }}</span><div class="gov-note">已啟動成本節流</div></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="gov-grid">
|
|
<div class="gov-stack">
|
|
<article class="gov-table-shell">
|
|
<div class="gov-table-title"><div><div class="gov-label">預算線</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><span>{{ obs_label.provider(r.provider) }}</span></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">尚未建立預算線。</td></tr>{% endfor %}</tbody></table></div>
|
|
</article>
|
|
|
|
{% if cost_trend_30d %}
|
|
<article class="gov-panel"><div class="gov-panel-head"><div><div class="gov-label">30 日成本趨勢</div><h2 class="gov-panel-title">每日成本堆疊趨勢</h2></div></div><div class="gov-panel-body"><div class="gov-chart"><canvas id="costTrend30dChart"></canvas></div></div></article>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<aside class="gov-stack">
|
|
{% if provider_cost_month %}
|
|
<article class="gov-panel"><div class="gov-panel-head"><div><div class="gov-label">供應商分布</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">成本集中</div><h2 class="gov-panel-title">Top 5 成本使用情境</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"><span>{{ obs_label.caller(c.caller) }}</span><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) }} 次 · {{ "{:,}".format(c.tokens) }} 用量</small></div>{% endfor %}</div></article>
|
|
{% endif %}
|
|
</aside>
|
|
</section>
|
|
|
|
{% if budget_strategies %}
|
|
<section class="gov-panel mt-3"><div class="gov-panel-head"><div><div class="gov-label">知識策略</div><h2 class="gov-panel-title">知識策略建議</h2></div></div><div class="gov-panel-body">{% for s in budget_strategies %}<div class="strategy-card"><span class="badge bg-info me-1">{{ obs_label.insight(s.insight_type) }}</span><span class="badge bg-secondary me-1">相似度 {{ "%.2f"|format(s.similarity) }}</span><span>{{ s.content }}{% if s.content|length >= 240 %}…{% endif %}</span></div>{% endfor %}</div></section>
|
|
{% endif %}
|
|
|
|
{% if price_rec_7d %}
|
|
<section class="gov-panel mt-3"><div class="gov-panel-head"><div><div class="gov-label">商業產出</div><h2 class="gov-panel-title">AI 價格決策 7 日</h2></div></div><div class="gov-panel-body"><div class="gov-mini-grid">{% for p in price_rec_7d %}<div class="gov-mini"><span class="gov-label">{{ obs_label.strategy(p.strategy) }}</span><strong>{{ p.count }}</strong><small class="text-muted">信心 {{ "%.2f"|format(p.avg_confidence) }}</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>
|
|
|
|
{% set budget_payload = {
|
|
'providerCostMonth': provider_cost_month | default([]),
|
|
'costTrend30d': cost_trend_30d
|
|
} %}
|
|
<template id="obs-budget-data">{{ budget_payload | 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 %}
|