fix: sanitize observability and review UI copy
All checks were successful
CD Pipeline / deploy (push) Successful in 1m12s
All checks were successful
CD Pipeline / deploy (push) Successful in 1m12s
This commit is contained in:
@@ -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: '活動促銷',
|
||||
|
||||
@@ -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,顯示費用
|
||||
|
||||
@@ -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, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
function updateStats(logs) {
|
||||
const lines = logs.split('\n').filter(l => l.trim());
|
||||
document.getElementById('total-lines').textContent = lines.length;
|
||||
|
||||
Reference in New Issue
Block a user