fix: hide raw ops terms on governance pages

This commit is contained in:
ogt
2026-06-26 07:43:50 +08:00
parent bd7cbcae62
commit 7d183dc738
10 changed files with 137 additions and 63 deletions

View File

@@ -402,7 +402,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '')
# ==========================================
# 系統版本與路徑
# ==========================================
SYSTEM_VERSION = "V10.701"
SYSTEM_VERSION = "V10.702"
LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log')
public_url = PUBLIC_URL # 用於模板顯示

View File

@@ -778,3 +778,4 @@ POSTGRES_HOST=momo-db
| 2026-06-25 | 工具頁與簡報頁也必須使用作戰語言Google Drive token 必須固定到持久化掛載 | V10.699 起簡報預覽、品牌素材、比價、匯入、缺貨與觀測台操作提示移除英文/內部流程字,改成可直接理解的狀態與下一步;正式容器明確指定 `/app/config/google_token.json``/app/config/google_credentials.json`,背景匯入不得因主機重啟或工作目錄變動而改找瀏覽器授權。 |
| 2026-06-26 | 邊角治理頁不得把 raw caller、模型資料或推版代碼當主訊息 | V10.700 起 AI 流量、主機健康、通知模板與缺貨匯入提示再收斂為「使用情境、建議路徑、服務資料、更新摘要、先選擇供應商缺貨檔」等營運語言,避免低頻治理頁回流 `原始`、模型品牌、commit/pipeline 欄位或泛用檔案提示。 |
| 2026-06-26 | 靜態資源部署必須保持容器可讀 | V10.701 起部署 SOP 改用 Git 物件打包差異檔,並以測試檢查 `web/static` 檔案可被容器讀取,避免 macOS / iCloud 工作目錄權限造成正式 `/static/*` 500讓全站 CSS/JS 不再因檔案模式回歸而失效。 |
| 2026-06-26 | 治理頁也要用營運情境語,不得渲染 raw caller、服務代碼或錯誤日誌 | V10.702 起 AI 流量頁把 caller/provider/上下文改成「使用情境、建議路徑、作戰素材」;服務更新監控頁隱藏 branch/sha/error log/pod 名稱等工程細節,改用「更新流程、服務元件、診斷線索」語言;缺貨頁移除英文 Vendor Stockout。 |

View File

