讓 PPT AiderHeal 背景派工
All checks were successful
CD Pipeline / deploy (push) Successful in 1m5s

This commit is contained in:
OoO
2026-05-19 00:30:24 +08:00
parent 5908d1bdca
commit 02682c81ed
5 changed files with 81 additions and 23 deletions

View File

@@ -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` 的投影片問題,讓「有問題」可追查,不再只剩問題數或空白錯誤欄。

View File

@@ -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 # 用於模板顯示

View File

@@ -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

View File

@@ -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_foundAiderHeal 應可吃診斷摘要派工。"""
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']

View File

@@ -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);
});
});