fix: sanitize observability and review UI copy
All checks were successful
CD Pipeline / deploy (push) Successful in 1m12s

This commit is contained in:
ogt
2026-06-25 15:18:15 +08:00
parent bbf6a97d77
commit aadbce73e5
25 changed files with 387 additions and 169 deletions

View File

@@ -6,9 +6,9 @@
'use strict';
const providerLabelMap = {
gcp_ollama: '主力 Ollama',
ollama_secondary: '備援 Ollama',
ollama_111: '111 Ollama',
gcp_ollama: '主力 AI 模型',
ollama_secondary: '備援 AI 模型',
ollama_111: '第三 AI 模型',
gemini: 'Gemini',
claude: 'Claude',
nim: 'NIM',
@@ -574,7 +574,7 @@
};
window.triggerAiderHeal = async function triggerAiderHeal(pptxFilename, errorMsg, triggerButton) {
if (!confirm(`觸發 AiderHeal 自動修復?\n\n檔案:${pptxFilename}\n錯誤${(errorMsg || '').substring(0, 200)}`)) return;
if (!confirm(`觸發修復流程\n\n檔案:${pptxFilename}\n問題${(errorMsg || '').substring(0, 200)}`)) return;
const statusNode = document.querySelector('[data-ppt-auto-status]');
const originalHtml = triggerButton ? triggerButton.innerHTML : '';
if (triggerButton) {
@@ -583,7 +583,7 @@
}
if (statusNode) {
statusNode.classList.add('is-working');
statusNode.textContent = `${pptxFilename || 'PPT'} 正在排入 AiderHeal 背景修復。`;
statusNode.textContent = `${pptxFilename || 'PPT'} 正在排入背景修復流程`;
}
try {
const response = await postJson('/observability/ppt_audit/trigger_aider_heal', {
@@ -602,7 +602,7 @@
}
if (statusNode) {
statusNode.classList.remove('is-working');
statusNode.textContent = data.message || 'AiderHeal 已排入背景執行,完成後會由 Telegram/Gitea/CD 結果回報。';
statusNode.textContent = data.message || '修復流程已排入背景執行,完成後會回報處理結果。';
}
if (window.refreshPptAiderHealStatus) {
window.refreshPptAiderHealStatus();
@@ -615,7 +615,7 @@
}
if (statusNode) {
statusNode.classList.remove('is-working');
statusNode.textContent = 'AiderHeal 派工失敗,請稍後再試或查看系統日誌。';
statusNode.textContent = '修復派工失敗,請稍後再試或查看系統日誌。';
}
}
};
@@ -629,10 +629,10 @@
const title = panel.querySelector('[data-ppt-aider-status-title]');
const meta = panel.querySelector('[data-ppt-aider-status-meta]');
const list = panel.querySelector('[data-ppt-aider-job-list]');
if (title) title.textContent = count > 0 ? `AiderHeal 執行中 · ${count}` : 'AiderHeal 待命';
if (title) title.textContent = count > 0 ? `修復流程執行中 · ${count}` : '修復流程待命';
if (meta) {
meta.textContent = count > 0
? '修復完成後會由 Telegram/Gitea/CD 回報。'
? '修復完成後會回報處理結果。'
: '有問題的審核紀錄可直接一鍵派工。';
}
if (list) {
@@ -687,7 +687,7 @@
const issueText = `${Number(summary.audited_count || 0)} 份 / ${Number(summary.total_issues || 0)} 問題`;
const errorText = Number(summary.error_count || 0) > 0
? `錯誤 ${Number(summary.error_count || 0)}`
: '無 runtime error';
: '無執行錯誤';
list.innerHTML = `
<div class="ppt-vision-job">
<span>${escapeHtml(timestamp)}</span>
@@ -804,7 +804,7 @@
});
if (pageStatus) {
pageStatus.classList.add('is-working');
pageStatus.textContent = `已準備送出 ${filenames.length} 份簡報進行視覺 QA完成後會寫入資料庫`;
pageStatus.textContent = `已準備送出 ${filenames.length} 份簡報進行視覺 QA完成後會更新審核紀錄`;
}
try {
const response = await postJson('/observability/ppt_audit/run_vision', {
@@ -823,8 +823,8 @@
});
if (pageStatus) {
pageStatus.textContent = data.status === 'already_running'
? '視覺 QA 已在執行中,請稍後重新整理查看資料庫結果。'
: `視覺 QA 已排入 ${filenames.length} 份簡報;審核結果會寫入 ppt_audit_results`;
? '視覺 QA 已在執行中,請稍後重新整理查看審核結果。'
: `視覺 QA 已排入 ${filenames.length} 份簡報;完成後會更新審核結果。`;
}
if (window.refreshPptVisionStatus) {
window.refreshPptVisionStatus();
@@ -1061,7 +1061,7 @@
competitor_price: '競品價格',
sales_anomaly: '業績異常',
budget_strategy: '預算策略',
rag_feedback: 'RAG 反饋',
rag_feedback: '知識反饋',
ppt_audit: 'PPT 審核',
quality_issue: '品質問題',
promotion: '活動促銷',

View File

@@ -283,7 +283,7 @@
.then(data => {
if (!data.success || !data.data) return;
const status = data.data;
updateAIStatusBadge('ollamaStatus', 'fas fa-server', 'Ollama 主路徑', status.ollama?.connected, 'ar-status--ok');
updateAIStatusBadge('ollamaStatus', 'fas fa-server', 'AI 模型主路徑', status.ollama?.connected, 'ar-status--ok');
updateAIStatusBadge('geminiStatus', 'fab fa-google', 'Gemini 備援', status.gemini?.connected, 'ar-status--info');
updateOllamaModels(status.ollama?.available_models || []);
})
@@ -422,7 +422,7 @@
document.getElementById('generatedCopy').innerHTML = formattedCopy;
// 組合元資料顯示
let metaHtml = `<i class="fas fa-robot me-1"></i>${data.data.provider === 'gemini' ? 'Gemini 備援' : 'Ollama 主路徑'}${data.data.model}`;
let metaHtml = `<i class="fas fa-robot me-1"></i>${data.data.provider === 'gemini' ? 'Gemini 備援' : 'AI 模型主路徑'}${data.data.model}`;
metaHtml += ` | <i class="fas fa-clock me-1"></i>耗時:${data.data.duration}`;
// 如果是 Gemini顯示費用

View File

@@ -68,11 +68,11 @@ function displayLogs() {
function formatLogLine(line) {
if (!line.trim()) return '';
let cls = '';
let f = line;
let f = escapeLogText(line);
const u = line.toUpperCase();
if (u.includes('ERROR')) { cls = 'error'; f = line.replace(/ERROR/gi, '<span class="log-error">ERROR</span>'); }
else if (u.includes('WARNING')) { cls = 'warning'; f = line.replace(/WARNING/gi, '<span class="log-warning">WARNING</span>'); }
else if (u.includes('INFO')) { cls = 'info'; f = line.replace(/INFO/gi, '<span class="log-info">INFO</span>'); }
if (u.includes('ERROR')) { cls = 'error'; f = f.replace(/ERROR/gi, '<span class="log-error">ERROR</span>'); }
else if (u.includes('WARNING')) { cls = 'warning'; f = f.replace(/WARNING/gi, '<span class="log-warning">WARNING</span>'); }
else if (u.includes('INFO')) { cls = 'info'; f = f.replace(/INFO/gi, '<span class="log-info">INFO</span>'); }
f = f.replace(/(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})/g, '<span class="log-timestamp">$1</span>');
if (searchKeyword) {
const re = new RegExp(`(${searchKeyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi');
@@ -81,6 +81,15 @@ function formatLogLine(line) {
return `<div class="log-line ${cls}">${f}</div>`;
}
function escapeLogText(value) {
return String(value || '')
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
function updateStats(logs) {
const lines = logs.split('\n').filter(l => l.trim());
document.getElementById('total-lines').textContent = lines.length;