@@ -119,16 +119,36 @@
'gcp_ollama': '主力建議路徑',
'ollama_secondary': '備援建議路徑',
'ollama_111': '第三建議路徑',
'nim_via_elephant': 'NIM Elephant',
'nim_via_elephant': '雲端加速備援',
'gemini': '雲端備援',
'claude': '雲端審查備援',
'nim': 'NIM',
'openrouter': 'OpenRouter',
'nim': '快速雲端建議',
'openrouter': '外部備援路徑',
'unknown': '未分類供應商'
} -%}
{%- if value -%}{{ labels.get(value, value|replace('_', ' ')) }}{%- else -%}{{ fallback }}{%- endif -%}
{%- endmacro %}
{% macro caller(value, fallback='營運建議流程') -%}
{%- set labels = {
'hermes_analyst': '業績分析建議',
'nemotron_dispatch': '深度策略判斷',
'openclaw_strategist': '作戰策略建議',
'openclaw': '作戰策略建議',
'code_review_openclaw': '程式品質審查',
'pchome_growth': 'PChome 成長判斷',
'pchome_growth_dashboard': 'PChome 成長儀表板',
'pchome_price_intelligence': 'PChome 價格情報',
'price_recommendation': '價格建議',
'sales_analysis': '業績分析',
'monthly_summary': '月度結構分析',
'ai_recommend': '銷售建議',
'telegram_bot': '通知助理',
'scheduler': '排程任務'
} -%}
{%- if value -%}{{ labels.get(value, fallback) }}{%- else -%}{{ fallback }}{%- endif -%}
{%- endmacro %}
{% macro source(value, fallback='未分類來源') -%}
{%- set labels = {
'ai_insights': 'AI 知識庫',

View File

@@ -55,10 +55,10 @@
<h1 class="calls-title">AI 流量控制塔</h1>
<p class="calls-subtitle">看成本、錯誤率與知識命中,確保 AI 建議穩定支援業績判斷。</p>
<div class="calls-actions">
<button class="btn btn-warning btn-sm" onclick="triggerCodeReview()"><i class="fas fa-microscope me-1"></i>觸發程式碼審查管線</button>
<button class="btn btn-warning btn-sm" onclick="triggerCodeReview()"><i class="fas fa-microscope me-1"></i>啟動品質審查</button>
<form method="get" class="calls-filter">
<select name="hours" class="form-select form-select-sm">{% 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>
<select name="caller" class="form-select form-select-sm"><option value="">全部呼叫端</option>{% for c in callers %}<option value="{{ c }}" {% if caller_filter == c %}selected{% endif %}>{{ c }}</option>{% endfor %}</select>
<select name="caller" class="form-select form-select-sm"><option value="">全部使用情境</option>{% for c in callers %}<option value="{{ c }}" {% if caller_filter == c %}selected{% endif %}>{{ obs_label.caller(c) }}</option>{% endfor %}</select>
<select name="provider" class="form-select form-select-sm"><option value="">全部供應商</option>{% for p in ['gcp_ollama','ollama_secondary','ollama_111','gemini','claude','nim','openrouter','nim_via_elephant'] %}<option value="{{ p }}" {% if provider_filter == p %}selected{% endif %}>{{ obs_label.provider(p) }}</option>{% endfor %}</select>
<button class="btn btn-primary btn-sm">套用</button>
</form>
@@ -87,8 +87,8 @@
{% if caller_richness %}
<article 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 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 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>
<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">知識命中</th><th class="text-end">工具協作</th><th class="text-end">品質回</th><th class="text-end">筆數</th></tr></thead><tbody>{% for c in caller_richness %}<tr><td><span>{{ obs_label.caller(c.caller) }}</span></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 %}
</div>
@@ -107,9 +107,9 @@
{% if recent_contexts %}
<article class="calls-panel">
<div class="calls-panel-head"><div><div class="calls-label">AI 上下文</div><h2 class="calls-panel-title">最近上下文</h2></div></div>
<div class="calls-panel-head"><div><div class="calls-label">建議素材</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 %}
{% for c in recent_contexts[:5] %}<div class="mb-2 pb-2 border-bottom"><span class="badge bg-info">{{ obs_label.caller(c.agent_name) }}</span><div class="text-muted small mt-1">已納入近期業績判斷素材</div></div>{% endfor %}
</div>
</article>
{% endif %}

View File

@@ -1,6 +1,6 @@
{% extends "ewoooc_base.html" %}
{% block title %}部署監控 - EwoooC{% endblock %}
{% block title %}服務更新監控 - EwoooC{% endblock %}
{% block extra_head %}
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css" rel="stylesheet">
@@ -80,7 +80,7 @@
font-weight: 800;
}
/* 部署流程 */
/* 服務更新流程 */
.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-item {
display: flex;
align-items: center;
@@ -473,8 +473,8 @@
<div class="container">
<div class="d-flex justify-content-between align-items-center">
<div>
<h1><i class="bi bi-rocket-takeoff me-2"></i>部署監控</h1>
<p class="mb-0 opacity-75">PChome 業績成長自動化作戰系統 · 持續整合與部署監控</p>
<h1><i class="bi bi-rocket-takeoff me-2"></i>服務更新監控</h1>
<p class="mb-0 opacity-75">PChome 業績成長自動化作戰系統 · 正式服務更新與可用性監控</p>
</div>
<div class="d-flex align-items-center gap-3">
<div class="realtime-badge">
@@ -524,11 +524,11 @@
</div>
</div>
<!-- 部署流程 -->
<!-- 服務更新流程 -->
<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>
<span id="pipelineId" class="badge bg-secondary">#--</span>
<span><i class="bi bi-diagram-3 me-2"></i>最新更新流程</span>
<span id="pipelineId" class="badge bg-secondary">待確認</span>
</div>
<div class="card-body">
<div class="pipeline-flow" id="pipelineFlow">
@@ -564,12 +564,12 @@
<div class="row g-3">
<div class="col-6">
<button class="action-btn primary w-100" onclick="triggerDeploy('uat')">
<i class="bi bi-cloud-upload"></i>部署 UAT
<i class="bi bi-cloud-upload"></i>更新測試站
</button>
</div>
<div class="col-6">
<button class="action-btn primary w-100" onclick="triggerDeploy('prod')">
<i class="bi bi-cloud-upload"></i>部署 PROD
<i class="bi bi-cloud-upload"></i>更新正式站
</button>
</div>
<div class="col-6">
@@ -589,7 +589,7 @@
<div class="row g-3">
<div class="col-12">
<a href="http://192.168.0.110:8929/root/momo-pro-system/-/pipelines" target="_blank" class="btn btn-outline-light w-100">
<i class="bi bi-box-arrow-up-right me-2"></i>開啟 GitLab 部署紀錄
<i class="bi bi-box-arrow-up-right me-2"></i>開啟更新紀錄
</a>
</div>
<div class="col-6">
@@ -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>
<!-- 部署歷史 -->
<!-- 更新歷史 -->
<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>
<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">
@@ -687,7 +687,7 @@
updateLastUpdate();
}
} catch (error) {
console.error('Failed to load dashboard:', error);
console.error('服務更新監控讀取失敗:', error);
}
}
@@ -718,10 +718,10 @@
<div class="issue-detail">
${issue.type === 'job' ? `<span class="badge bg-secondary me-1">${displayStageName(issue.stage)}</span>` : ''}
${issue.type === 'runtime' ? `<span class="badge bg-info me-1">${issue.environment?.toUpperCase()}</span>` : ''}
${issue.error ? `<br><span class="text-muted">${escapeHtml(issue.error.substring(0, 120))}</span>` : ''}
${issue.error ? `<br><span class="text-muted">已記錄診斷線索</span>` : ''}
</div>
${issue.fix_suggestion ? `<div class="issue-suggestion">💡 ${escapeHtml(issue.fix_suggestion)}</div>` : ''}
${issue.error_log ? `<div class="error-log-preview">${escapeHtml(issue.error_log.substring(0, 300))}</div>` : ''}
${issue.error_log ? `<div class="error-log-preview">已保留診斷資料,請由維護者查看。</div>` : ''}
</div>
<div class="issue-actions">
${issue.auto_fixable ? `
@@ -768,7 +768,7 @@
return;
}
pipelineIdEl.textContent = `#${latestPipeline.id}`;
pipelineIdEl.textContent = '最近一次';
// 如果已有 jobs 數據,直接使用
if (latestJobs && latestJobs.length > 0) {
@@ -940,8 +940,8 @@
${!env.healthy && env.error ? `
<div class="env-error">
<div class="env-error-title">❌ 連線錯誤</div>
<div class="env-error-detail">${escapeHtml(env.error)}</div>
<div class="env-error-title">❌ 服務連線需確認</div>
<div class="env-error-detail">已記錄診斷線索,請先確認健康檢查與服務狀態。</div>
<button class="btn-fix mt-2" onclick="triggerAutoFix('diagnose', '${envId}')">
<i class="bi bi-search me-1"></i>診斷服務
</button>
@@ -949,7 +949,7 @@
` : ''}
<div class="env-details mt-2">
<strong class="d-block mb-2">執行環境狀態:</strong>
<strong class="d-block mb-2">服務元件狀態:</strong>
${renderPods(env.pods, envId)}
</div>
</div>
@@ -963,14 +963,14 @@
// 渲染 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">正式服務由主機容器管理,暫無元件明細。</p>';
}
return pods.map(pod => `
<div class="pod-status">
<span class="status-indicator ${pod.healthy ? 'healthy' : 'unhealthy'}"></span>
<span class="pod-name">${pod.name}</span>
<span class="pod-ready ${pod.healthy ? 'healthy' : 'unhealthy'}">${pod.ready}</span>
<span class="pod-name">${displayServiceName(pod.name)}</span>
<span class="pod-ready ${pod.healthy ? 'healthy' : 'unhealthy'}">${pod.healthy ? '正常' : '需確認'}</span>
<span class="text-muted small ms-2">${pod.age}</span>
${pod.restarts > 0 ? `<span class="badge bg-warning ms-2">${pod.restarts} 重啟</span>` : ''}
${!pod.healthy ? `<span class="badge bg-danger ms-2">${pod.status}</span>` : ''}
@@ -978,12 +978,12 @@
`).join('');
}
// 更新部署歷史
// 更新歷史
function updatePipelineHistory(pipelines) {
const container = document.getElementById('pipelineHistory');
if (!pipelines || pipelines.length === 0) {
container.innerHTML = '<p class="text-center text-muted">暫無部署流程紀錄</p>';
container.innerHTML = '<p class="text-center text-muted">暫無更新紀錄</p>';
return;
}
@@ -992,9 +992,9 @@
html += `
<div class="pipeline-item">
<span class="pipeline-status">${p.status_icon}</span>
<span class="pipeline-id">#${p.id}</span>
<span class="pipeline-id">更新</span>
<div class="pipeline-info">
<div class="pipeline-commit">${p.ref} @ ${p.sha}</div>
<div class="pipeline-commit">${displayRefName(p.ref)}</div>
<div class="pipeline-time">${formatTime(p.created_at)} ${p.duration ? `${p.duration}` : ''}</div>
</div>
<a href="${p.web_url}" target="_blank" class="btn btn-sm btn-outline-secondary">
@@ -1014,9 +1014,9 @@
el.textContent = `最後更新: ${now.toLocaleTimeString('zh-TW')}`;
}
// 觸發部署
// 觸發服務更新
async function triggerDeploy(env) {
if (!confirm(`確定要觸發 ${env.toUpperCase()} 部署嗎?`)) return;
if (!confirm(`確定要更新${displayEnvName(env)}嗎?`)) return;
try {
const response = await fetch('/api/cicd/deploy', {
@@ -1032,13 +1032,13 @@
setTimeout(loadDashboard, 2000);
}
} catch (error) {
showNotification('錯誤', '無法觸發部署');
showNotification('錯誤', '無法啟動服務更新');
}
}
// 觸發回滾
async function triggerRollback(env) {
if (!confirm(`確定要回${env.toUpperCase()} 嗎?這將恢復到上一個版本`)) return;
if (!confirm(`確定要回${displayEnvName(env)}到上一個版本嗎?`)) return;
try {
const response = await fetch('/api/cicd/rollback', {
@@ -1074,9 +1074,9 @@
// 自動修復
async function triggerAutoFix(action, env) {
if (!confirm(`確定要執行自動修復 (${action}) 嗎?`)) return;
if (!confirm(`確定要執行服務修復嗎?`)) return;
showNotification('執行中', `正在執行 ${action}...`);
showNotification('執行中', '正在執行服務修復...');
try {
const response = await fetch('/api/cicd/auto-fix', {
@@ -1094,11 +1094,11 @@
showNotification('❌ 修復失敗', data.error, true);
}
} catch (error) {
showNotification('❌ 錯誤', '無法執行自動修復: ' + error, true);
showNotification('❌ 錯誤', '無法執行自動修復', true);
}
}
// 部署修復
// 服務修復
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這會執行必要服務修復與環境診斷。`)) return;
if (!confirm(`確定要對${displayEnvName(env)}執行服務修復嗎?\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('❌ 錯誤', '無法執行服務修復', true);
}
}
@@ -1138,7 +1138,7 @@
return;
}
showNotification('執行中', `正在診斷 ${env.toUpperCase()} 環境...`);
showNotification('執行中', `正在診斷${displayEnvName(env)}...`);
try {
const response = await fetch('/api/cicd/diagnose', {
@@ -1171,7 +1171,7 @@
showNotification('❌ 診斷失敗', data.error, true);
}
} catch (error) {
showNotification('❌ 錯誤', '無法執行診斷: ' + error, true);
showNotification('❌ 錯誤', '無法執行診斷', true);
}
}
@@ -1194,6 +1194,26 @@
return labels[stage] || stage || '未分類';
}
function displayEnvName(env) {
const labels = { uat: '測試站', prod: '正式站' };
return labels[env] || '指定環境';
}
function displayRefName(ref) {
const labels = { main: '正式版更新', master: '正式版更新', dev: '測試版更新', uat: '測試版更新', prod: '正式版更新' };
return labels[ref] || '服務更新';
}
function displayServiceName(name) {
const labels = {
'momo-pro-system': '主系統',
'momo-scheduler': '排程與匯入',
'momo-telegram-bot': '通知服務',
'momo-db': '資料服務'
};
return labels[name] || '服務元件';
}
function displayStatusText(status) {
const labels = {
ok: '正常',

View File

@@ -8,7 +8,7 @@
<section class="vendor-tools-page" data-vendor-tool="history">
<header class="vendor-tools-header">
<div>
<span class="vendor-tools-kicker"><i class="fas fa-boxes-stacked"></i> Vendor Stockout</span>
<span class="vendor-tools-kicker"><i class="fas fa-boxes-stacked"></i>供貨風險</span>
<h1>發送歷史</h1>
<p>回看缺貨處理紀錄,找出需要補救的供貨風險。</p>
</div>

View File

@@ -14,7 +14,7 @@
<div>
<div class="vendor-eyebrow momo-mono">
<i class="fas fa-box-open"></i>
VENDOR STOCKOUT
供貨風險
</div>
<h1 class="vendor-title">廠商缺貨</h1>
<p class="vendor-subtitle">

View File

@@ -80,8 +80,8 @@ def test_high_visibility_pages_use_traditional_chinese_labels():
assert "工作隊列" in combined
assert "覆蓋率流程" in combined
assert "同步部署" in combined
assert "部署監控" in combined
assert "最新部署流程" in combined
assert "服務更新監控" in combined
assert "最新更新流程" in combined
assert "執行環境正常" in combined
assert "視覺檢查" in combined
@@ -617,10 +617,27 @@ def test_utility_pages_keep_operator_copy_professional():
auto_import = (ROOT / "templates/auto_import_index.html").read_text(encoding="utf-8")
stockout_import = (ROOT / "templates/vendor_stockout_import_v2.html").read_text(encoding="utf-8")
vendor_import_js = (ROOT / "web/static/js/page-vendor-import.js").read_text(encoding="utf-8")
stockout_index = (ROOT / "templates/vendor_stockout_index_v2.html").read_text(encoding="utf-8")
stockout_history = (ROOT / "templates/vendor_stockout_history_v2.html").read_text(encoding="utf-8")
ai_calls = (ROOT / "templates/admin/ai_calls_dashboard.html").read_text(encoding="utf-8")
observability_labels = (ROOT / "templates/admin/_observability_labels.html").read_text(encoding="utf-8")
host_health = (ROOT / "templates/admin/host_health.html").read_text(encoding="utf-8")
cicd_dashboard = (ROOT / "templates/cicd_dashboard.html").read_text(encoding="utf-8")
observability_js = (ROOT / "web/static/js/observability-charts.js").read_text(encoding="utf-8")
combined = "\n".join([ppt_history, ppt_preview, auto_import, stockout_import, vendor_import_js, ai_calls, host_health, observability_js])
combined = "\n".join([
ppt_history,
ppt_preview,
auto_import,
stockout_import,
vendor_import_js,
stockout_index,
stockout_history,
ai_calls,
observability_labels,
host_health,
cicd_dashboard,
observability_js,
])
assert "簡報線上預覽" in ppt_preview
assert "下載簡報檔" in ppt_history
@@ -628,6 +645,11 @@ def test_utility_pages_keep_operator_copy_professional():
assert "缺少必要資料時,會先停止匯入" in stockout_import
assert "先選擇供應商缺貨 Excel 檔。" in vendor_import_js
assert "使用情境" in ai_calls
assert "全部使用情境" in ai_calls
assert "情境 × 知識命中矩陣" in ai_calls
assert "服務更新監控" in cicd_dashboard
assert "正式服務更新與可用性監控" in cicd_dashboard
assert "供貨風險" in stockout_index
assert "無服務資料 / 未連線" in host_health
assert "部署檢查已排入背景處理" in observability_js
@@ -638,6 +660,17 @@ def test_utility_pages_keep_operator_copy_professional():
"原始:",
"無模型資料",
"請選擇 Excel 檔案",
"Vendor Stockout",
"VENDOR STOCKOUT",
"全部呼叫端",
"呼叫端 ×",
"<code>{{ c.caller }}</code>",
"NIM Elephant",
"OpenRouter",
"GitLab 部署紀錄",
"${p.ref} @ ${p.sha}",
"error_log.substring",
"${escapeHtml(env.error)}",
"系統會去重",
"系統會拒絕",
"管線 ID",

View File

@@ -905,9 +905,9 @@ def test_visible_operations_pages_hide_internal_runtime_terms():
"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/ai_calls_dashboard.html": ["用量", "最近作戰素材", "情境 × 知識命中矩陣"],
"templates/admin/observability_overview.html": ["用量", "知識與工具矩陣"],
"templates/cicd_dashboard.html": ["部署流程", "部署歷史", "修復部署", "查看部署紀錄"],
"templates/cicd_dashboard.html": ["最新更新流程", "更新歷史", "修復服務", "查看更新紀錄"],
}
forbidden_by_path = {
"templates/ai_recommend.html": ["權杖:", "AI 模型", "分析模型", "AI 路徑", "Gemini 備援", "Ollama 主路徑"],

View File

@@ -11,9 +11,9 @@
ollama_111: '第三建議路徑',
gemini: '雲端備援',
claude: 'Claude',
nim: 'NIM',
openrouter: 'OpenRouter',
nim_via_elephant: 'NIM Elephant'
nim: '快速雲端建議',
openrouter: '外部備援路徑',
nim_via_elephant: '雲端加速備援'
};
function readJson(id, fallback) {