fix(ena-planner): restore planning lock UX and robust send interception flow

This commit is contained in:
2026-02-25 17:43:33 +08:00
parent 1324d9c831
commit 419c95808a

View File

@@ -139,10 +139,6 @@ async function saveConfigNow() {
} }
} }
function toastInfo(msg) {
if (window.toastr?.info) return window.toastr.info(msg);
console.log('[EnaPlanner]', msg);
}
function toastErr(msg) { function toastErr(msg) {
if (window.toastr?.error) return window.toastr.error(msg); if (window.toastr?.error) return window.toastr.error(msg);
console.error('[EnaPlanner]', msg); console.error('[EnaPlanner]', msg);
@@ -183,6 +179,49 @@ function setSendUIBusy(busy) {
if (textarea) textarea.disabled = !!busy; if (textarea) textarea.disabled = !!busy;
} }
function ensurePlanningStatusEl() {
const ta = getSendTextarea();
if (!ta) return null;
let el = document.getElementById('xb-ena-planning-status');
if (el) return el;
el = document.createElement('div');
el.id = 'xb-ena-planning-status';
el.style.cssText = [
'margin-top:6px',
'font-size:12px',
'line-height:1.4',
'color:var(--SmartThemeBodyColor,#c9d1d9)',
'opacity:.82',
'display:none',
].join(';');
ta.insertAdjacentElement('afterend', el);
return el;
}
function setPlanningStatus(text, type = 'info') {
const el = ensurePlanningStatusEl();
if (!el) return;
el.textContent = text || '';
el.style.display = text ? 'block' : 'none';
if (!text) return;
if (type === 'error') {
el.style.color = '#f87171';
} else if (type === 'success') {
el.style.color = '#3ecf8e';
} else {
el.style.color = 'var(--SmartThemeBodyColor,#c9d1d9)';
}
}
function clearPlanningStatus(delay = 0) {
if (delay > 0) {
setTimeout(() => setPlanningStatus(''), delay);
return;
}
setPlanningStatus('');
}
function safeStringify(val) { function safeStringify(val) {
if (val == null) return ''; if (val == null) return '';
if (typeof val === 'string') return val; if (typeof val === 'string') return val;
@@ -1255,7 +1294,10 @@ async function runPlanningOnce(rawUserInput, silent = false) {
const { messages } = await buildPlannerMessages(rawUserInput); const { messages } = await buildPlannerMessages(rawUserInput);
log.requestMessages = messages; log.requestMessages = messages;
const rawReply = await callPlanner(messages); const rawReply = await Promise.race([
callPlanner(messages),
new Promise((_, reject) => setTimeout(() => reject(new Error('规划超时,请重试')), 120000)),
]);
log.rawReply = rawReply; log.rawReply = rawReply;
const filtered = filterPlannerForInput(rawReply); const filtered = filterPlannerForInput(rawReply);
@@ -1292,29 +1334,34 @@ function shouldInterceptNow() {
return true; return true;
} }
async function doInterceptAndPlanThenSend() { async function doInterceptAndPlanThenSend(rawSnapshot = '') {
const ta = getSendTextarea(); const ta = getSendTextarea();
const btn = getSendButton(); const btn = getSendButton();
if (!ta || !btn) return; if (!ta || !btn) return;
const raw = String(ta.value ?? '').trim(); const raw = String(rawSnapshot || ta.value || '').trim();
if (!raw) return; if (!raw) return;
state.isPlanning = true; state.isPlanning = true;
setSendUIBusy(true); setSendUIBusy(true);
setPlanningStatus('Planning...');
try { try {
toastInfo('Ena Planner正在规划…');
const { filtered } = await runPlanningOnce(raw, false); const { filtered } = await runPlanningOnce(raw, false);
const merged = `${raw}\n\n${filtered}`.trim(); const merged = `${raw}\n\n${filtered}`.trim();
ta.value = merged; ta.value = merged;
state.lastInjectedText = merged; state.lastInjectedText = merged;
setPlanningStatus('Planning done', 'success');
state.bypassNextSend = true; state.bypassNextSend = true;
btn.click(); btn.click();
} catch (err) {
setPlanningStatus(String(err?.message || 'Planning failed'), 'error');
throw err;
} finally { } finally {
state.isPlanning = false; state.isPlanning = false;
setSendUIBusy(false); setSendUIBusy(false);
clearPlanningStatus(2000);
setTimeout(() => { state.bypassNextSend = false; }, 800); setTimeout(() => { state.bypassNextSend = false; }, 800);
} }
} }
@@ -1325,6 +1372,9 @@ function installSendInterceptors() {
const btn = getSendButton(); const btn = getSendButton();
if (!btn) return; if (!btn) return;
if (e.target !== btn && !btn.contains(e.target)) return; if (e.target !== btn && !btn.contains(e.target)) return;
const ta = getSendTextarea();
const raw = String(ta?.value ?? '').trim();
if (!raw) return;
if (state.isPlanning) { if (state.isPlanning) {
e.preventDefault(); e.preventDefault();
e.stopImmediatePropagation(); e.stopImmediatePropagation();
@@ -1333,7 +1383,7 @@ function installSendInterceptors() {
if (!shouldInterceptNow()) return; if (!shouldInterceptNow()) return;
e.preventDefault(); e.preventDefault();
e.stopImmediatePropagation(); e.stopImmediatePropagation();
doInterceptAndPlanThenSend().catch(err => toastErr(String(err?.message ?? err))); doInterceptAndPlanThenSend(raw).catch(err => toastErr(String(err?.message ?? err)));
}; };
sendClickHandler = (e) => { sendClickHandler = (e) => {
const btn = getSendButton(); const btn = getSendButton();
@@ -1345,18 +1395,23 @@ function installSendInterceptors() {
return; return;
} }
if (!shouldInterceptNow()) return; if (!shouldInterceptNow()) return;
const ta = getSendTextarea();
const raw = String(ta?.value ?? '').trim();
if (!raw) return;
e.preventDefault(); e.preventDefault();
e.stopImmediatePropagation(); e.stopImmediatePropagation();
doInterceptAndPlanThenSend().catch(err => toastErr(String(err?.message ?? err))); doInterceptAndPlanThenSend(raw).catch(err => toastErr(String(err?.message ?? err)));
}; };
sendKeydownHandler = (e) => { sendKeydownHandler = (e) => {
const ta = getSendTextarea(); const ta = getSendTextarea();
if (!ta || e.target !== ta) return; if (!ta || e.target !== ta) return;
if (e.key === 'Enter' && !e.shiftKey) { if (e.key === 'Enter' && !e.shiftKey) {
if (!shouldInterceptNow()) return; if (!shouldInterceptNow()) return;
const raw = String(ta.value ?? '').trim();
if (!raw) return;
e.preventDefault(); e.preventDefault();
e.stopImmediatePropagation(); e.stopImmediatePropagation();
doInterceptAndPlanThenSend().catch(err => toastErr(String(err?.message ?? err))); doInterceptAndPlanThenSend(raw).catch(err => toastErr(String(err?.message ?? err)));
} }
}; };
sendKeyupHandler = (e) => { sendKeyupHandler = (e) => {