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) {
if (window.toastr?.error) return window.toastr.error(msg);
console.error('[EnaPlanner]', msg);
@@ -183,6 +179,49 @@ function setSendUIBusy(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) {
if (val == null) return '';
if (typeof val === 'string') return val;
@@ -1255,7 +1294,10 @@ async function runPlanningOnce(rawUserInput, silent = false) {
const { messages } = await buildPlannerMessages(rawUserInput);
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;
const filtered = filterPlannerForInput(rawReply);
@@ -1292,29 +1334,34 @@ function shouldInterceptNow() {
return true;
}
async function doInterceptAndPlanThenSend() {
async function doInterceptAndPlanThenSend(rawSnapshot = '') {
const ta = getSendTextarea();
const btn = getSendButton();
if (!ta || !btn) return;
const raw = String(ta.value ?? '').trim();
const raw = String(rawSnapshot || ta.value || '').trim();
if (!raw) return;
state.isPlanning = true;
setSendUIBusy(true);
setPlanningStatus('Planning...');
try {
toastInfo('Ena Planner正在规划…');
const { filtered } = await runPlanningOnce(raw, false);
const merged = `${raw}\n\n${filtered}`.trim();
ta.value = merged;
state.lastInjectedText = merged;
setPlanningStatus('Planning done', 'success');
state.bypassNextSend = true;
btn.click();
} catch (err) {
setPlanningStatus(String(err?.message || 'Planning failed'), 'error');
throw err;
} finally {
state.isPlanning = false;
setSendUIBusy(false);
clearPlanningStatus(2000);
setTimeout(() => { state.bypassNextSend = false; }, 800);
}
}
@@ -1325,6 +1372,9 @@ function installSendInterceptors() {
const btn = getSendButton();
if (!btn) 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) {
e.preventDefault();
e.stopImmediatePropagation();
@@ -1333,7 +1383,7 @@ function installSendInterceptors() {
if (!shouldInterceptNow()) return;
e.preventDefault();
e.stopImmediatePropagation();
doInterceptAndPlanThenSend().catch(err => toastErr(String(err?.message ?? err)));
doInterceptAndPlanThenSend(raw).catch(err => toastErr(String(err?.message ?? err)));
};
sendClickHandler = (e) => {
const btn = getSendButton();
@@ -1345,18 +1395,23 @@ function installSendInterceptors() {
return;
}
if (!shouldInterceptNow()) return;
const ta = getSendTextarea();
const raw = String(ta?.value ?? '').trim();
if (!raw) return;
e.preventDefault();
e.stopImmediatePropagation();
doInterceptAndPlanThenSend().catch(err => toastErr(String(err?.message ?? err)));
doInterceptAndPlanThenSend(raw).catch(err => toastErr(String(err?.message ?? err)));
};
sendKeydownHandler = (e) => {
const ta = getSendTextarea();
if (!ta || e.target !== ta) return;
if (e.key === 'Enter' && !e.shiftKey) {
if (!shouldInterceptNow()) return;
const raw = String(ta.value ?? '').trim();
if (!raw) return;
e.preventDefault();
e.stopImmediatePropagation();
doInterceptAndPlanThenSend().catch(err => toastErr(String(err?.message ?? err)));
doInterceptAndPlanThenSend(raw).catch(err => toastErr(String(err?.message ?? err)));
}
};
sendKeyupHandler = (e) => {