fix: replace internal runtime terms in visible UI
Some checks failed
CD Pipeline / deploy (push) Failing after 1m4s
Some checks failed
CD Pipeline / deploy (push) Failing after 1m4s
This commit is contained in:
@@ -402,7 +402,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '')
|
||||
# ==========================================
|
||||
# 系統版本與路徑
|
||||
# ==========================================
|
||||
SYSTEM_VERSION = "V10.669"
|
||||
SYSTEM_VERSION = "V10.670"
|
||||
LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log')
|
||||
public_url = PUBLIC_URL # 用於模板顯示
|
||||
|
||||
|
||||
@@ -746,3 +746,4 @@ POSTGRES_HOST=momo-db
|
||||
| 2026-06-25 | 邊角工具頁不可只描述功能,必須說明如何支援 PChome 業績判斷 | V10.667 起 AI 生成歷史、PChome 爬蟲、趨勢資料、用戶管理、外部工具狀態與市場情報停用頁都改為短句,聚焦資料新鮮度、文案回收、權限治理與正式比價流程。 |
|
||||
| 2026-06-25 | 低頻治理頁與舊入口不可出現英文工程狀態或教學句 | V10.668 起 legacy bridge、維護/權限、匯入、設定、通知模板、缺貨管理、登入歷史、系統日誌與 AI 健康檢查頁統一改為繁中短句,聚焦資料可靠、權限守門、供貨風險與部署治理如何支援業績流程。 |
|
||||
| 2026-06-25 | 匯入頁不可把資料表流程當成使用者主訊息 | V10.669 起雲端匯入與系統匯入完成訊息改說明業績資料新鮮度與更新筆數,不再用「下載→匯入資料庫→刪除」或資料表名稱作為前台重點。 |
|
||||
| 2026-06-25 | 可見操作頁不可把權杖、DB、Agent、Pipeline 當成主語 | V10.670 起 AI 助手、日報、銷售分析、缺貨、部署監控與觀測台頁面進一步改用「用量、產出紀錄、AI 分工、部署流程、知識命中」等營運可讀語言。 |
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends "ewoooc_base.html" %}
|
||||
|
||||
{% block title %}Agent 指揮矩陣{% endblock %}
|
||||
{% block title %}AI 分工指揮台{% endblock %}
|
||||
|
||||
{% block ewooo_content %}
|
||||
<style>
|
||||
@@ -9,12 +9,12 @@
|
||||
</style>
|
||||
|
||||
<div class="container-fluid mt-3">
|
||||
<section class="agent-hero"><div class="agent-kicker"><i class="fas fa-network-wired me-1"></i> Agent 指揮矩陣 · {{ hours }} 小時視窗</div><h1 class="agent-title">Agent 指揮矩陣</h1><p class="agent-subtitle">確認 AI 分工、Ollama 占比、RAG 命中與 MCP 編排是否支撐業績決策。</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">Ollama 占比</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">RAG 命中率</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 分工、Ollama 占比、知識命中與 MCP 編排是否支撐業績決策。</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">Ollama 占比</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">四 Agent 矩陣</div><h3>LLM × MCP × RAG 編排矩陣</h3></div></div><div class="table-responsive"><table class="table mb-0"><thead class="table-light"><tr><th>Agent</th><th class="text-end">呼叫</th><th class="text-end">成本</th><th class="text-end">Ollama</th><th class="text-end">付費</th><th class="text-end">MCP</th><th class="text-end">RAG</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">A {{ ag.ollama_gcp_a }} · B {{ ag.ollama_gcp_b }} · 111 {{ 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">Ollama</th><th class="text-end">付費</th><th class="text-end">MCP</th><th class="text-end">RAG</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">A {{ ag.ollama_gcp_a }} · B {{ ag.ollama_gcp_b }} · 111 {{ 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>
|
||||
</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">Ollama {{ "%.0f"|format(ag.ollama_pct) if ag.calls > 0 else 0 }}% · RAG {{ "%.0f"|format(ag.rag_rate) if ag.calls > 0 else 0 }}% · MCP {{ "%.0f"|format(ag.mcp_rate) if ag.calls > 0 else 0 }}%</small></div>{% endfor %}</div></article>
|
||||
@@ -22,7 +22,7 @@
|
||||
</section>
|
||||
|
||||
{% if recommendations %}<section class="agent-panel mt-3"><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 r in recommendations %}<div class="rec-card"><span class="badge {% if r.severity == 'high' %}bg-danger{% elif r.severity == 'med' %}bg-warning{% else %}bg-info{% endif %} me-1">{{ r.severity|upper }}</span><strong>{{ r.agent }}</strong><div class="small mt-1"><i class="fas fa-search me-1"></i><strong>發現:</strong>{{ r.finding }}</div><div class="small text-muted"><i class="fas fa-arrow-right me-1"></i><strong>建議:</strong>{{ r.suggestion }}</div></div>{% endfor %}</div></section>{% endif %}
|
||||
{% if mcp_matrix %}<section class="agent-table-shell"><div class="agent-table-title"><div><div class="agent-label">MCP 明細</div><h3>MCP 服務 × 呼叫端工作量</h3></div></div><div class="table-responsive"><table class="table table-sm mb-0"><thead class="table-light"><tr><th>MCP 服務</th><th>呼叫端</th><th class="text-end">tool 呼叫</th><th class="text-end">快取</th><th class="text-end">快取率</th><th class="text-end">成本</th></tr></thead><tbody>{% for m in mcp_matrix %}<tr><td><code>{{ m.server }}</code></td><td><code>{{ m.caller }}</code></td><td class="text-end">{{ "{:,}".format(m.calls) }}</td><td class="text-end">{{ m.cache_hits }}</td><td class="text-end"><span class="{% if m.cache_rate >= 50 %}status-good{% elif m.cache_rate >= 20 %}status-warn{% endif %}">{{ "%.0f"|format(m.cache_rate) }}%</span></td><td class="text-end">${{ "%.4f"|format(m.cost) }}</td></tr>{% endfor %}</tbody></table></div></section>{% endif %}
|
||||
<p class="text-muted mt-3"><small><i class="fas fa-robot me-1"></i>Ollama 優先策略 v5.0 — Agent 指揮矩陣</small></p>
|
||||
{% if mcp_matrix %}<section class="agent-table-shell"><div class="agent-table-title"><div><div class="agent-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></tr></thead><tbody>{% for m in mcp_matrix %}<tr><td><code>{{ m.server }}</code></td><td><code>{{ m.caller }}</code></td><td class="text-end">{{ "{:,}".format(m.calls) }}</td><td class="text-end">{{ m.cache_hits }}</td><td class="text-end"><span class="{% if m.cache_rate >= 50 %}status-good{% elif m.cache_rate >= 20 %}status-warn{% endif %}">{{ "%.0f"|format(m.cache_rate) }}%</span></td><td class="text-end">${{ "%.4f"|format(m.cost) }}</td></tr>{% endfor %}</tbody></table></div></section>{% endif %}
|
||||
<p class="text-muted mt-3"><small><i class="fas fa-robot me-1"></i>Ollama 優先策略 — AI 分工指揮台</small></p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
|
||||
<section class="calls-command">
|
||||
<div class="calls-signal"><div class="calls-label">呼叫總量</div><span class="calls-value">{{ "{:,}".format(total) }}</span>{% if hourly_trend %}<canvas data-spark="calls" height="26"></canvas>{% endif %}</div>
|
||||
<div class="calls-signal"><div class="calls-label">權杖量</div><span class="calls-value">{{ "{:,}".format(summary.total_tokens or 0) }}</span><div class="calls-note">{{ avg_tokens }} 權杖/次</div></div>
|
||||
<div class="calls-signal"><div class="calls-label">用量</div><span class="calls-value">{{ "{:,}".format(summary.total_tokens or 0) }}</span><div class="calls-note">{{ avg_tokens }} 單位/次</div></div>
|
||||
<div class="calls-signal"><div class="calls-label">成本</div><span class="calls-value">${{ "%.2f"|format(summary.total_cost or 0) }}</span>{% if hourly_trend %}<canvas data-spark="cost" height="26"></canvas>{% endif %}</div>
|
||||
<div class="calls-signal"><div class="calls-label">延遲</div><span class="calls-value">{{ summary.avg_duration or 0 }}ms</span><div class="calls-note">{{ summary.cache_hits or 0 }} 次快取命中</div></div>
|
||||
<div class="calls-signal"><div class="calls-label">RAG 命中</div><span class="calls-value status-blue">{{ "%.1f"|format(rag_rate) }}%</span><div class="calls-note">{{ summary.rag_hits or 0 }} 次命中</div></div>
|
||||
@@ -87,7 +87,7 @@
|
||||
|
||||
{% if caller_richness %}
|
||||
<article class="calls-table-shell">
|
||||
<div class="calls-table-title"><div><div class="calls-label">呼叫端編排</div><h3>呼叫端 × RAG × MCP 編排矩陣</h3></div></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 class="text-end">總呼叫</th><th class="text-end">RAG 命中</th><th class="text-end">MCP 編排</th><th class="text-end">RAG 反饋</th><th class="text-end">筆數</th></tr></thead><tbody>{% for c in caller_richness %}<tr><td><code>{{ c.caller }}</code></td><td class="text-end">{{ "{:,}".format(c.total_calls) }}</td><td class="text-end"><strong class="{% if c.rag_hit_rate >= 50 %}status-good{% elif c.rag_hit_rate >= 20 %}status-warn{% else %}text-muted{% endif %}">{{ "%.1f"|format(c.rag_hit_rate) }}%</strong> <small class="text-muted">({{ c.rag_hits }})</small></td><td class="text-end"><strong class="{% if c.mcp_rate >= 30 %}status-blue{% elif c.mcp_rate >= 10 %}status-warn{% endif %}">{{ "%.1f"|format(c.mcp_rate) }}%</strong></td><td class="text-end">{% if c.feedback_count > 0 %}<strong class="{% if c.avg_rag_feedback >= 4 %}status-good{% elif c.avg_rag_feedback >= 3 %}status-warn{% else %}status-bad{% endif %}">{{ "%.2f"|format(c.avg_rag_feedback) }}/5</strong>{% else %}<small class="text-muted">—</small>{% endif %}</td><td class="text-end">{{ c.feedback_count }}</td></tr>{% endfor %}</tbody></table></div>
|
||||
</article>
|
||||
{% endif %}
|
||||
@@ -99,7 +99,7 @@
|
||||
<div class="calls-panel-body">
|
||||
<div class="calls-mini-grid">
|
||||
{% for row in by_provider[:4] %}
|
||||
<div class="calls-mini"><span class="calls-label">{{ obs_label.provider(row.provider) }}</span><strong>{{ "{:,}".format(row.calls) }}</strong><small class="text-muted">${{ "%.2f"|format(row.cost) }} · {{ "{:,}".format(row.tokens) }} 權杖</small></div>
|
||||
<div class="calls-mini"><span class="calls-label">{{ obs_label.provider(row.provider) }}</span><strong>{{ "{:,}".format(row.calls) }}</strong><small class="text-muted">${{ "%.2f"|format(row.cost) }} · {{ "{:,}".format(row.tokens) }} 用量</small></div>
|
||||
{% else %}<div class="text-muted small">尚無供應商資料</div>{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
@@ -107,7 +107,7 @@
|
||||
|
||||
{% if recent_contexts %}
|
||||
<article class="calls-panel">
|
||||
<div class="calls-panel-head"><div><div class="calls-label">Agent 上下文</div><h2 class="calls-panel-title">最近上下文</h2></div></div>
|
||||
<div class="calls-panel-head"><div><div class="calls-label">AI 上下文</div><h2 class="calls-panel-title">最近上下文</h2></div></div>
|
||||
<div class="calls-panel-body">
|
||||
{% for c in recent_contexts[:5] %}<div class="mb-2 pb-2 border-bottom"><span class="badge bg-info">{{ c.agent_name }}</span> <code>{{ c.context_key }}</code><div class="text-muted small mt-1">{{ c.preview }}{% if c.preview|length >= 120 %}…{% endif %}</div></div>{% endfor %}
|
||||
</div>
|
||||
@@ -119,7 +119,7 @@
|
||||
{% 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="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>
|
||||
</section>
|
||||
{% endif %}
|
||||
|
||||
|
||||
@@ -434,7 +434,7 @@
|
||||
<div class="obs-signal">
|
||||
<div class="obs-signal-label">24 小時呼叫</div>
|
||||
<div class="obs-signal-value">{{ "{:,}".format(ai.total) if ai else '—' }}</div>
|
||||
<div class="obs-signal-note">權杖量:{{ "{:,}".format(ai.tokens) if ai else '—' }}</div>
|
||||
<div class="obs-signal-note">用量:{{ "{:,}".format(ai.tokens) if ai else '—' }}</div>
|
||||
</div>
|
||||
<div class="obs-signal">
|
||||
<div class="obs-signal-label">成本水位</div>
|
||||
@@ -600,7 +600,7 @@
|
||||
<h3>系統與成本</h3>
|
||||
<div class="obs-route-list">
|
||||
<a class="obs-route-card" href="/observability/host_health"><span class="obs-route-icon"><i class="fas fa-heartbeat"></i></span><span><span class="obs-route-title">主機健康</span><span class="obs-route-desc">三主機、MCP、AIOps、AutoHeal。</span></span><span class="obs-route-code">04</span></a>
|
||||
<a class="obs-route-card" href="/observability/ai_calls"><span class="obs-route-icon"><i class="fas fa-chart-bar"></i></span><span><span class="obs-route-title">AI 呼叫</span><span class="obs-route-desc">權杖量、成本、錯誤、RAG × MCP 矩陣。</span></span><span class="obs-route-code">05</span></a>
|
||||
<a class="obs-route-card" href="/observability/ai_calls"><span class="obs-route-icon"><i class="fas fa-chart-bar"></i></span><span><span class="obs-route-title">AI 呼叫</span><span class="obs-route-desc">用量、成本、錯誤、知識與工具矩陣。</span></span><span class="obs-route-code">05</span></a>
|
||||
<a class="obs-route-card" href="/observability/budget"><span class="obs-route-icon"><i class="fas fa-wallet"></i></span><span><span class="obs-route-title">預算控管</span><span class="obs-route-desc">供應商成本與強制節流。</span></span><span class="obs-route-code">06</span></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -263,7 +263,7 @@
|
||||
<div class="ppt-label">工作隊列</div>
|
||||
<h2 class="ppt-panel-title">接下來要處理的事</h2>
|
||||
</div>
|
||||
<small class="text-muted">把缺漏、預覽、視覺 QA、DB 寫入集中成工作隊列</small>
|
||||
<small class="text-muted">把缺漏、預覽、視覺 QA 與產出紀錄集中成工作隊列</small>
|
||||
</div>
|
||||
<div class="ppt-action-grid">
|
||||
{% for lane in pipeline_view.action_lanes %}
|
||||
@@ -413,7 +413,7 @@
|
||||
<small class="text-muted">目前缺漏 {{ auto_generation.missing_count }} 類</small>
|
||||
</div>
|
||||
<div>
|
||||
<span class="ppt-label">DB 紀錄</span>
|
||||
<span class="ppt-label">產出紀錄</span>
|
||||
<strong>{{ generation_runs|length }}</strong>
|
||||
<small class="text-muted">本月最近寫入</small>
|
||||
</div>
|
||||
@@ -426,7 +426,7 @@
|
||||
<div class="ppt-coverage-list" aria-label="定義簡報覆蓋明細">
|
||||
<div class="ppt-coverage-list-head">
|
||||
<span class="ppt-label">報表覆蓋矩陣</span>
|
||||
<small>DB / 預覽 / 視覺 QA / 交付</small>
|
||||
<small>紀錄 / 預覽 / 視覺 QA / 交付</small>
|
||||
</div>
|
||||
{% for item in auto_generation_items %}
|
||||
<div class="ppt-coverage-row is-{{ item.delivery_status }}">
|
||||
@@ -481,7 +481,7 @@
|
||||
</div>
|
||||
<div class="ppt-auto-status small text-muted mt-3" data-ppt-auto-status>
|
||||
{% if auto_generation.enabled %}
|
||||
{{ auto_generation.cadence_summary }} 會定期產出並寫入 DB;目前缺漏 {{ auto_generation.missing_count }} 類。視覺 QA 可立即補跑,或等待每日 22:00 排程。
|
||||
{{ auto_generation.cadence_summary }} 會定期產出並保存紀錄;目前缺漏 {{ auto_generation.missing_count }} 類。視覺 QA 可立即補跑,或等待每日 22:00 排程。
|
||||
{% else %}
|
||||
PPT_AUTO_GENERATION_ENABLED=false,已停用自動補齊。
|
||||
{% endif %}
|
||||
@@ -489,10 +489,10 @@
|
||||
<div class="ppt-run-log mt-3">
|
||||
<div class="ppt-run-log-head">
|
||||
<div>
|
||||
<div class="ppt-label">DB 產出紀錄</div>
|
||||
<h3>最近寫入 ppt_generation_runs</h3>
|
||||
<div class="ppt-label">產出紀錄</div>
|
||||
<h3>最近產出</h3>
|
||||
</div>
|
||||
<small class="text-muted">產出檔案、參數、狀態會同步保留在資料庫</small>
|
||||
<small class="text-muted">產出檔案、參數、狀態會同步保留供追蹤</small>
|
||||
</div>
|
||||
{% if generation_runs %}
|
||||
<div class="ppt-run-list">
|
||||
@@ -522,7 +522,7 @@
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="ppt-empty ppt-run-empty">
|
||||
本月尚無 DB 產出紀錄;先補排程或手動產出,再進行視覺 QA。
|
||||
本月尚無產出紀錄;先補排程或手動產出,再進行視覺 QA。
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -622,18 +622,18 @@
|
||||
{% if f.source == 'database' %}
|
||||
{% if f.file_exists %}
|
||||
{% if f.is_valid_ppt %}
|
||||
<span class="text-success">資料庫快取 + 檔案可預覽</span>
|
||||
<span class="text-success">產出紀錄 + 檔案可預覽</span>
|
||||
{% else %}
|
||||
<span class="status-bad">資料庫快取 + 檔案損毀,建議重跑</span>
|
||||
<span class="status-bad">產出紀錄 + 檔案損毀,建議重跑</span>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<span class="text-muted">資料庫快取(檔案未落盤)</span>
|
||||
<span class="text-muted">產出紀錄(檔案未落盤)</span>
|
||||
{% endif %}
|
||||
{% elif f.source == 'both' %}
|
||||
{% if f.is_valid_ppt %}
|
||||
<span class="text-success">檔案 + 資料庫</span>
|
||||
<span class="text-success">檔案 + 產出紀錄</span>
|
||||
{% else %}
|
||||
<span class="status-bad">檔案 + 資料庫,檔案損毀</span>
|
||||
<span class="status-bad">檔案 + 產出紀錄,檔案損毀</span>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% if f.is_valid_ppt %}
|
||||
|
||||
@@ -88,7 +88,7 @@
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<label class="form-label small fw-bold mb-1">AI 模型</label>
|
||||
<label class="form-label small fw-bold mb-1">分析模型</label>
|
||||
<select class="form-select form-select-sm" id="ollamaModelSelect">
|
||||
{% for model in available_models %}
|
||||
<option value="{{ model }}" {% if 'gemma3:4b' in model %}selected{% endif %}>{{ model }}</option>
|
||||
@@ -111,7 +111,7 @@
|
||||
<div class="d-flex justify-content-between small mt-1">
|
||||
<span>費用:<strong id="geminiMonthlyCost">$0.0000</strong></span>
|
||||
<span>請求:<span id="geminiRequestCount">0</span> 次</span>
|
||||
<span>權杖:<span id="geminiTokenUsage">0</span></span>
|
||||
<span>用量:<span id="geminiTokenUsage">0</span></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
/* Pipeline Flow */
|
||||
/* 部署流程 */
|
||||
.pipeline-flow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -200,7 +200,7 @@
|
||||
.pod-status .pod-ready.healthy { background: rgba(40, 167, 69, 0.3); }
|
||||
.pod-status .pod-ready.unhealthy { background: rgba(220, 53, 69, 0.3); }
|
||||
|
||||
/* Pipeline History */
|
||||
/* 部署歷史 */
|
||||
.pipeline-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -524,7 +524,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pipeline Flow -->
|
||||
<!-- 部署流程 -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<span><i class="bi bi-diagram-3 me-2"></i>最新部署流程</span>
|
||||
@@ -617,7 +617,7 @@
|
||||
<i class="bi bi-search me-1"></i>診斷
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-info" onclick="triggerFullRepair()">
|
||||
<i class="bi bi-wrench me-1"></i>一鍵修復
|
||||
<i class="bi bi-wrench me-1"></i>修復部署
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -625,12 +625,12 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pipeline History -->
|
||||
<!-- 部署歷史 -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<span><i class="bi bi-clock-history me-2"></i>部署歷史</span>
|
||||
<a href="http://192.168.0.110:8929/root/momo-pro-system/-/pipelines" target="_blank" class="btn btn-sm btn-outline-light">
|
||||
查看全部
|
||||
查看部署紀錄
|
||||
</a>
|
||||
</div>
|
||||
<div class="card-body" id="pipelineHistory">
|
||||
@@ -963,7 +963,7 @@
|
||||
// 渲染 runtime 狀態
|
||||
function renderPods(pods, envId) {
|
||||
if (!pods || pods.length === 0) {
|
||||
return '<p class="text-muted small mb-0">Docker Compose 執行環境;舊叢集資訊不適用</p>';
|
||||
return '<p class="text-muted small mb-0">Docker Compose 執行環境;外部叢集資訊不適用</p>';
|
||||
}
|
||||
|
||||
return pods.map(pod => `
|
||||
@@ -1098,7 +1098,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 完整修復
|
||||
// 部署修復
|
||||
async function triggerFullRepair() {
|
||||
const env = prompt('請輸入要修復的環境 (uat 或 prod):', 'uat');
|
||||
if (!env || !['uat', 'prod'].includes(env)) {
|
||||
@@ -1106,9 +1106,9 @@
|
||||
return;
|
||||
}
|
||||
|
||||
if (!confirm(`確定要對 ${env.toUpperCase()} 執行完整修復嗎?\n這會重啟 Registry 並執行執行環境診斷,不會重啟舊叢集。`)) return;
|
||||
if (!confirm(`確定要對 ${env.toUpperCase()} 執行部署修復嗎?\n這會執行必要服務修復與環境診斷。`)) return;
|
||||
|
||||
showNotification('執行中', '正在執行完整修復...');
|
||||
showNotification('執行中', '正在修復部署狀態...');
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/cicd/auto-fix', {
|
||||
@@ -1120,13 +1120,13 @@
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
showNotification('✅ 完整修復完成', `已執行 ${data.results?.length || 0} 個修復動作`);
|
||||
showNotification('✅ 部署修復完成', `已執行 ${data.results?.length || 0} 個修復動作`);
|
||||
setTimeout(loadDashboard, 5000);
|
||||
} else {
|
||||
showNotification('❌ 修復失敗', data.error, true);
|
||||
}
|
||||
} catch (error) {
|
||||
showNotification('❌ 錯誤', '無法執行完整修復: ' + error, true);
|
||||
showNotification('❌ 錯誤', '無法執行部署修復: ' + error, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -305,7 +305,7 @@
|
||||
<div class="card-header"><i class="fas fa-trophy"></i> 商品 Top 10</div>
|
||||
<div class="card-body">
|
||||
<div class="chart-mobile-hint d-md-none">
|
||||
<i class="fas fa-hand-pointer"></i> 左右滑動查看完整圖表
|
||||
<i class="fas fa-hand-pointer"></i> 左右滑動看業績趨勢
|
||||
</div>
|
||||
<div class="chart-responsive">
|
||||
<div class="chart-container has-html-chart" id="top10ChartContainer">
|
||||
@@ -499,7 +499,7 @@
|
||||
</header>
|
||||
<div class="card-body">
|
||||
<div class="chart-mobile-hint d-md-none">
|
||||
<i class="fas fa-hand-pointer"></i> 左右滑動查看完整列表
|
||||
<i class="fas fa-hand-pointer"></i> 左右滑動看分類明細
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table id="categoryTable" class="table table-hover">
|
||||
|
||||
@@ -354,8 +354,8 @@
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="dashboard-focus-title">尚無 AI 挑品</div>
|
||||
<div class="dashboard-focus-sub momo-mono">請先讓 PChome 比對與挑品 Agent 累積資料</div>
|
||||
<div class="dashboard-focus-title">尚無挑品建議</div>
|
||||
<div class="dashboard-focus-sub momo-mono">先累積 PChome 比價與挑品資料</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -390,14 +390,14 @@
|
||||
<div class="col-md-6">
|
||||
<div class="sa-empty__hint sa-empty__hint--olive">
|
||||
<h5><i class="fas fa-calendar-alt me-2" aria-hidden="true"></i>自訂日期區間</h5>
|
||||
<p>精確指定開始/結束日期進行分析</p>
|
||||
<p class="sa-empty__hint-foot"><i class="fas fa-clock me-1" aria-hidden="true"></i>適合特定區間分析</p>
|
||||
<p>鎖定活動或檔期,評估業績變化</p>
|
||||
<p class="sa-empty__hint-foot"><i class="fas fa-clock me-1" aria-hidden="true"></i>適合檔期與活動回顧</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="alert alert-info sa-empty__tip">
|
||||
<i class="fas fa-info-circle me-2" aria-hidden="true"></i>
|
||||
<strong>提示:</strong>選擇條件後,直接看影響業績的圖表、分類與商品明細。
|
||||
<strong>分析下一步:</strong>選擇條件後,直接看影響業績的圖表、分類與商品明細。
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -437,7 +437,7 @@
|
||||
<h6 class="sa-panel__title">
|
||||
<i class="fas fa-sort-amount-down me-2" aria-hidden="true"></i>ABC 分析 (80/20 法則)
|
||||
</h6>
|
||||
<span class="sa-tag sa-tag--neutral">點擊類別查看詳情</span>
|
||||
<span class="sa-tag sa-tag--neutral">選類別看貢獻</span>
|
||||
</div>
|
||||
<div class="row mt-3 sa-abc">
|
||||
{% set abc_classes = [
|
||||
@@ -565,7 +565,7 @@
|
||||
<tr>
|
||||
<td class="text-center text-muted">{{ loop.index }}</td>
|
||||
<td class="fw-bold">
|
||||
<a href="javascript:setFilter('vendor', '{{ v.name }}')" class="sa-vendor-link" title="點擊篩選此廠商商品">
|
||||
<a href="javascript:setFilter('vendor', '{{ v.name }}')" class="sa-vendor-link" title="篩選此廠商商品">
|
||||
{{ v.name }}
|
||||
<i class="fas fa-filter ms-1 sa-vendor-link__icon" aria-hidden="true"></i>
|
||||
</a>
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
<div class="stockout-import-card">
|
||||
<div class="stockout-card-label momo-mono">必要欄位</div>
|
||||
<div class="stockout-card-title">來源供應商編號、來源供應商名稱、商品ID、商品名稱</div>
|
||||
<div class="stockout-card-meta momo-mono">缺少必要欄位時,API 會拒絕匯入並回傳錯誤原因。</div>
|
||||
<div class="stockout-card-meta momo-mono">缺少必要欄位時,系統會拒絕匯入並回傳錯誤原因。</div>
|
||||
</div>
|
||||
<div class="stockout-import-card">
|
||||
<div class="stockout-card-label momo-mono">欄位範本</div>
|
||||
@@ -113,7 +113,7 @@
|
||||
<div class="stockout-batch-id momo-mono">批次編號 <span id="stockoutBatchId"></span></div>
|
||||
<div class="stockout-import-actions is-result">
|
||||
<a class="stockout-action is-primary" href="{{ url_for('vendor.list_page') }}">
|
||||
<i class="fas fa-table-list"></i>查看缺貨清單
|
||||
<i class="fas fa-table-list"></i>處理缺貨清單
|
||||
</a>
|
||||
<button class="stockout-action" id="stockoutImportAgainButton" type="button">
|
||||
<i class="fas fa-rotate-right"></i>再次匯入
|
||||
|
||||
@@ -140,7 +140,7 @@
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="vendor-summary-title">尚無匯入紀錄</div>
|
||||
<div class="vendor-summary-meta momo-mono">資料庫目前沒有缺貨資料</div>
|
||||
<div class="vendor-summary-meta momo-mono">先匯入缺貨批次,確認主推商品供貨風險</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="vendor-summary-item">
|
||||
|
||||
@@ -142,8 +142,8 @@
|
||||
<div class="upload-icon">
|
||||
<i class="fas fa-cloud-upload-alt"></i>
|
||||
</div>
|
||||
<h4 class="mb-3">拖曳檔案到此處或點擊選擇檔案</h4>
|
||||
<p class="text-muted">支援格式: Excel (.xlsx, .xls)</p>
|
||||
<h4 class="mb-3">匯入供應商窗口名單</h4>
|
||||
<p class="text-muted">上傳 Excel,讓缺貨通知送到正確窗口。</p>
|
||||
<p class="text-muted small">
|
||||
<strong>必要欄位:</strong>來源供應商編號、來源供應商名稱<br>
|
||||
<strong>選填欄位:</strong>Mail(支援多個郵件用逗號或分號分隔)
|
||||
@@ -229,7 +229,7 @@
|
||||
<i class="fas fa-redo me-2"></i>再次匯入
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary" style="background: var(--momo-page-accent-dark); border-color: var(--momo-page-accent-dark);" onclick="refreshVendorList()">
|
||||
<i class="fas fa-list me-2"></i>查看廠商清單
|
||||
<i class="fas fa-list me-2"></i>確認窗口清單
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -821,3 +821,43 @@ def test_governance_and_low_frequency_pages_avoid_engineering_status_copy():
|
||||
assert "已整併" in text
|
||||
for bad in forbidden:
|
||||
assert bad not in text
|
||||
|
||||
|
||||
def test_visible_operations_pages_hide_internal_runtime_terms():
|
||||
from pathlib import Path
|
||||
|
||||
expected = {
|
||||
"templates/ai_recommend.html": ["分析模型", "用量"],
|
||||
"templates/vendor_stockout_index_v2.html": ["先匯入缺貨批次", "供貨風險"],
|
||||
"templates/dashboard_v2.html": ["尚無挑品建議", "先累積 PChome 比價與挑品資料"],
|
||||
"templates/daily_sales.html": ["左右滑動看業績趨勢", "左右滑動看分類明細"],
|
||||
"templates/sales_analysis.html": ["分析下一步", "選類別看貢獻", "適合檔期與活動回顧"],
|
||||
"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/ai_calls_dashboard.html": ["用量", "AI 上下文", "知識與工具編排矩陣"],
|
||||
"templates/admin/observability_overview.html": ["用量", "知識與工具矩陣"],
|
||||
"templates/cicd_dashboard.html": ["部署流程", "部署歷史", "修復部署", "查看部署紀錄"],
|
||||
}
|
||||
forbidden_by_path = {
|
||||
"templates/ai_recommend.html": ["權杖:", "AI 模型"],
|
||||
"templates/vendor_stockout_index_v2.html": ["資料庫目前沒有缺貨資料"],
|
||||
"templates/dashboard_v2.html": ["尚無 AI 挑品", "挑品 Agent"],
|
||||
"templates/daily_sales.html": ["左右滑動查看完整圖表", "左右滑動查看完整列表"],
|
||||
"templates/sales_analysis.html": ["提示:</strong>選擇條件", "點擊類別查看詳情", "點擊篩選此廠商商品"],
|
||||
"templates/vendor_stockout_vendor_management_v2.html": ["拖曳檔案到此處或點擊選擇檔案", "查看廠商清單"],
|
||||
"templates/vendor_stockout_import_v2.html": ["API 會拒絕匯入", "查看缺貨清單"],
|
||||
"templates/admin/ppt_audit_history.html": ["DB 紀錄", "DB / 預覽", "寫入 DB", "DB 產出紀錄", "資料庫快取", "本月尚無 DB"],
|
||||
"templates/admin/agent_orchestration.html": ["Agent 指揮矩陣", "四 Agent 矩陣", "LLM × MCP × RAG 編排矩陣", "權杖"],
|
||||
"templates/admin/ai_calls_dashboard.html": ["權杖量", "權杖/次", ">權杖<", "Agent 上下文", "RAG × MCP"],
|
||||
"templates/admin/observability_overview.html": ["權杖量", "RAG × MCP"],
|
||||
"templates/cicd_dashboard.html": ["Pipeline Flow", "Pipeline History", "完整修復", "一鍵修復", "重啟 Registry", "舊叢集"],
|
||||
}
|
||||
|
||||
for path, markers in expected.items():
|
||||
text = Path(path).read_text(encoding="utf-8")
|
||||
for marker in markers:
|
||||
assert marker in text
|
||||
for bad in forbidden_by_path.get(path, []):
|
||||
assert bad not in text
|
||||
|
||||
Reference in New Issue
Block a user