fix: hide runtime terms on governance pages
This commit is contained in:
@@ -402,7 +402,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '')
|
||||
# ==========================================
|
||||
# 系統版本與路徑
|
||||
# ==========================================
|
||||
SYSTEM_VERSION = "V10.697"
|
||||
SYSTEM_VERSION = "V10.698"
|
||||
LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log')
|
||||
public_url = PUBLIC_URL # 用於模板顯示
|
||||
|
||||
|
||||
@@ -774,3 +774,4 @@ POSTGRES_HOST=momo-db
|
||||
| 2026-06-25 | 匯入任務列表只顯示處置提醒 | V10.695 起 `/auto_import` 任務列表不再把 `error_message` 原文當主要欄位顯示,而是由 `buildImportActionHint()` 轉成 Google Drive 授權、當日業績明細檔、重新匯入或通知維護人員等下一步,避免重啟後瀏覽器/授權/同步技術錯誤直接暴露給營運使用者。 |
|
||||
| 2026-06-25 | 系統設定匯入提示不得顯示資料表或日誌口徑 | V10.696 起 `/system_settings` 不再用 `realtime_sales_monthly` 判斷前端提示,也不再顯示「資料落點、檢查日誌、發生系統錯誤」等內部口徑;所有匯入與備份失敗提示統一走 `toImportActionMessage()`,轉成重新授權、改用正確業績報表、重新匯入或通知維護人員。 |
|
||||
| 2026-06-25 | 分析與建議頁必須使用 PChome 作戰流程語言 | V10.697 起 `/sales_analysis`、`/monthly_summary_analysis`、`/ai_recommend` 頁首與主要操作區統一使用「主推、守價、補比價、成長缺口、毛利貢獻、品類結構」等營運語言;前台不得把 AI 模型、權杖、資料庫、欄位、英文指標縮寫或內部錯誤作為使用者主訊息。 |
|
||||
| 2026-06-25 | 治理與匯入頁也不得外露模型/權杖/欄位口徑 | V10.698 起缺貨匯入、供應商窗口、AI 歷史、預算、AI 流量、AI 分工與主機健康頁統一改用「必要資料、用量、建議引擎、建議路徑、雲端備援、AI 建議服務」等前台可讀詞,避免使用者在營運頁看到 raw model、token、欄位或模型品牌。 |
|
||||
|
||||
@@ -61,7 +61,7 @@ _PPT_INTERNAL_ERROR_MARKERS = (
|
||||
_PPT_PUBLIC_REPLACEMENTS = (
|
||||
('AiderHeal', '修復流程'),
|
||||
('RAG', '知識建議'),
|
||||
('Ollama', 'AI 模型服務'),
|
||||
('Ollama', 'AI 建議服務'),
|
||||
('minicpm-v', '視覺模型'),
|
||||
('LibreOffice', '轉檔服務'),
|
||||
('runtime', '執行條件'),
|
||||
@@ -198,10 +198,10 @@ def _build_ai_call_recent_row(row):
|
||||
if provider == 'gemini':
|
||||
if caller in _GEMINI_BACKUP_CALLER_DISPLAY:
|
||||
caller_display = _GEMINI_BACKUP_CALLER_DISPLAY[caller]
|
||||
route_badges.append('Gemini 備援')
|
||||
route_badges.append('雲端備援')
|
||||
route_badges.append('舊 caller')
|
||||
elif caller in _GEMINI_BACKUP_CALLERS or caller.endswith('_gemini'):
|
||||
route_badges.append('Gemini 備援')
|
||||
route_badges.append('雲端備援')
|
||||
else:
|
||||
route_badges.append('ADR-028 鎖定/升級')
|
||||
|
||||
|
||||
@@ -116,11 +116,11 @@
|
||||
|
||||
{% macro provider(value, fallback='未分類供應商') -%}
|
||||
{%- set labels = {
|
||||
'gcp_ollama': '主力 AI 模型',
|
||||
'ollama_secondary': '備援 AI 模型',
|
||||
'ollama_111': '第三 AI 模型',
|
||||
'gcp_ollama': '主力建議路徑',
|
||||
'ollama_secondary': '備援建議路徑',
|
||||
'ollama_111': '第三建議路徑',
|
||||
'nim_via_elephant': 'NIM Elephant',
|
||||
'gemini': 'Gemini',
|
||||
'gemini': '雲端備援',
|
||||
'claude': 'Claude',
|
||||
'nim': 'NIM',
|
||||
'openrouter': 'OpenRouter',
|
||||
|
||||
@@ -9,15 +9,15 @@
|
||||
</style>
|
||||
|
||||
<div class="container-fluid mt-3">
|
||||
<section class="agent-hero"><div class="agent-kicker"><i class="fas fa-network-wired me-1"></i> AI 分工指揮台 · {{ hours }} 小時視窗</div><h1 class="agent-title">AI 分工指揮台</h1><p class="agent-subtitle">確認 AI 分工、本地模型、知識命中與工具編排是否支撐業績決策。</p><form method="get" class="agent-filter"><select name="hours" class="form-select form-select-sm" onchange="this.form.submit()">{% for h in [1,6,24,72,168] %}<option value="{{ h }}" {% if hours == h %}selected{% endif %}>{% if h < 24 %}過去 {{ h }} 小時{% else %}過去 {{ h//24 }} 天{% endif %}</option>{% endfor %}</select></form>{% if overall %}<div class="agent-command"><div class="agent-signal"><div class="agent-label">呼叫總量</div><span class="agent-value">{{ "{:,}".format(overall.total_calls) }}</span><small class="text-muted">{{ "{:,}".format(overall.total_tokens) }} 用量</small></div><div class="agent-signal"><div class="agent-label">本地模型占比</div><span class="agent-value status-good">{{ "%.0f"|format(overall.local_pct) }}%</span><small class="text-muted">{{ "{:,}".format(overall.local_calls) }} 次本地呼叫</small></div><div class="agent-signal"><div class="agent-label">付費成本</div><span class="agent-value {% if overall.total_cost > 0 %}status-warn{% else %}status-good{% endif %}">${{ "%.2f"|format(overall.total_cost) }}</span><small class="text-muted">{{ "{:,}".format(overall.paid_calls) }} 次付費呼叫</small></div><div class="agent-signal"><div class="agent-label">知識命中率</div><span class="agent-value status-blue">{{ "%.0f"|format(overall.rag_rate) }}%</span><small class="text-muted">{{ "{:,}".format(overall.rag_hits) }} 次命中</small></div></div>{% endif %}</section>
|
||||
<section class="agent-hero"><div class="agent-kicker"><i class="fas fa-network-wired me-1"></i> AI 分工指揮台 · {{ hours }} 小時視窗</div><h1 class="agent-title">AI 分工指揮台</h1><p class="agent-subtitle">確認 AI 分工、建議路徑、知識命中與工具編排是否支撐業績決策。</p><form method="get" class="agent-filter"><select name="hours" class="form-select form-select-sm" onchange="this.form.submit()">{% for h in [1,6,24,72,168] %}<option value="{{ h }}" {% if hours == h %}selected{% endif %}>{% if h < 24 %}過去 {{ h }} 小時{% else %}過去 {{ h//24 }} 天{% endif %}</option>{% endfor %}</select></form>{% if overall %}<div class="agent-command"><div class="agent-signal"><div class="agent-label">呼叫總量</div><span class="agent-value">{{ "{:,}".format(overall.total_calls) }}</span><small class="text-muted">{{ "{:,}".format(overall.total_tokens) }} 用量</small></div><div class="agent-signal"><div class="agent-label">主力路徑占比</div><span class="agent-value status-good">{{ "%.0f"|format(overall.local_pct) }}%</span><small class="text-muted">{{ "{:,}".format(overall.local_calls) }} 次主力呼叫</small></div><div class="agent-signal"><div class="agent-label">付費成本</div><span class="agent-value {% if overall.total_cost > 0 %}status-warn{% else %}status-good{% endif %}">${{ "%.2f"|format(overall.total_cost) }}</span><small class="text-muted">{{ "{:,}".format(overall.paid_calls) }} 次付費呼叫</small></div><div class="agent-signal"><div class="agent-label">知識命中率</div><span class="agent-value status-blue">{{ "%.0f"|format(overall.rag_rate) }}%</span><small class="text-muted">{{ "{:,}".format(overall.rag_hits) }} 次命中</small></div></div>{% endif %}</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="agent-grid">
|
||||
<div class="agent-stack">
|
||||
<article class="agent-table-shell"><div class="agent-table-title"><div><div class="agent-label">AI 分工矩陣</div><h3>模型、工具與知識命中矩陣</h3></div></div><div class="table-responsive"><table class="table 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 class="text-end">工具</th><th class="text-end">知識</th><th class="text-end">錯誤</th><th class="text-end">耗時</th></tr></thead><tbody>{% for ag in agent_matrix %}<tr><td><strong>{{ ag.label }}</strong><small class="d-block text-muted">{{ ag.desc }}</small></td><td class="text-end">{% if ag.calls > 0 %}<strong>{{ "{:,}".format(ag.calls) }}</strong><small class="d-block text-muted">{{ "{:,}".format(ag.tokens) }} 用量</small>{% else %}<small class="text-muted">—</small>{% endif %}</td><td class="text-end">{% if ag.calls > 0 %}${{ "%.2f"|format(ag.cost) }}{% else %}<small class="text-muted">—</small>{% endif %}</td><td class="text-end">{% if ag.calls > 0 %}<strong class="status-good">{{ "%.0f"|format(ag.ollama_pct) }}%</strong><small class="d-block text-muted">主力 {{ ag.ollama_gcp_a }} · 備援 {{ ag.ollama_gcp_b }} · 第三 {{ ag.ollama_111 }}</small>{% else %}<small class="text-muted">—</small>{% endif %}</td><td class="text-end">{% if ag.calls > 0 %}<strong class="{% if ag.paid_pct > 50 %}status-bad{% elif ag.paid_pct > 20 %}status-warn{% endif %}">{{ "%.0f"|format(ag.paid_pct) }}%</strong><small class="d-block text-muted">Gemini {{ ag.gemini }}{% if ag.other_paid %} · 其他 {{ ag.other_paid }}{% endif %}</small>{% else %}<small class="text-muted">—</small>{% endif %}</td><td class="text-end">{% if ag.calls > 0 %}<strong class="{% if ag.mcp_rate >= 30 %}status-blue{% elif ag.mcp_rate >= 10 %}status-warn{% else %}text-muted{% endif %}">{{ "%.1f"|format(ag.mcp_rate) }}%</strong><small class="d-block text-muted">{{ ag.mcp_calls }}</small>{% else %}<small class="text-muted">—</small>{% endif %}</td><td class="text-end">{% if ag.calls > 0 %}<strong class="status-blue">{{ "%.1f"|format(ag.rag_rate) }}%</strong><small class="d-block text-muted">{{ ag.rag_hits }}</small>{% else %}<small class="text-muted">—</small>{% endif %}</td><td class="text-end">{% if ag.calls > 0 %}<strong class="{% if ag.error_rate >= 15 %}status-bad{% elif ag.error_rate >= 5 %}status-warn{% else %}status-good{% endif %}">{{ "%.1f"|format(ag.error_rate) }}%</strong><small class="d-block text-muted">{{ ag.errors }}</small>{% else %}<small class="text-muted">—</small>{% endif %}</td><td class="text-end">{% if ag.calls > 0 %}{{ ag.avg_ms }} ms{% else %}<small class="text-muted">—</small>{% endif %}</td></tr>{% endfor %}</tbody></table></div></article>
|
||||
<article class="agent-table-shell"><div class="agent-table-title"><div><div class="agent-label">AI 分工矩陣</div><h3>建議路徑、工具與知識命中矩陣</h3></div></div><div class="table-responsive"><table class="table 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 class="text-end">工具</th><th class="text-end">知識</th><th class="text-end">錯誤</th><th class="text-end">耗時</th></tr></thead><tbody>{% for ag in agent_matrix %}<tr><td><strong>{{ ag.label }}</strong><small class="d-block text-muted">{{ ag.desc }}</small></td><td class="text-end">{% if ag.calls > 0 %}<strong>{{ "{:,}".format(ag.calls) }}</strong><small class="d-block text-muted">{{ "{:,}".format(ag.tokens) }} 用量</small>{% else %}<small class="text-muted">—</small>{% endif %}</td><td class="text-end">{% if ag.calls > 0 %}${{ "%.2f"|format(ag.cost) }}{% else %}<small class="text-muted">—</small>{% endif %}</td><td class="text-end">{% if ag.calls > 0 %}<strong class="status-good">{{ "%.0f"|format(ag.ollama_pct) }}%</strong><small class="d-block text-muted">主力 {{ ag.ollama_gcp_a }} · 備援 {{ ag.ollama_gcp_b }} · 第三 {{ ag.ollama_111 }}</small>{% else %}<small class="text-muted">—</small>{% endif %}</td><td class="text-end">{% if ag.calls > 0 %}<strong class="{% if ag.paid_pct > 50 %}status-bad{% elif ag.paid_pct > 20 %}status-warn{% endif %}">{{ "%.0f"|format(ag.paid_pct) }}%</strong><small class="d-block text-muted">雲端備援 {{ ag.gemini }}{% if ag.other_paid %} · 其他 {{ ag.other_paid }}{% endif %}</small>{% else %}<small class="text-muted">—</small>{% endif %}</td><td class="text-end">{% if ag.calls > 0 %}<strong class="{% if ag.mcp_rate >= 30 %}status-blue{% elif ag.mcp_rate >= 10 %}status-warn{% else %}text-muted{% endif %}">{{ "%.1f"|format(ag.mcp_rate) }}%</strong><small class="d-block text-muted">{{ ag.mcp_calls }}</small>{% else %}<small class="text-muted">—</small>{% endif %}</td><td class="text-end">{% if ag.calls > 0 %}<strong class="status-blue">{{ "%.1f"|format(ag.rag_rate) }}%</strong><small class="d-block text-muted">{{ ag.rag_hits }}</small>{% else %}<small class="text-muted">—</small>{% endif %}</td><td class="text-end">{% if ag.calls > 0 %}<strong class="{% if ag.error_rate >= 15 %}status-bad{% elif ag.error_rate >= 5 %}status-warn{% else %}status-good{% endif %}">{{ "%.1f"|format(ag.error_rate) }}%</strong><small class="d-block text-muted">{{ ag.errors }}</small>{% else %}<small class="text-muted">—</small>{% endif %}</td><td class="text-end">{% if ag.calls > 0 %}{{ ag.avg_ms }} ms{% else %}<small class="text-muted">—</small>{% endif %}</td></tr>{% endfor %}</tbody></table></div></article>
|
||||
</div>
|
||||
<aside class="agent-stack">
|
||||
<article class="agent-panel"><div class="agent-panel-head"><div><div class="agent-label">分工卡片</div><h2 class="agent-panel-title">分工健康速覽</h2></div></div><div class="agent-panel-body">{% for ag in agent_matrix %}<div class="agent-card"><div class="agent-card-top"><div><strong>{{ ag.label }}</strong><small class="d-block text-muted">{{ ag.desc }}</small></div><span class="badge {% if ag.error_rate >= 15 %}bg-danger{% elif ag.calls == 0 %}bg-secondary{% else %}bg-success{% endif %}">{{ ag.calls }} 次呼叫</span></div><div class="agent-meter"><span style="width:{{ ag.ollama_pct|round|int if ag.calls > 0 else 0 }}%"></span></div><small class="text-muted">本地模型 {{ "%.0f"|format(ag.ollama_pct) if ag.calls > 0 else 0 }}% · 知識 {{ "%.0f"|format(ag.rag_rate) if ag.calls > 0 else 0 }}% · 工具 {{ "%.0f"|format(ag.mcp_rate) if ag.calls > 0 else 0 }}%</small></div>{% endfor %}</div></article>
|
||||
<article class="agent-panel"><div class="agent-panel-head"><div><div class="agent-label">分工卡片</div><h2 class="agent-panel-title">分工健康速覽</h2></div></div><div class="agent-panel-body">{% for ag in agent_matrix %}<div class="agent-card"><div class="agent-card-top"><div><strong>{{ ag.label }}</strong><small class="d-block text-muted">{{ ag.desc }}</small></div><span class="badge {% if ag.error_rate >= 15 %}bg-danger{% elif ag.calls == 0 %}bg-secondary{% else %}bg-success{% endif %}">{{ ag.calls }} 次呼叫</span></div><div class="agent-meter"><span style="width:{{ ag.ollama_pct|round|int if ag.calls > 0 else 0 }}%"></span></div><small class="text-muted">主力路徑 {{ "%.0f"|format(ag.ollama_pct) if ag.calls > 0 else 0 }}% · 知識 {{ "%.0f"|format(ag.rag_rate) if ag.calls > 0 else 0 }}% · 工具 {{ "%.0f"|format(ag.mcp_rate) if ag.calls > 0 else 0 }}%</small></div>{% endfor %}</div></article>
|
||||
</aside>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -118,14 +118,14 @@
|
||||
|
||||
{% if by_model %}
|
||||
<section class="calls-table-shell">
|
||||
<div class="calls-table-title"><div><div class="calls-label">模型成本</div><h3>依模型細分</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><th class="text-end">用量</th><th class="text-end">成本</th><th class="text-end">耗時</th><th class="text-end">錯誤</th></tr></thead><tbody>{% for m in by_model %}<tr><td><code>{{ m.model[:35] }}</code></td><td><span class="badge bg-secondary">{{ obs_label.provider(m.provider) }}</span></td><td class="text-end">{{ "{:,}".format(m.calls) }}</td><td class="text-end">{{ "{:,}".format(m.tokens) }}</td><td class="text-end">${{ "%.4f"|format(m.cost) }}</td><td class="text-end">{{ m.avg_ms }} ms</td><td class="text-end">{% if m.errors > 0 %}<span class="status-bad">{{ m.errors }}</span>{% else %}<small class="text-muted">0</small>{% endif %}</td></tr>{% endfor %}</tbody></table></div>
|
||||
<div class="calls-table-title"><div><div class="calls-label">路徑成本</div><h3>依建議路徑細分</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><th class="text-end">用量</th><th class="text-end">成本</th><th class="text-end">耗時</th><th class="text-end">錯誤</th></tr></thead><tbody>{% for m in by_model %}<tr><td><span class="badge bg-secondary">{{ obs_label.provider(m.provider) }}</span></td><td><span class="badge bg-secondary">{{ obs_label.provider(m.provider) }}</span></td><td class="text-end">{{ "{:,}".format(m.calls) }}</td><td class="text-end">{{ "{:,}".format(m.tokens) }}</td><td class="text-end">${{ "%.4f"|format(m.cost) }}</td><td class="text-end">{{ m.avg_ms }} ms</td><td class="text-end">{% if m.errors > 0 %}<span class="status-bad">{{ m.errors }}</span>{% else %}<small class="text-muted">0</small>{% endif %}</td></tr>{% endfor %}</tbody></table></div>
|
||||
</section>
|
||||
{% endif %}
|
||||
|
||||
<section class="calls-table-shell">
|
||||
<div class="calls-table-title"><div><div class="calls-label">最近呼叫</div><h3>最近呼叫 100 筆</h3></div></div>
|
||||
<div class="table-responsive"><table class="table table-sm table-striped mb-0"><thead class="table-light"><tr><th>編號</th><th>時間</th><th>呼叫端</th><th>供應商</th><th>模型</th><th class="text-end">輸入</th><th class="text-end">輸出</th><th class="text-end">耗時</th><th>狀態</th><th class="text-end">成本</th><th>標記</th></tr></thead><tbody>{% for r in recent %}<tr {% if r.status not in ['ok','cache_only'] %}class="table-warning"{% endif %}><td>{{ r.id }}</td><td><small>{{ r.called_at }}</small></td><td><code>{{ r.caller_display or r.caller }}</code>{% if r.caller_display and r.caller_display != r.caller %}<br><small class="text-muted">原始:{{ r.caller }}</small>{% endif %}</td><td><small>{{ obs_label.provider(r.provider) }}</small></td><td><small>{{ r.model[:25] }}</small></td><td class="text-end">{{ r.in_tokens }}</td><td class="text-end">{{ r.out_tokens }}</td><td class="text-end">{{ r.duration_ms }}</td><td><small>{{ obs_label.status(r.status, '-') }}</small></td><td class="text-end">${{ "%.4f"|format(r.cost) }}</td><td>{% for badge in r.route_badges %}<span class="badge bg-secondary me-1">{{ badge }}</span>{% endfor %}{% if r.cache_hit %}<span class="badge bg-success">快取</span>{% endif %}{% if r.rag_hit %}<span class="badge bg-info">知識命中</span>{% endif %}</td></tr>{% endfor %}</tbody></table></div>
|
||||
<div class="table-responsive"><table class="table table-sm table-striped mb-0"><thead class="table-light"><tr><th>編號</th><th>時間</th><th>呼叫端</th><th>供應商</th><th>建議路徑</th><th class="text-end">輸入</th><th class="text-end">輸出</th><th class="text-end">耗時</th><th>狀態</th><th class="text-end">成本</th><th>標記</th></tr></thead><tbody>{% for r in recent %}<tr {% if r.status not in ['ok','cache_only'] %}class="table-warning"{% endif %}><td>{{ r.id }}</td><td><small>{{ r.called_at }}</small></td><td><code>{{ r.caller_display or r.caller }}</code>{% if r.caller_display and r.caller_display != r.caller %}<br><small class="text-muted">原始:{{ r.caller }}</small>{% endif %}</td><td><small>{{ obs_label.provider(r.provider) }}</small></td><td><small>{{ obs_label.provider(r.provider) }}</small></td><td class="text-end">{{ r.in_tokens }}</td><td class="text-end">{{ r.out_tokens }}</td><td class="text-end">{{ r.duration_ms }}</td><td><small>{{ obs_label.status(r.status, '-') }}</small></td><td class="text-end">${{ "%.4f"|format(r.cost) }}</td><td>{% for badge in r.route_badges %}<span class="badge bg-secondary me-1">{{ badge }}</span>{% endfor %}{% if r.cache_hit %}<span class="badge bg-success">快取</span>{% endif %}{% if r.rag_hit %}<span class="badge bg-info">知識命中</span>{% endif %}</td></tr>{% endfor %}</tbody></table></div>
|
||||
</section>
|
||||
|
||||
<p class="text-muted mt-3"><small><i class="fas fa-robot me-1"></i>AI 流量控制塔</small></p>
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
{% 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"><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) }} 次呼叫 · {{ "{:,}".format(c.tokens) }} 權杖</small></div>{% endfor %}</div></article>
|
||||
<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"><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) }} 次呼叫 · {{ "{:,}".format(c.tokens) }} 用量</small></div>{% endfor %}</div></article>
|
||||
{% endif %}
|
||||
</aside>
|
||||
</section>
|
||||
|
||||
@@ -59,11 +59,11 @@
|
||||
|
||||
<div class="container-fluid mt-3">
|
||||
<section class="runtime-hero">
|
||||
<div class="runtime-kicker"><i class="fas fa-heartbeat me-1"></i> 基礎設施生命線 · AI 模型 / 工具服務 / 自癒</div>
|
||||
<div class="runtime-kicker"><i class="fas fa-heartbeat me-1"></i> 基礎設施生命線 · AI 建議服務 / 工具服務 / 自癒</div>
|
||||
<h1 class="runtime-title">基礎設施生命線</h1>
|
||||
<p class="runtime-subtitle">先看 AI 模型、工具服務與自癒是否正常,避免 AI 建議與比價流程中斷。</p>
|
||||
<p class="runtime-subtitle">先看 AI 建議服務、工具服務與自癒是否正常,避免 AI 建議與比價流程中斷。</p>
|
||||
<div class="runtime-command">
|
||||
<div class="runtime-signal"><div class="runtime-label">AI 模型離線</div><span class="runtime-value {% if down.count > 0 %}status-bad{% else %}status-good{% endif %}">{{ down.count }}</span><small class="text-muted">{{ ollama_hosts|length }} 台即時探測</small></div>
|
||||
<div class="runtime-signal"><div class="runtime-label">建議服務離線</div><span class="runtime-value {% if down.count > 0 %}status-bad{% else %}status-good{% endif %}">{{ down.count }}</span><small class="text-muted">{{ ollama_hosts|length }} 台即時探測</small></div>
|
||||
<div class="runtime-signal"><div class="runtime-label">AIOps 未解</div><span class="runtime-value {% if aiops_summary and aiops_summary.incidents_open > 0 %}status-bad{% else %}status-good{% endif %}">{{ aiops_summary.incidents_open if aiops_summary else '—' }}</span><small class="text-muted">7 日事件未解決</small></div>
|
||||
<div class="runtime-signal"><div class="runtime-label">自癒成功率</div><span class="runtime-value {% if aiops_summary and aiops_summary.heal_success_rate >= 80 %}status-good{% elif aiops_summary and aiops_summary.heal_success_rate >= 50 %}status-warn{% else %}status-bad{% endif %}">{{ "%.0f"|format(aiops_summary.heal_success_rate) if aiops_summary else '—' }}{% if aiops_summary %}%{% endif %}</span><small class="text-muted">近 7 日自癒成功率</small></div>
|
||||
<div class="runtime-signal"><div class="runtime-label">節流供應商</div><span class="runtime-value {% if throttled.count > 0 %}status-warn{% else %}status-good{% endif %}">{{ throttled.count }}</span><small class="text-muted">成本節流供應商</small></div>
|
||||
@@ -74,7 +74,7 @@
|
||||
<div class="runtime-stack">
|
||||
<article class="runtime-panel">
|
||||
<div class="runtime-panel-head">
|
||||
<div><div class="runtime-label">模型服務</div><h2 class="runtime-panel-title">AI 模型主機</h2></div>
|
||||
<div><div class="runtime-label">建議服務</div><h2 class="runtime-panel-title">AI 建議服務主機</h2></div>
|
||||
<span class="badge {% if down.count > 0 %}bg-danger{% else %}bg-success{% endif %}">{{ '需要處理' if down.count > 0 else '全部在線' }}</span>
|
||||
</div>
|
||||
<div class="runtime-panel-body">
|
||||
@@ -157,11 +157,11 @@
|
||||
|
||||
<section class="runtime-main">
|
||||
<div class="runtime-stack">
|
||||
<article class="runtime-table-shell"><div class="runtime-table-title"><div><div class="runtime-label">事件紀錄</div><h3>最近 10 筆事件</h3></div></div><div class="table-responsive">{% if recent_incidents %}{% set task_labels = {'ElephantAlphaAutonomousEngine': 'AI 自癒監控', 'run_icaim_analysis_task': '市場分析任務'} %}{% set error_labels = {'ollama_unhealthy': 'AI 模型主機不穩', 'scheduler_task_failure': '排程任務異常', 'crawler_timeout': '資料擷取逾時', 'python_exception': '程式例外'} %}<table class="table table-sm mb-0"><thead class="table-light"><tr><th>時間</th><th>任務</th><th>問題</th><th>等級</th><th>狀態</th><th>處置提醒</th></tr></thead><tbody>{% for i in recent_incidents %}<tr><td><small>{{ i.created_at }}</small></td><td><span>{{ task_labels.get(i.task_name, '系統任務') }}</span></td><td><span class="badge bg-secondary">{{ error_labels.get(i.error_type, '系統異常') }}</span></td><td><span class="badge {% if i.severity in ('P0','P1') %}bg-danger{% elif i.severity == 'P2' %}bg-warning{% else %}bg-info{% endif %}">{{ i.severity }}</span></td><td>{{ i.status }}</td><td><small class="text-muted">{% if i.error_type == 'ollama_unhealthy' %}AI 模型主機暫時不穩,已進入自癒監控。{% elif i.error_type == 'python_exception' %}資料流程發生程式例外,需由修復流程處理。{% elif i.error_type == 'scheduler_task_failure' %}排程任務異常,需確認下次執行是否恢復。{% else %}系統事件已記錄,請依狀態追蹤後續是否恢復。{% endif %}</small></td></tr>{% endfor %}</tbody></table>{% else %}<div class="text-muted text-center p-3 small">尚無事件紀錄</div>{% endif %}</div></article>
|
||||
<article class="runtime-table-shell"><div class="runtime-table-title"><div><div class="runtime-label">事件紀錄</div><h3>最近 10 筆事件</h3></div></div><div class="table-responsive">{% if recent_incidents %}{% set task_labels = {'ElephantAlphaAutonomousEngine': 'AI 自癒監控', 'run_icaim_analysis_task': '市場分析任務'} %}{% set error_labels = {'ollama_unhealthy': 'AI 建議服務不穩', 'scheduler_task_failure': '排程任務異常', 'crawler_timeout': '資料擷取逾時', 'python_exception': '程式例外'} %}<table class="table table-sm mb-0"><thead class="table-light"><tr><th>時間</th><th>任務</th><th>問題</th><th>等級</th><th>狀態</th><th>處置提醒</th></tr></thead><tbody>{% for i in recent_incidents %}<tr><td><small>{{ i.created_at }}</small></td><td><span>{{ task_labels.get(i.task_name, '系統任務') }}</span></td><td><span class="badge bg-secondary">{{ error_labels.get(i.error_type, '系統異常') }}</span></td><td><span class="badge {% if i.severity in ('P0','P1') %}bg-danger{% elif i.severity == 'P2' %}bg-warning{% else %}bg-info{% endif %}">{{ i.severity }}</span></td><td>{{ i.status }}</td><td><small class="text-muted">{% if i.error_type == 'ollama_unhealthy' %}AI 建議服務暫時不穩,已進入自癒監控。{% elif i.error_type == 'python_exception' %}資料流程發生程式例外,需由修復流程處理。{% elif i.error_type == 'scheduler_task_failure' %}排程任務異常,需確認下次執行是否恢復。{% else %}系統事件已記錄,請依狀態追蹤後續是否恢復。{% endif %}</small></td></tr>{% endfor %}</tbody></table>{% else %}<div class="text-muted text-center p-3 small">尚無事件紀錄</div>{% endif %}</div></article>
|
||||
<article class="runtime-table-shell"><div class="runtime-table-title"><div><div class="runtime-label">自癒紀錄</div><h3>最近 10 筆自癒</h3></div></div><div class="table-responsive">{% if recent_heals %}<table class="table table-sm mb-0"><thead class="table-light"><tr><th>時間</th><th>動作</th><th>結果</th><th class="text-end">耗時</th><th>細節</th></tr></thead><tbody>{% for h in recent_heals %}<tr><td><small>{{ h.created_at }}</small></td><td><span class="badge bg-info">{{ h.action_type or '—' }}</span></td><td>{% if h.result == 'success' %}<span class="badge bg-success">成功</span>{% elif h.result == 'failed' %}<span class="badge bg-danger">失敗</span>{% else %}<span class="badge bg-secondary">{{ h.result }}</span>{% endif %}</td><td class="text-end">{{ h.duration_ms }} ms</td><td><small class="text-muted">{{ h.action_detail }}</small></td></tr>{% endfor %}</tbody></table>{% else %}<div class="text-muted text-center p-3 small">尚無自癒紀錄</div>{% endif %}</div></article>
|
||||
</div>
|
||||
<aside class="runtime-stack">
|
||||
<article class="runtime-table-shell"><div class="runtime-table-title"><div><div class="runtime-label">自癒劇本</div><h3>自癒劇本</h3></div></div><div class="table-responsive">{% if playbook_ranking %}{% set playbook_error_labels = {'python_exception': '程式例外', 'scheduler_task_failure': '排程異常', 'ollama_unhealthy': 'AI 模型主機不穩'} %}{% set playbook_action_labels = {'CODE_FIX': '程式修復', 'SERVICE_RESTART': '服務重啟', 'ALERT_ONLY': '告警追蹤'} %}<table class="table table-sm mb-0"><thead class="table-light"><tr><th>名稱</th><th>成功率</th><th>狀態</th><th>切換</th></tr></thead><tbody>{% for p in playbook_ranking %}<tr><td><strong>{{ p.name }}</strong><br><small class="text-muted">{{ playbook_error_labels.get(p.error_type, '系統異常') }} · {{ playbook_action_labels.get(p.action_type, '自癒處理') }}</small></td><td>{% if (p.success + p.fail) > 0 %}<strong>{{ "%.0f"|format(p.success_rate) }}%</strong>{% else %}<span class="text-muted">—</span>{% endif %}</td><td>{% if p.is_active %}<span class="badge bg-success">啟用</span>{% else %}<span class="badge bg-secondary">停用</span>{% endif %}</td><td><button class="btn btn-sm btn-outline-secondary" onclick="togglePlaybook({{ p.id }}, {{ p.name|tojson }})">切換</button></td></tr>{% endfor %}</tbody></table>{% else %}<div class="text-muted text-center p-3 small">尚無劇本資料</div>{% endif %}</div></article>
|
||||
<article class="runtime-table-shell"><div class="runtime-table-title"><div><div class="runtime-label">自癒劇本</div><h3>自癒劇本</h3></div></div><div class="table-responsive">{% if playbook_ranking %}{% set playbook_error_labels = {'python_exception': '程式例外', 'scheduler_task_failure': '排程異常', 'ollama_unhealthy': 'AI 建議服務不穩'} %}{% set playbook_action_labels = {'CODE_FIX': '程式修復', 'SERVICE_RESTART': '服務重啟', 'ALERT_ONLY': '告警追蹤'} %}<table class="table table-sm mb-0"><thead class="table-light"><tr><th>名稱</th><th>成功率</th><th>狀態</th><th>切換</th></tr></thead><tbody>{% for p in playbook_ranking %}<tr><td><strong>{{ p.name }}</strong><br><small class="text-muted">{{ playbook_error_labels.get(p.error_type, '系統異常') }} · {{ playbook_action_labels.get(p.action_type, '自癒處理') }}</small></td><td>{% if (p.success + p.fail) > 0 %}<strong>{{ "%.0f"|format(p.success_rate) }}%</strong>{% else %}<span class="text-muted">—</span>{% endif %}</td><td>{% if p.is_active %}<span class="badge bg-success">啟用</span>{% else %}<span class="badge bg-secondary">停用</span>{% endif %}</td><td><button class="btn btn-sm btn-outline-secondary" onclick="togglePlaybook({{ p.id }}, {{ p.name|tojson }})">切換</button></td></tr>{% endfor %}</tbody></table>{% else %}<div class="text-muted text-center p-3 small">尚無劇本資料</div>{% endif %}</div></article>
|
||||
<article class="runtime-table-shell"><div class="runtime-table-title"><div><div class="runtime-label">備份</div><h3>備份歷史 7 日</h3></div></div><div class="table-responsive">{% if backup_history %}<table class="table table-sm mb-0"><thead class="table-light"><tr><th>時間</th><th>狀態</th><th class="text-end">MB</th></tr></thead><tbody>{% for b in backup_history %}<tr><td><small>{{ b.created_at }}</small></td><td>{% if b.status == 'success' %}<span class="badge bg-success">成功</span>{% else %}<span class="badge bg-danger">{{ b.status }}</span>{% endif %}</td><td class="text-end">{{ b.size_mb }}</td></tr>{% endfor %}</tbody></table>{% else %}<div class="text-muted text-center p-3 small">過去 7 日無備份紀錄</div>{% endif %}</div></article>
|
||||
</aside>
|
||||
</section>
|
||||
|
||||
@@ -564,7 +564,7 @@ function renderHistory(items) {
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div class="small text-muted">
|
||||
<span class="me-3"><i class="fas fa-microchip me-1"></i>${item.model_name || '-'}</span>
|
||||
<span class="me-3"><i class="fas fa-wand-magic-sparkles me-1"></i>建議引擎</span>
|
||||
${item.generation_duration ? `<span><i class="fas fa-stopwatch me-1"></i>${item.generation_duration}秒</span>` : ''}
|
||||
${item.input_style ? `<span class="ms-3"><i class="fas fa-palette me-1"></i>${item.input_style}</span>` : ''}
|
||||
</div>
|
||||
|
||||
@@ -54,13 +54,13 @@
|
||||
|
||||
<aside class="stockout-import-side">
|
||||
<div class="stockout-import-card">
|
||||
<div class="stockout-card-label momo-mono">必要欄位</div>
|
||||
<div class="stockout-card-label momo-mono">必要資料</div>
|
||||
<div class="stockout-card-title">來源供應商編號、來源供應商名稱、商品ID、商品名稱</div>
|
||||
<div class="stockout-card-meta momo-mono">缺少必要欄位時,系統會拒絕匯入並回傳錯誤原因。</div>
|
||||
<div class="stockout-card-meta momo-mono">缺少必要資料時,系統會拒絕匯入並回傳錯誤原因。</div>
|
||||
</div>
|
||||
<div class="stockout-import-card">
|
||||
<div class="stockout-card-label momo-mono">欄位範本</div>
|
||||
<div class="stockout-card-title">下載只有正式欄位標題的空白範本</div>
|
||||
<div class="stockout-card-label momo-mono">匯入範本</div>
|
||||
<div class="stockout-card-title">下載正式格式的空白範本</div>
|
||||
<div class="stockout-card-meta">
|
||||
<a class="stockout-action" href="{{ url_for('vendor.api_import_template') }}">
|
||||
<i class="fas fa-download"></i>下載範本
|
||||
|
||||
@@ -145,8 +145,8 @@
|
||||
<h4 class="mb-3">匯入供應商窗口名單</h4>
|
||||
<p class="text-muted">上傳 Excel,讓缺貨通知送到正確窗口。</p>
|
||||
<p class="text-muted small">
|
||||
<strong>必要欄位:</strong>來源供應商編號、來源供應商名稱<br>
|
||||
<strong>選填欄位:</strong>Mail(支援多個郵件用逗號或分號分隔)
|
||||
<strong>必要資料:</strong>來源供應商編號、來源供應商名稱<br>
|
||||
<strong>選填資料:</strong>Mail(支援多個郵件用逗號或分號分隔)
|
||||
</p>
|
||||
<button type="button" class="btn btn-primary btn-lg mt-3" id="selectFileBtn" style="background: var(--momo-page-accent-dark); border-color: var(--momo-page-accent-dark);">
|
||||
<i class="fas fa-folder-open me-2"></i>選擇檔案
|
||||
@@ -549,7 +549,7 @@
|
||||
const emailsText = document.getElementById('vendorEmails').value;
|
||||
|
||||
if (!vendorCode || !vendorName) {
|
||||
alert('廠商代碼和名稱為必填欄位!');
|
||||
alert('廠商代碼和名稱必須填寫!');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -123,7 +123,7 @@ def test_ai_calls_recent_row_marks_legacy_code_review_gemini_as_backup():
|
||||
assert data['caller'] == 'code_review_openclaw'
|
||||
assert data['caller_display'] == 'code_review_openclaw_gemini'
|
||||
assert data['provider'] == 'gemini'
|
||||
assert 'Gemini 備援' in data['route_badges']
|
||||
assert '雲端備援' in data['route_badges']
|
||||
assert '舊 caller' in data['route_badges']
|
||||
|
||||
|
||||
@@ -150,7 +150,7 @@ def test_ai_calls_recent_row_marks_legacy_openclaw_qa_gemini_as_backup():
|
||||
assert data['caller'] == 'openclaw_qa'
|
||||
assert data['caller_display'] == 'openclaw_qa_gemini_fallback'
|
||||
assert data['provider'] == 'gemini'
|
||||
assert 'Gemini 備援' in data['route_badges']
|
||||
assert '雲端備援' in data['route_badges']
|
||||
assert '舊 caller' in data['route_badges']
|
||||
|
||||
|
||||
@@ -175,7 +175,7 @@ def test_ai_calls_recent_row_marks_new_openclaw_qa_gemini_fallback_as_backup():
|
||||
data = _build_ai_call_recent_row(row)
|
||||
|
||||
assert data['caller_display'] == 'openclaw_qa_gemini_fallback'
|
||||
assert 'Gemini 備援' in data['route_badges']
|
||||
assert '雲端備援' in data['route_badges']
|
||||
assert '舊 caller' not in data['route_badges']
|
||||
|
||||
|
||||
|
||||
@@ -965,7 +965,7 @@ def test_visible_operations_pages_hide_internal_runtime_terms():
|
||||
"templates/vendor_stockout_vendor_management_v2.html": ["匯入供應商窗口名單", "確認窗口清單"],
|
||||
"templates/vendor_stockout_import_v2.html": ["系統會拒絕匯入", "處理缺貨清單"],
|
||||
"templates/admin/ppt_audit_history.html": ["產出紀錄", "最近產出", "保存紀錄"],
|
||||
"templates/admin/agent_orchestration.html": ["AI 分工指揮台", "模型、工具與知識命中矩陣", "工具服務明細"],
|
||||
"templates/admin/agent_orchestration.html": ["AI 分工指揮台", "建議路徑、工具與知識命中矩陣", "工具服務明細"],
|
||||
"templates/admin/ai_calls_dashboard.html": ["用量", "AI 上下文", "知識與工具編排矩陣"],
|
||||
"templates/admin/observability_overview.html": ["用量", "知識與工具矩陣"],
|
||||
"templates/cicd_dashboard.html": ["部署流程", "部署歷史", "修復部署", "查看部署紀錄"],
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
'use strict';
|
||||
|
||||
const providerLabelMap = {
|
||||
gcp_ollama: '主力 AI 模型',
|
||||
ollama_secondary: '備援 AI 模型',
|
||||
ollama_111: '第三 AI 模型',
|
||||
gemini: 'Gemini',
|
||||
gcp_ollama: '主力建議路徑',
|
||||
ollama_secondary: '備援建議路徑',
|
||||
ollama_111: '第三建議路徑',
|
||||
gemini: '雲端備援',
|
||||
claude: 'Claude',
|
||||
nim: 'NIM',
|
||||
openrouter: 'OpenRouter',
|
||||
|
||||
Reference in New Issue
Block a user