This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
================================================================================
|
||||
|
||||
【已完成】
|
||||
- V10.217 讓 `/observability/ppt_audit_history` 的 AiderHeal 派工改為非阻塞背景任務:頁面立即回「已排入」,修復工作在背景執行,避免瀏覽器與 Gunicorn worker 等 SSH、模型與 git push 到超時。
|
||||
- V10.216 修正 `/observability/ppt_audit_history` 的 AiderHeal 派工斷點:失敗簡報即使只有 `issues_found` 診斷摘要也能一鍵送修,並修正 `execute_code_fix` 參數與 dict 回傳解析,避免按鈕 400/500。
|
||||
- V10.215 強化 `/observability/ppt_audit_history` 視覺問題追蹤:將 `issues_found` 拆成投影片、問題類型、問題文字與回放入口,新增「視覺問題追蹤」面板,讓問題簡報能直接定位與預覽。
|
||||
- V10.213 補 `/observability/ppt_audit_history` 視覺 QA 診斷摘要:審核歷史與 Action Queue 直接顯示 `ppt_audit_results.issues_found` 的投影片問題,讓「有問題」可追查,不再只剩問題數或空白錯誤欄。
|
||||
|
||||
@@ -320,7 +320,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '')
|
||||
# ==========================================
|
||||
# 系統版本與路徑
|
||||
# ==========================================
|
||||
SYSTEM_VERSION = "V10.216"
|
||||
SYSTEM_VERSION = "V10.217"
|
||||
LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log')
|
||||
public_url = PUBLIC_URL # 用於模板顯示
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ Operation Ollama-First v5.0 / Phase 27 — Admin Observability Dashboard
|
||||
"""
|
||||
|
||||
import logging
|
||||
import threading
|
||||
from datetime import datetime, timedelta
|
||||
from flask import Blueprint, render_template, request, jsonify, send_file, url_for
|
||||
from sqlalchemy import text as sa_text
|
||||
@@ -1811,7 +1812,8 @@ def ppt_audit_trigger_aider_heal():
|
||||
結果會 git push 到 main 觸發 CD 自動部署。
|
||||
"""
|
||||
try:
|
||||
from services.aider_heal_executor import execute_code_fix
|
||||
from services import aider_heal_executor
|
||||
|
||||
data = request.get_json(silent=True) or {}
|
||||
error_msg = (data.get('error_msg') or '').strip()
|
||||
issue_summary = (data.get('issue_summary') or '').strip()
|
||||
@@ -1833,19 +1835,41 @@ def ppt_audit_trigger_aider_heal():
|
||||
'triggered_by': 'admin_observability',
|
||||
'issue_summary': issue_summary[:500],
|
||||
}
|
||||
result = execute_code_fix(
|
||||
error_type='ppt_vision_audit_failure',
|
||||
error_message=error_message,
|
||||
target_file='services/ppt_generator.py',
|
||||
context=context,
|
||||
)
|
||||
|
||||
def _heal_worker():
|
||||
try:
|
||||
result = aider_heal_executor.execute_code_fix(
|
||||
error_type='ppt_vision_audit_failure',
|
||||
error_message=error_message,
|
||||
target_file='services/ppt_generator.py',
|
||||
context=context,
|
||||
)
|
||||
logger.info(
|
||||
"[PPTAudit] AiderHeal 背景任務完成 | file=%s | ok=%s | message=%s",
|
||||
pptx_filename or '-',
|
||||
bool(result.get('success')),
|
||||
(result.get('message') or '')[:160],
|
||||
)
|
||||
except Exception:
|
||||
logger.exception(
|
||||
"[PPTAudit] AiderHeal 背景任務失敗 | file=%s",
|
||||
pptx_filename or '-',
|
||||
)
|
||||
|
||||
thread_key = ''.join(ch for ch in pptx_filename if ch.isalnum())[:24] or 'manual'
|
||||
threading.Thread(
|
||||
target=_heal_worker,
|
||||
daemon=True,
|
||||
name=f"ppt-aider-heal-{thread_key}",
|
||||
).start()
|
||||
|
||||
return jsonify({
|
||||
'ok': bool(result.get('success')),
|
||||
'action': result.get('action'),
|
||||
'message': result.get('message') or '已派出 AiderHeal',
|
||||
'commit_sha': result.get('commit_sha'),
|
||||
'reverted': bool(result.get('reverted')),
|
||||
})
|
||||
'ok': True,
|
||||
'status': 'queued',
|
||||
'action': 'CODE_FIX',
|
||||
'message': 'AiderHeal 已排入背景執行;完成後會由 Telegram/Gitea/CD 結果回報。',
|
||||
'target_file': 'services/ppt_generator.py',
|
||||
}), 202
|
||||
except Exception as e:
|
||||
return jsonify({'ok': False, 'error': f'{type(e).__name__}: {str(e)[:200]}'}), 500
|
||||
|
||||
|
||||
@@ -520,6 +520,7 @@ def test_ppt_audit_run_vision_queues_background_audit(client, monkeypatch):
|
||||
|
||||
def test_ppt_audit_trigger_aider_heal_accepts_issue_summary(client, monkeypatch):
|
||||
"""視覺 QA failed 常只有 issues_found;AiderHeal 應可吃診斷摘要派工。"""
|
||||
from routes import admin_observability_routes as mod
|
||||
from services import aider_heal_executor as svc
|
||||
|
||||
captured = {}
|
||||
@@ -536,6 +537,15 @@ def test_ppt_audit_trigger_aider_heal_accepts_issue_summary(client, monkeypatch)
|
||||
|
||||
monkeypatch.setattr(svc, 'execute_code_fix', fake_execute_code_fix)
|
||||
|
||||
class ImmediateThread:
|
||||
def __init__(self, target, **_kwargs):
|
||||
self.target = target
|
||||
|
||||
def start(self):
|
||||
self.target()
|
||||
|
||||
monkeypatch.setattr(mod.threading, 'Thread', ImmediateThread)
|
||||
|
||||
r = client.post(
|
||||
'/observability/ppt_audit/trigger_aider_heal',
|
||||
json={
|
||||
@@ -545,8 +555,9 @@ def test_ppt_audit_trigger_aider_heal_accepts_issue_summary(client, monkeypatch)
|
||||
)
|
||||
data = r.get_json()
|
||||
|
||||
assert r.status_code == 200
|
||||
assert r.status_code == 202
|
||||
assert data['ok'] is True
|
||||
assert data['status'] == 'queued'
|
||||
assert captured['error_type'] == 'ppt_vision_audit_failure'
|
||||
assert captured['target_file'] == 'services/ppt_generator.py'
|
||||
assert 'ocbot_daily_20260517.pptx' in captured['error_message']
|
||||
|
||||
@@ -573,23 +573,45 @@
|
||||
}
|
||||
};
|
||||
|
||||
window.triggerAiderHeal = async function triggerAiderHeal(pptxFilename, errorMsg) {
|
||||
window.triggerAiderHeal = async function triggerAiderHeal(pptxFilename, errorMsg, triggerButton) {
|
||||
if (!confirm(`觸發 AiderHeal 自動修復?\n\n檔案:${pptxFilename}\n錯誤:${(errorMsg || '').substring(0, 200)}`)) return;
|
||||
const statusNode = document.querySelector('[data-ppt-auto-status]');
|
||||
const originalHtml = triggerButton ? triggerButton.innerHTML : '';
|
||||
if (triggerButton) {
|
||||
triggerButton.disabled = true;
|
||||
triggerButton.innerHTML = '<i class="fas fa-spinner fa-spin me-1"></i>派工中';
|
||||
}
|
||||
if (statusNode) {
|
||||
statusNode.classList.add('is-working');
|
||||
statusNode.textContent = `${pptxFilename || 'PPT'} 正在排入 AiderHeal 背景修復。`;
|
||||
}
|
||||
try {
|
||||
const response = await postJson('/observability/ppt_audit/trigger_aider_heal', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ pptx_filename: pptxFilename, error_msg: errorMsg || '' })
|
||||
body: JSON.stringify({ pptx_filename: pptxFilename, issue_summary: errorMsg || '' })
|
||||
});
|
||||
const data = await response.json();
|
||||
if (data.ok) {
|
||||
alert(`✅ AiderHeal 已派出\n動作:${data.action || '—'}\n訊息:${data.message || ''}`);
|
||||
} else {
|
||||
alert(`❌ ${data.error || data.message || '觸發失敗'}`);
|
||||
if (!response.ok || !data.ok) {
|
||||
throw new Error(data.error || data.message || '觸發失敗');
|
||||
}
|
||||
if (triggerButton) {
|
||||
triggerButton.innerHTML = '<i class="fas fa-check me-1"></i>已排入';
|
||||
}
|
||||
if (statusNode) {
|
||||
statusNode.classList.remove('is-working');
|
||||
statusNode.textContent = data.message || 'AiderHeal 已排入背景執行,完成後會由 Telegram/Gitea/CD 結果回報。';
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('ppt_audit_trigger_aider_heal_failed', error);
|
||||
alert('操作暫時無法完成,請稍後再試或查看系統日誌。');
|
||||
if (triggerButton) {
|
||||
triggerButton.disabled = false;
|
||||
triggerButton.innerHTML = originalHtml;
|
||||
}
|
||||
if (statusNode) {
|
||||
statusNode.classList.remove('is-working');
|
||||
statusNode.textContent = 'AiderHeal 派工失敗,請稍後再試或查看系統日誌。';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -666,7 +688,7 @@
|
||||
if (button.dataset.bound === '1') return;
|
||||
button.dataset.bound = '1';
|
||||
button.addEventListener('click', () => {
|
||||
window.triggerAiderHeal(button.dataset.pptFilename || '', button.dataset.pptError || '');
|
||||
window.triggerAiderHeal(button.dataset.pptFilename || '', button.dataset.pptError || '', button);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user