diff --git a/modules/ena-planner/ena-planner.js b/modules/ena-planner/ena-planner.js index 5c2d025..611f0e4 100644 --- a/modules/ena-planner/ena-planner.js +++ b/modules/ena-planner/ena-planner.js @@ -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) => {