This commit is contained in:
@@ -402,7 +402,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '')
|
||||
# ==========================================
|
||||
# 系統版本與路徑
|
||||
# ==========================================
|
||||
SYSTEM_VERSION = "V10.612"
|
||||
SYSTEM_VERSION = "V10.613"
|
||||
LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log')
|
||||
public_url = PUBLIC_URL # 用於模板顯示
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# PChome 業績成長自動化作戰系統 — AI 競價情報模組 Single Source of Truth
|
||||
|
||||
> **最後更新**: 2026-06-16 (台北時間)
|
||||
> **狀態**: 🟢 四 AI Agent 自動化閉環已落地;LLM 路由紅線升級為 Ollama-first 三主機級聯;PChome 後台業績匯入韌性已補強;產品定位正名為「PChome 業績成長自動化作戰系統」;外部市場來源正規化層、自動同步、作戰清單與價格參考表優先讀取、CSV 備援預檢與前台操作入口已建立
|
||||
> **適用版本**: V10.612
|
||||
> **狀態**: 🟢 四 AI Agent 自動化閉環已落地;LLM 路由紅線升級為 Ollama-first 三主機級聯;PChome 後台業績匯入韌性已補強;產品定位正名為「PChome 業績成長自動化作戰系統」;外部市場來源正規化層、自動同步、作戰清單與價格參考表優先讀取、CSV 備援預檢、前台操作入口與高可見頁面繁中化守門已建立
|
||||
> **適用版本**: V10.613
|
||||
|
||||
---
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
- V10.610 起 `/api/ai/pchome-growth/opportunities` 優先讀取 `external_offers` 的自動同步資料;只有新資料層缺資料時才 fallback 舊 `competitor_prices`。API stats 會回傳資料來源計數,方便確認作戰清單是否已走新資料層。
|
||||
- V10.611 起 `/ai_intelligence` 是營運使用者主入口:頁首提供「今日作戰入口」,依序連到 PChome 成長作戰、補商品對應、MOMO 外部價格參考與外部報價預檢;作戰清單左側會直接顯示今日優先動作與資料來源摘要。
|
||||
- V10.612 起 `/api/ai/icaim/dashboard` 的「MOMO 外部價格參考」表格也優先讀 `external_offers`,缺資料才 fallback `competitor_prices`;價差與風險改採 PChome 視角,正數代表 PChome 比 MOMO 外部參考價高。
|
||||
- V10.613 起高可見前台頁面必須以繁體中文呈現:程式碼審查、AI 自動化健康檢查、PPT 產線與商品看板操作標籤不得使用英文工程標題或簡體字;測試需防止頁面文案退回英文。
|
||||
|
||||
## 零之一、12 Agent 決策信封(2026-05-24)
|
||||
|
||||
@@ -169,11 +170,11 @@ SQL漏斗(~300筆)
|
||||
- `/metrics` 匯出 `momo_ai_event_router_replay_total`。
|
||||
- `/metrics` 匯出 `momo_ai_autoheal_action_total` 與 `momo_ai_autoheal_duration_ms_count/sum/max`。
|
||||
- `/metrics` 在尚無事件時仍輸出 `momo_ai_*` zero-baseline series,讓 Prometheus/Grafana 重啟後可立即看到 metric names。
|
||||
- `/ai_automation_smoke` 提供登入後 smoke dashboard。
|
||||
- `/ai_automation_smoke` 提供登入後 AI 自動化健康檢查頁。
|
||||
- `/api/ai-automation/smoke` 提供 read-only JSON 狀態,不做外部網路呼叫。
|
||||
- Smoke API 會將最近快檢結果保存到 JSONL,dashboard 顯示最近狀態趨勢。
|
||||
- Smoke history 支援 JSONL 匯出、清理與每日 OK / Warning / Critical 摘要。
|
||||
- Smoke 每日摘要支援手動 Telegram 推播,並由 `momo-scheduler` 每日 09:10 呼叫 `run_ai_smoke_daily_summary_task()`。
|
||||
- 健康檢查 API 會將最近檢查結果保存到 JSONL,頁面顯示最近狀態趨勢。
|
||||
- 健康檢查歷史支援 JSONL 匯出、清理與每日「正常 / 注意 / 嚴重」摘要。
|
||||
- 健康檢查每日摘要支援手動 Telegram 推播,並由 `momo-scheduler` 每日 09:10 呼叫 `run_ai_smoke_daily_summary_task()`。
|
||||
- Grafana provisioning 新增 `docker/grafana/provisioning/dashboards/json/ai-automation-overview.json`,觀測 EventRouter dispatch/latency、safe action、Telegram replay 與 AutoHeal action/duration。
|
||||
- Active monitoring stack 使用 `monitoring/prometheus.yml` 的 `momo-app` job scrape `momo-pro-system:80/metrics`;Prometheus container 需加入 `momo-network`。
|
||||
- Active Blackbox HTTP targets 必須探測 `/health`(188 stack 目前 `https://mo.wooo.work/health` 與 `http://momo-pro-system:80/health`;110 gateway stack 目前 `https://mo.wooo.work/health`),不可探測 Dashboard 首頁 `/`,避免監控流量觸發重型 DB 查詢。
|
||||
|
||||
@@ -226,3 +226,10 @@
|
||||
- 表格 stats 新增 `competitor_data_source_counts`,前端 footer 顯示「自動同步資料層 / 舊比價快取」各幾筆;每列狀態會顯示「自動同步」或「舊資料」。
|
||||
- 價差與高風險統計改採 PChome 視角:正數代表 PChome 比 MOMO 外部參考價高,才列入需檢查價格。
|
||||
- 下一步:把 Hermes / ElephantAlpha / AI product pick 等後端價格分析逐步改讀 `external_offers`,讓告警與頁面使用同一份資料來源。
|
||||
|
||||
## 16. 2026-06-16 V10.613 高可見頁面繁中化守門
|
||||
|
||||
- 使用者要求所有內容與頁面必須使用繁體中文;工程內部變數、CSS class、API key 名稱可保留英文,但使用者可見標題、按鈕、狀態與說明不得用英文工程語。
|
||||
- 已把 `/code-review/` 顯示文字改成「AI 程式碼審查」「流程進度」「程式碼審查完成」,並將 OpenClaw 模型顯示改成「Ollama 優先」,避免前台誤以為 Gemini 是主路徑。
|
||||
- 已把 AI 自動化健康檢查頁改為白話繁中命名,狀態顯示改成「正常 / 注意 / 嚴重 / 產生時間」。
|
||||
- 已把 PPT 觀測台與商品看板高可見英文標籤改成「產線健康度 / 工作隊列 / 視覺問題 / 產線控制台 / 覆蓋率流程」,並新增測試防回歸。
|
||||
|
||||
@@ -233,7 +233,7 @@
|
||||
</section>
|
||||
<section class="ppt-health-board" aria-label="PPT 產線健康總覽">
|
||||
<div class="ppt-health-main is-{{ pipeline_view.status }}">
|
||||
<div class="ppt-label">Pipeline Health</div>
|
||||
<div class="ppt-label">產線健康度</div>
|
||||
<h2>{{ pipeline_view.title }}</h2>
|
||||
<p>{{ pipeline_view.message }}</p>
|
||||
<div class="ppt-health-facts">
|
||||
@@ -260,7 +260,7 @@
|
||||
<section class="ppt-action-queue" id="ppt-action-queue" data-ppt-action-queue aria-label="PPT 工作隊列">
|
||||
<div class="ppt-workbench-head">
|
||||
<div>
|
||||
<div class="ppt-label">Action Queue</div>
|
||||
<div class="ppt-label">工作隊列</div>
|
||||
<h2 class="ppt-panel-title">接下來要處理的事</h2>
|
||||
</div>
|
||||
<small class="text-muted">把缺漏、預覽、視覺 QA、DB 寫入集中成工作隊列</small>
|
||||
@@ -315,7 +315,7 @@
|
||||
<section class="ppt-issue-board" id="ppt-issue-board" aria-label="視覺問題追蹤">
|
||||
<div class="ppt-workbench-head">
|
||||
<div>
|
||||
<div class="ppt-label">Vision Findings</div>
|
||||
<div class="ppt-label">視覺問題</div>
|
||||
<h2 class="ppt-panel-title">視覺問題追蹤</h2>
|
||||
</div>
|
||||
<div class="ppt-issue-metrics">
|
||||
@@ -368,7 +368,7 @@
|
||||
data-report-types="{{ auto_generation_missing_report_types | join(',') }}">
|
||||
<div class="ppt-panel-head">
|
||||
<div>
|
||||
<div class="ppt-label">Production Command Center</div>
|
||||
<div class="ppt-label">產線控制台</div>
|
||||
<h2 class="ppt-panel-title">簡報產線控制台</h2>
|
||||
</div>
|
||||
<div class="ppt-panel-actions">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends 'ewoooc_base.html' %}
|
||||
|
||||
{% block title %}AI 自動化 Smoke Dashboard - EwoooC{% endblock %}
|
||||
{% block title %}AI 自動化健康檢查 - EwoooC{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
<style>
|
||||
@@ -127,8 +127,8 @@
|
||||
<div class="smoke-hero mb-4">
|
||||
<div class="d-flex flex-column flex-lg-row justify-content-between gap-3 position-relative" style="z-index: 1;">
|
||||
<div>
|
||||
<span class="smoke-pill mb-3"><i class="fas fa-robot"></i> FOUR-AGENT CONTROL PLANE</span>
|
||||
<h1 class="fw-bold mb-2">AI 自動化 Smoke Dashboard</h1>
|
||||
<span class="smoke-pill mb-3"><i class="fas fa-robot"></i>四 Agent 控制面</span>
|
||||
<h1 class="fw-bold mb-2">AI 自動化健康檢查</h1>
|
||||
<p class="mb-0 text-muted">快速確認 EventRouter、AutoHeal、NemoTron、OpenClaw 與 ElephantAlpha 的閉環狀態。</p>
|
||||
</div>
|
||||
<div class="text-lg-end">
|
||||
@@ -151,17 +151,17 @@
|
||||
</div>
|
||||
|
||||
<div class="row g-3 mb-4" id="summaryCards">
|
||||
<div class="col-md-3"><div class="card smoke-card"><div class="card-body"><div class="text-muted small">OK</div><div class="h3 mb-0" id="okCount">-</div></div></div></div>
|
||||
<div class="col-md-3"><div class="card smoke-card"><div class="card-body"><div class="text-muted small">Warning</div><div class="h3 mb-0" id="warningCount">-</div></div></div></div>
|
||||
<div class="col-md-3"><div class="card smoke-card"><div class="card-body"><div class="text-muted small">Critical</div><div class="h3 mb-0" id="criticalCount">-</div></div></div></div>
|
||||
<div class="col-md-3"><div class="card smoke-card"><div class="card-body"><div class="text-muted small">Generated</div><div class="fs-6 fw-semibold" id="generatedAt">-</div></div></div></div>
|
||||
<div class="col-md-3"><div class="card smoke-card"><div class="card-body"><div class="text-muted small">正常</div><div class="h3 mb-0" id="okCount">-</div></div></div></div>
|
||||
<div class="col-md-3"><div class="card smoke-card"><div class="card-body"><div class="text-muted small">注意</div><div class="h3 mb-0" id="warningCount">-</div></div></div></div>
|
||||
<div class="col-md-3"><div class="card smoke-card"><div class="card-body"><div class="text-muted small">嚴重</div><div class="h3 mb-0" id="criticalCount">-</div></div></div></div>
|
||||
<div class="col-md-3"><div class="card smoke-card"><div class="card-body"><div class="text-muted small">產生時間</div><div class="fs-6 fw-semibold" id="generatedAt">-</div></div></div></div>
|
||||
</div>
|
||||
|
||||
<div class="card smoke-card mb-4">
|
||||
<div class="card-body">
|
||||
<div class="d-flex flex-column flex-md-row justify-content-between gap-2 mb-3">
|
||||
<div>
|
||||
<h5 class="mb-1">最近 Smoke 趨勢</h5>
|
||||
<h5 class="mb-1">最近健康檢查趨勢</h5>
|
||||
<div class="text-muted small">每次 API 快檢會保存一筆精簡紀錄,保留最近 200 筆。</div>
|
||||
</div>
|
||||
<div class="text-muted small" id="historySummary">等待資料...</div>
|
||||
@@ -175,7 +175,7 @@
|
||||
<div class="d-flex flex-column flex-md-row justify-content-between gap-2 mb-3">
|
||||
<div>
|
||||
<h5 class="mb-1">每日摘要</h5>
|
||||
<div class="text-muted small">依最近保存紀錄彙整,方便快速觀察是否有連續 warning / critical。</div>
|
||||
<div class="text-muted small">依最近保存紀錄彙整,方便快速觀察是否有連續注意或嚴重狀態。</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
@@ -183,10 +183,10 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th>日期</th>
|
||||
<th>OK</th>
|
||||
<th>Warning</th>
|
||||
<th>Critical</th>
|
||||
<th>Total</th>
|
||||
<th>正常</th>
|
||||
<th>注意</th>
|
||||
<th>嚴重</th>
|
||||
<th>總計</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="dailySummaryRows">
|
||||
@@ -203,7 +203,7 @@
|
||||
{% block extra_js %}
|
||||
<script>
|
||||
function badge(status) {
|
||||
const label = {ok: 'OK', warning: 'WARNING', critical: 'CRITICAL'}[status] || status;
|
||||
const label = {ok: '正常', warning: '注意', critical: '嚴重'}[status] || status;
|
||||
return `<span class="status-badge status-${status}">${label}</span>`;
|
||||
}
|
||||
|
||||
@@ -244,7 +244,7 @@ function renderTrend(history) {
|
||||
const recent = history.recent || [];
|
||||
const counts = history.counts || {ok: 0, warning: 0, critical: 0};
|
||||
document.getElementById('historySummary').textContent =
|
||||
`OK ${counts.ok || 0} / Warning ${counts.warning || 0} / Critical ${counts.critical || 0}`;
|
||||
`正常 ${counts.ok || 0} / 注意 ${counts.warning || 0} / 嚴重 ${counts.critical || 0}`;
|
||||
|
||||
const strip = document.getElementById('trendStrip');
|
||||
if (!recent.length) {
|
||||
@@ -289,7 +289,7 @@ async function loadSmoke() {
|
||||
document.getElementById('checkGrid').innerHTML = `
|
||||
<div class="card smoke-card">
|
||||
<div class="card-body">
|
||||
<h5>Smoke API</h5>
|
||||
<h5>健康檢查 API</h5>
|
||||
<p class="text-danger">讀取失敗:${escapeHtml(err.message)}</p>
|
||||
</div>
|
||||
</div>`;
|
||||
@@ -301,7 +301,7 @@ async function loadSmoke() {
|
||||
|
||||
document.getElementById('refreshBtn').addEventListener('click', loadSmoke);
|
||||
document.getElementById('clearHistoryBtn').addEventListener('click', async () => {
|
||||
if (!confirm('確定要清理 AI Smoke 趨勢紀錄?這只會刪除本頁 JSONL history,不會影響 DB 或事件資料。')) {
|
||||
if (!confirm('確定要清理 AI 健康檢查趨勢紀錄?這只會刪除本頁健康檢查紀錄檔,不會影響 DB 或事件資料。')) {
|
||||
return;
|
||||
}
|
||||
const btn = document.getElementById('clearHistoryBtn');
|
||||
@@ -310,7 +310,7 @@ document.getElementById('clearHistoryBtn').addEventListener('click', async () =>
|
||||
const res = await fetchWithCSRF('/api/ai-automation/smoke/history/clear', {method: 'POST'});
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
const data = await res.json();
|
||||
showToast(`已清理 ${data.cleared || 0} 筆 Smoke 趨勢紀錄`, 'success');
|
||||
showToast(`已清理 ${data.cleared || 0} 筆健康檢查趨勢紀錄`, 'success');
|
||||
await loadSmoke();
|
||||
} catch (err) {
|
||||
showToast(`清理失敗:${err.message}`, 'error');
|
||||
@@ -325,7 +325,7 @@ document.getElementById('sendSummaryBtn').addEventListener('click', async () =>
|
||||
const res = await fetchWithCSRF('/api/ai-automation/smoke/daily-summary/send', {method: 'POST'});
|
||||
const data = await res.json();
|
||||
if (!res.ok) throw new Error((data.telegram?.errors || []).join(', ') || `HTTP ${res.status}`);
|
||||
showToast(`AI Smoke 每日摘要已推播:sent=${data.telegram?.sent || 0}`, 'success');
|
||||
showToast(`AI 健康檢查每日摘要已推播:已送出 ${data.telegram?.sent || 0} 則`, 'success');
|
||||
} catch (err) {
|
||||
showToast(`推播失敗:${err.message}`, 'error');
|
||||
} finally {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% extends "ewoooc_base.html" %}
|
||||
|
||||
{% block title %}AI Code Review - EwoooC{% endblock %}
|
||||
{% block title %}AI 程式碼審查 - EwoooC{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
<style>
|
||||
@@ -276,9 +276,9 @@
|
||||
<!-- Top Bar -->
|
||||
<div class="topbar">
|
||||
<span>🔍</span>
|
||||
<h1>AI Code Review</h1>
|
||||
<span class="badge">EwoooC · Post-Deploy Pipeline</span>
|
||||
<div id="liveDot" class="live-dot idle" title="Pipeline 狀態"></div>
|
||||
<h1>AI 程式碼審查</h1>
|
||||
<span class="badge">EwoooC · 部署後檢查流程</span>
|
||||
<div id="liveDot" class="live-dot idle" title="流程狀態"></div>
|
||||
</div>
|
||||
|
||||
<div class="layout">
|
||||
@@ -288,16 +288,16 @@
|
||||
|
||||
<!-- Pipeline Steps -->
|
||||
<div class="card">
|
||||
<div class="card-header">🤖 Pipeline 進度
|
||||
<div class="card-header">🤖 流程進度
|
||||
<span id="pipelineId" style="font-size:11px;color:var(--muted);margin-left:auto;font-family:monospace"></span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="pipeline" id="stepsContainer">
|
||||
<div class="step" id="step-1"><div class="step-num">1</div><div class="step-info"><div class="step-name">讀取變更檔案</div><div class="step-agent">system</div></div></div>
|
||||
<div class="step" id="step-1"><div class="step-num">1</div><div class="step-info"><div class="step-name">讀取變更檔案</div><div class="step-agent">系統</div></div></div>
|
||||
<div class="step" id="step-2"><div class="step-num">2</div><div class="step-info"><div class="step-name">Hermes 程式碼掃描</div><div class="step-agent">Hermes · hermes3:latest</div></div></div>
|
||||
<div class="step" id="step-3"><div class="step-num">3</div><div class="step-info"><div class="step-name">OpenClaw 架構評估</div><div class="step-agent">OpenClaw · Gemini 2.5</div></div></div>
|
||||
<div class="step" id="step-3"><div class="step-num">3</div><div class="step-info"><div class="step-name">OpenClaw 架構評估</div><div class="step-agent">OpenClaw · Ollama 優先</div></div></div>
|
||||
<div class="step" id="step-4"><div class="step-num">4</div><div class="step-info"><div class="step-name">Elephant Alpha 決策</div><div class="step-agent">Elephant Alpha · 100B</div></div></div>
|
||||
<div class="step" id="step-5"><div class="step-num">5</div><div class="step-info"><div class="step-name">NemoTron 行動派遣</div><div class="step-agent">NemoTron · Dispatcher</div></div></div>
|
||||
<div class="step" id="step-5"><div class="step-num">5</div><div class="step-info"><div class="step-name">NemoTron 行動派遣</div><div class="step-agent">NemoTron · 派遣器</div></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -307,10 +307,10 @@
|
||||
<div class="card-header">📊 問題嚴重度分佈</div>
|
||||
<div class="card-body">
|
||||
<div class="sev-grid">
|
||||
<div class="sev-cell sev-critical"><div class="num" id="cnt-critical">—</div><div class="lbl">🔴 CRITICAL</div></div>
|
||||
<div class="sev-cell sev-high"><div class="num" id="cnt-high">—</div><div class="lbl">🟠 HIGH</div></div>
|
||||
<div class="sev-cell sev-medium"><div class="num" id="cnt-medium">—</div><div class="lbl">🟡 MEDIUM</div></div>
|
||||
<div class="sev-cell sev-low"><div class="num" id="cnt-low">—</div><div class="lbl">🟢 LOW</div></div>
|
||||
<div class="sev-cell sev-critical"><div class="num" id="cnt-critical">—</div><div class="lbl">🔴 最高</div></div>
|
||||
<div class="sev-cell sev-high"><div class="num" id="cnt-high">—</div><div class="lbl">🟠 高</div></div>
|
||||
<div class="sev-cell sev-medium"><div class="num" id="cnt-medium">—</div><div class="lbl">🟡 中</div></div>
|
||||
<div class="sev-cell sev-low"><div class="num" id="cnt-low">—</div><div class="lbl">🟢 低</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -354,7 +354,7 @@
|
||||
</div>
|
||||
<div class="card-body" style="padding:0">
|
||||
<div id="findingsTable">
|
||||
<div class="empty"><div class="icon">🔍</div><div>等待 Code Review 完成...</div></div>
|
||||
<div class="empty"><div class="icon">🔍</div><div>等待程式碼審查完成...</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -392,6 +392,9 @@
|
||||
{% block extra_js %}
|
||||
<script>
|
||||
const SEV_ORDER = { CRITICAL: 0, HIGH: 1, MEDIUM: 2, LOW: 3 };
|
||||
const SEV_LABEL = { CRITICAL: '最高', HIGH: '高', MEDIUM: '中', LOW: '低' };
|
||||
const PRIORITY_LABEL = { critical: '最高', high: '高', medium: '中', low: '低' };
|
||||
const DEPLOY_TYPE_LABEL = { sync: '同步部署', rebuild: '重建部署', manual: '手動觸發' };
|
||||
let _polling = null;
|
||||
let _lastPipelineId = null;
|
||||
|
||||
@@ -452,7 +455,7 @@ function renderFindings(findings) {
|
||||
<thead><tr><th>嚴重度</th><th>類型</th><th>檔案 / 位置</th><th>問題說明</th><th>修復建議</th></tr></thead>
|
||||
<tbody>${sorted.map(f => `
|
||||
<tr>
|
||||
<td><span class="badge-sev badge-${f.severity}">${f.severity}</span></td>
|
||||
<td><span class="badge-sev badge-${f.severity}">${SEV_LABEL[f.severity] || f.severity || '?'}</span></td>
|
||||
<td><span class="badge-type">${f.type||'?'}</span></td>
|
||||
<td><code>${(f.file||'').split('/').pop()}</code><br><span style="font-size:11px;color:var(--muted)">${f.line_hint||''}</span></td>
|
||||
<td>${escHtml(f.description||'')}</td>
|
||||
@@ -474,11 +477,12 @@ function renderEA(ea, autoFix) {
|
||||
}
|
||||
const priColors = { critical: 'var(--red)', high: 'var(--orange)', medium: 'var(--yellow)', low: 'var(--green)' };
|
||||
const col = priColors[ea.priority] || 'var(--blue)';
|
||||
const priorityLabel = PRIORITY_LABEL[ea.priority] || ea.priority || '待確認';
|
||||
const fixFiles = (ea.fix_files||[]).map(f=>`<code>${f}</code>`).join(' ');
|
||||
document.getElementById('eaDecision').innerHTML = `
|
||||
<div class="ea-box ${ea.priority}">
|
||||
<div style="display:flex;align-items:center;gap:10px">
|
||||
<div class="ea-priority" style="color:${col}">${ea.priority.toUpperCase()}</div>
|
||||
<div class="ea-priority" style="color:${col}">${priorityLabel}</div>
|
||||
<div>${autoFix ? '🔧 <b>自動修復已觸發(AiderHeal)</b>' : (ea.human_review_needed ? '👁 <b>需人工審查</b>' : '✅ <b>無需修復</b>')}</div>
|
||||
</div>
|
||||
<div class="ea-reasoning">${escHtml(ea.reasoning||'')}</div>
|
||||
@@ -493,9 +497,9 @@ function renderCommitInfo(state) {
|
||||
const files = (state.changed_files||[]).slice(0,5).map(f=>`<code>${f.split('/').pop()}</code>`).join(' ');
|
||||
const more = (state.changed_files||[]).length > 5 ? `<span style="color:var(--muted)">+${state.changed_files.length-5}</span>` : '';
|
||||
el.innerHTML = `
|
||||
<div><b>Commit</b> <code>${state.commit_sha.slice(0,8)}</code></div>
|
||||
<div><b>Branch</b> <code>${state.branch||'?'}</code></div>
|
||||
<div><b>模式</b> ${state.deploy_type||'sync'}</div>
|
||||
<div><b>提交</b> <code>${state.commit_sha.slice(0,8)}</code></div>
|
||||
<div><b>分支</b> <code>${state.branch||'?'}</code></div>
|
||||
<div><b>模式</b> ${DEPLOY_TYPE_LABEL[state.deploy_type] || state.deploy_type || '同步部署'}</div>
|
||||
<div><b>變更</b> ${files} ${more}</div>`;
|
||||
}
|
||||
|
||||
@@ -508,9 +512,9 @@ function renderStatusBar(state) {
|
||||
el.style.display = 'block';
|
||||
el.className = state.status;
|
||||
const msgs = {
|
||||
running: `⟳ <b>Pipeline 執行中</b> — Step ${state.current_step}/5`,
|
||||
completed: `✅ <b>Code Review 完成</b> — ${state.message||''}`,
|
||||
error: `❌ <b>Pipeline 失敗</b> — ${escHtml(state.message||'')}`,
|
||||
running: `⟳ <b>流程執行中</b> — 第 ${state.current_step}/5 步`,
|
||||
completed: `✅ <b>程式碼審查完成</b> — ${state.message||''}`,
|
||||
error: `❌ <b>流程失敗</b> — ${escHtml(state.message||'')}`,
|
||||
skipped: `⏭ <b>已略過</b> — ${escHtml(state.message||'')}`,
|
||||
};
|
||||
el.innerHTML = msgs[state.status] || '';
|
||||
@@ -554,15 +558,15 @@ function loadHistoryItem(idx) {
|
||||
const files = (h.changed_files||[]).slice(0,5).map(f=>`<code>${f.split('/').pop()}</code>`).join(' ');
|
||||
const more = (h.changed_files||[]).length > 5 ? `<span style="color:var(--muted)">+${h.changed_files.length-5}</span>` : '';
|
||||
document.getElementById('commitInfo').innerHTML = `
|
||||
<div><b>Commit</b> <code>${h.commit_sha}</code></div>
|
||||
<div><b>Branch</b> <code>${h.branch||'?'}</code></div>
|
||||
<div><b>提交</b> <code>${h.commit_sha}</code></div>
|
||||
<div><b>分支</b> <code>${h.branch||'?'}</code></div>
|
||||
<div><b>時間</b> ${h.created_at.slice(0,16).replace('T',' ')}</div>
|
||||
<div><b>變更</b> ${files} ${more}</div>`;
|
||||
document.getElementById('pipelineId').textContent = (h.pipeline_id||'').slice(-14);
|
||||
const sBar = document.getElementById('statusBar');
|
||||
sBar.style.display = 'block';
|
||||
sBar.className = 'completed';
|
||||
sBar.innerHTML = `✅ <b>歷史記錄</b> — Commit ${h.commit_sha}${h.auto_fix ? ' 🔧 已自動修復' : ''}`;
|
||||
sBar.innerHTML = `✅ <b>歷史記錄</b> — 提交 ${h.commit_sha}${h.auto_fix ? ' 🔧 已自動修復' : ''}`;
|
||||
renderFindings(h.findings || []);
|
||||
renderOpenClaw(h.openclaw_report || '');
|
||||
renderEA(h.ea_decision || {}, h.auto_fix || false);
|
||||
|
||||
@@ -351,7 +351,7 @@
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item" href="/ai_automation_smoke">
|
||||
<i class="fas fa-heartbeat me-2"></i>AI 自動化 Smoke
|
||||
<i class="fas fa-heartbeat me-2"></i>AI 自動化健康檢查
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
</div>
|
||||
<div class="dashboard-decision-workbench" aria-label="提升決策支援覆蓋率工作流">
|
||||
<div class="dashboard-decision-workbench__head">
|
||||
<span class="dashboard-backfill-label momo-mono">COVERAGE WORKFLOW</span>
|
||||
<span class="dashboard-backfill-label momo-mono">覆蓋率流程</span>
|
||||
<strong>提升決策支援覆蓋率</strong>
|
||||
<em>先處理最能轉成可決策資料的隊列,避免盲目降低門檻。</em>
|
||||
</div>
|
||||
|
||||
@@ -403,11 +403,11 @@ def test_ppt_audit_history_shows_ppt_schedule_and_db_runs(client, monkeypatch):
|
||||
assert text in html
|
||||
assert 'ppt_generation_runs' in html
|
||||
assert '每日日報' in html
|
||||
assert 'Pipeline Health' in html
|
||||
assert '產線健康度' in html
|
||||
assert '排程節奏' in html
|
||||
assert 'DB 寫入' in html
|
||||
assert '線上預覽' in html
|
||||
assert 'Action Queue' in html
|
||||
assert '工作隊列' in html
|
||||
assert '接下來要處理的事' in html
|
||||
assert '異常優先' in html
|
||||
assert '產線錯誤:缺少資料來源' in html
|
||||
|
||||
@@ -50,6 +50,54 @@ def test_frontend_v2_shell_uses_real_runtime_context():
|
||||
assert all(marker not in combined for marker in forbidden_markers)
|
||||
|
||||
|
||||
def test_high_visibility_pages_use_traditional_chinese_labels():
|
||||
page_paths = [
|
||||
"templates/code_review.html",
|
||||
"templates/ai_automation_smoke.html",
|
||||
"templates/admin/ppt_audit_history.html",
|
||||
"templates/dashboard_v2.html",
|
||||
"templates/components/_navbar.html",
|
||||
]
|
||||
combined = "\n".join((ROOT / path).read_text(encoding="utf-8") for path in page_paths)
|
||||
|
||||
assert "AI 程式碼審查" in combined
|
||||
assert "等待程式碼審查完成" in combined
|
||||
assert "AI 自動化健康檢查" in combined
|
||||
assert "四 Agent 控制面" in combined
|
||||
assert "產線健康度" in combined
|
||||
assert "工作隊列" in combined
|
||||
assert "覆蓋率流程" in combined
|
||||
assert "NemoTron · 派遣器" in combined
|
||||
assert "同步部署" in combined
|
||||
|
||||
forbidden_visible_text = [
|
||||
"AI Code Review",
|
||||
"Smoke Dashboard",
|
||||
"FOUR-AGENT CONTROL PLANE",
|
||||
"Warning</",
|
||||
"Critical</",
|
||||
"Generated</",
|
||||
"Action Queue",
|
||||
"Pipeline Health",
|
||||
"Vision Findings",
|
||||
"Production Command Center",
|
||||
"COVERAGE WORKFLOW",
|
||||
"OpenClaw · Gemini 2.5",
|
||||
"等待 Code Review 完成",
|
||||
"Pipeline 執行中",
|
||||
"Pipeline 失敗",
|
||||
"Step ${state.current_step}",
|
||||
"AI 自動化 Smoke",
|
||||
"NemoTron · Dispatcher",
|
||||
"<b>Commit</b>",
|
||||
"<b>Branch</b>",
|
||||
"${f.severity}</span>",
|
||||
"ea.priority.toUpperCase",
|
||||
]
|
||||
for marker in forbidden_visible_text:
|
||||
assert marker not in combined
|
||||
|
||||
|
||||
def test_topbar_observability_indicator_is_cached_and_timeout_bounded():
|
||||
base_js = (ROOT / "web/static/js/ewoooc-base.js").read_text(encoding="utf-8")
|
||||
observability_route = (ROOT / "routes/admin_observability_routes.py").read_text(encoding="utf-8")
|
||||
|
||||
Reference in New Issue
Block a user