From 00dfcb6615500f2ed70daeee33a7ed01d1fed465 Mon Sep 17 00:00:00 2001 From: moranzhi Date: Fri, 31 Jul 2026 00:57:13 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=AF=A2=E9=97=AE=E5=8D=A1?= =?UTF-8?q?=E5=8F=91=E9=80=81=E5=90=8E=E4=BB=8D=E5=9B=9E=E9=97=AA=EF=BC=9A?= =?UTF-8?q?=E7=82=B9=E5=8F=91=E9=80=81=E5=8D=B3=E6=94=B6=E8=B5=B7=E6=9C=AC?= =?UTF-8?q?=E8=BD=AE=E5=8D=A1=EF=BC=8C=E5=B9=B6=E9=81=BF=E5=85=8D=20loadin?= =?UTF-8?q?g=20=E7=94=A8=E6=97=A7=20waitingReason=20=E9=87=8D=E7=BB=98?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- web/app.js | 35 +++++++++++++++++++++++++++++++---- web/questions-ui.js | 45 ++++++++++++++++++++++++++++++++++++++++----- web/styles.css | 4 ++-- 3 files changed, 73 insertions(+), 11 deletions(-) diff --git a/web/app.js b/web/app.js index 83c1e48..9d0b9d0 100644 --- a/web/app.js +++ b/web/app.js @@ -866,10 +866,14 @@ async function sendText(text) { const qHost = $("questions-card-host"); const activeQs = getActiveQuestions(lastView); const reviewing = lastView?.waitingReason?.kind === "review_artifact"; + const cardOpen = Boolean( + activeQs?.questions?.length && qHost?.classList.contains("is-open"), + ); // 产物验收:底栏有字 ⇒ 按修改意见重做;接受走「接受目前产物」 if (reviewing && trimmed) { try { + clearQuestionCardState(qHost, lastView); renderSession(lastView, true); const view = await api(`/api/sessions/${encodeURIComponent(sessionId)}/messages`, { method: "POST", @@ -878,17 +882,18 @@ async function sendText(text) { clearQuestionCardState(qHost); renderSession(view, false); } catch (err) { + if (qHost) qHost._qDismissed = null; if (lastView) renderSession({ ...lastView, hints: [err.message] }, false); } return; } - if (activeQs?.questions?.length && qHost?.classList.contains("is-open")) { + if (cardOpen) { const collected = collectQuestionAnswers(qHost, lastView); const answered = collected.ok ? collected.answers.filter((a) => a.text && a.text !== "(未答)") : []; - // 可选追问:未选任何选项时,允许直接发自由文本(或空操作交给下方逻辑) + // 必答题未选:拦住发送 if (!collected.ok && !activeQs.optional) { if (typeof collected.page === "number" && qHost._qState) { qHost._qState.page = collected.page; @@ -899,8 +904,10 @@ async function sendText(text) { alert(collected.error); return; } + // 有选中 → 问+答拼接发送,并收起卡 if (collected.ok && answered.length) { try { + clearQuestionCardState(qHost, lastView); renderSession(lastView, true); const body = { answers: collected.answers }; if (trimmed) body.note = trimmed; @@ -911,10 +918,18 @@ async function sendText(text) { clearQuestionCardState(qHost); renderSession(view, false); } catch (err) { + if (qHost) qHost._qDismissed = null; if (lastView) renderSession({ ...lastView, hints: [err.message] }, false); } return; } + // 未选中:不带问;点发送仍算答复 → 收起卡 + if (!trimmed) { + clearQuestionCardState(qHost, lastView); + await runAction("skip_questions"); + return; + } + clearQuestionCardState(qHost, lastView); } if (!trimmed) return; @@ -924,26 +939,38 @@ async function sendText(text) { method: "POST", body: JSON.stringify({ text: trimmed }), }); + clearQuestionCardState(qHost); renderSession(view, false); } catch (err) { + if (cardOpen && qHost) qHost._qDismissed = null; if (lastView) renderSession({ ...lastView, hints: [err.message] }, false); } } async function runAction(action) { if (!sessionId) return; + const qHost = $("questions-card-host"); try { + // 接受 / 跳过:先收起询问卡,避免 loading 用旧 waitingReason 再画出来 + if (action === "accept" || action === "skip_questions") { + clearQuestionCardState(qHost, lastView); + } renderSession(lastView, true); const view = await api(`/api/sessions/${encodeURIComponent(sessionId)}/actions`, { method: "POST", body: JSON.stringify({ action }), }); - // 接受产物 = 不再完善 → 收起询问卡;跳过追问同理 if (action === "accept" || action === "skip_questions") { - clearQuestionCardState($("questions-card-host")); + clearQuestionCardState(qHost); } renderSession(view, false); } catch (err) { + if ( + (action === "accept" || action === "skip_questions") && + qHost + ) { + qHost._qDismissed = null; + } if (lastView) renderSession({ ...lastView, hints: [err.message] }, false); } } diff --git a/web/questions-ui.js b/web/questions-ui.js index 6074ffe..e57f8ff 100644 --- a/web/questions-ui.js +++ b/web/questions-ui.js @@ -1,6 +1,7 @@ /** * 结构化询问卡:点字母选中,点文案编辑;左右分页。 - * 普通提问:提交走底栏 composer(问+答拼进上下文),卡上无独立发送钮。 + * 询问是对同一次发送的可选增强:有选中则问+答拼接;未选中则不带问。 + * 点发送即答复 → 收起本轮卡(卡上无独立发送钮)。 * 产物验收挂载题:底栏有字优先按「改产物」发送;无字时才提交所选答案。 * 分页 / 跳过为悬浮控件,不占独立 header/footer 行。 */ @@ -196,8 +197,29 @@ export function collectQuestionAnswers(host, view) { return { ok: true, answers }; } -export function clearQuestionCardState(host) { - if (host) host._qState = null; +/** + * 收起询问卡。传入 view 时标记本轮题已答复,避免 loading 用旧 waitingReason 把卡又画回来。 + * @param {HTMLElement | null | undefined} host + * @param {object} [view] + */ +export function clearQuestionCardState(host, view) { + if (!host) return; + const active = view ? getActiveQuestions(view) : null; + if (active?.questions?.length && view?.id) { + host._qDismissed = { + sessionId: view.id, + key: JSON.stringify(active.questions.map((q) => q.id)), + }; + } else if (host._qState?.sessionId && host._qState?.key) { + host._qDismissed = { + sessionId: host._qState.sessionId, + key: host._qState.key, + }; + } + host._qState = null; + host.hidden = true; + host.innerHTML = ""; + host.classList.remove("is-open"); } /** @@ -209,6 +231,19 @@ export function renderQuestionsCard(host, view, _handlers = {}) { if (!host) return false; const active = getActiveQuestions(view); if (!active?.questions?.length) { + host.hidden = true; + host.innerHTML = ""; + host.classList.remove("is-open"); + host._qDismissed = null; + return false; + } + + const qKey = JSON.stringify(active.questions.map((q) => q.id)); + // 本轮已通过发送/跳过答复 → 在 waitingReason 清掉前也不再打开 + if ( + host._qDismissed?.sessionId === view.id && + host._qDismissed?.key === qKey + ) { host.hidden = true; host.innerHTML = ""; host.classList.remove("is-open"); @@ -218,11 +253,11 @@ export function renderQuestionsCard(host, view, _handlers = {}) { host.hidden = false; host.classList.add("is-open"); - const state = host._qState?.sessionId === view.id && host._qState?.key === JSON.stringify(active.questions.map((q) => q.id)) + const state = host._qState?.sessionId === view.id && host._qState?.key === qKey ? host._qState : { sessionId: view.id, - key: JSON.stringify(active.questions.map((q) => q.id)), + key: qKey, page: 0, // questionId -> { optionId?, text, otherText? } answers: {}, diff --git a/web/styles.css b/web/styles.css index 5ec9242..5f7ac80 100644 --- a/web/styles.css +++ b/web/styles.css @@ -722,8 +722,8 @@ body[data-lifecycle="play"] .panel-agent { background: #f7fef9; } min-height: 0; } .questions-card-host[hidden] { display: none !important; } -/* 开启时占 feed 剩余高度的上限,不把整页撑出视口 */ -.questions-card-host.is-open { +/* 开启时占 feed 剩余高度的上限,不把整页撑出视口;勿与 [hidden] 并存抢 display */ +.questions-card-host.is-open:not([hidden]) { display: flex; flex-direction: column; max-height: 48%;