fix(web): keep composer enabled while generating and treat stop as neutral (BUG-551, BUG-552)

Enter now queues one follow-up instead of dropping it, and a rectification stop leaves the streamed reply with a grey notice instead of an error alert.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jesse_Chen
2026-09-06 13:08:43 +08:00
co-authored by Cursor
parent f5566c06ef
commit 055b7adca9
18 changed files with 534 additions and 74 deletions
+8
View File
@@ -1501,6 +1501,14 @@ button:disabled { cursor: default; opacity: .45; }
}
}
.error-message { margin: var(--space-3) 0 0; padding: var(--space-3) var(--space-4); border-left: 3px solid var(--color-danger); background: var(--color-danger-muted); color: var(--color-danger); font-size: var(--type-caption); line-height: 1.6; border-color: var(--color-danger); border-radius: 0 var(--radius-md) var(--radius-md) 0; }
.message-stopped-notice { margin: var(--space-2) 0 0; color: var(--color-ink-tertiary); font-size: var(--type-caption); line-height: 1.5; }
.composer-queue { width: min(760px, 100%); display: flex; align-items: flex-start; gap: var(--space-3); margin: 0 auto var(--space-2); padding: var(--space-3) var(--space-4); border: 1px solid var(--color-border); border-radius: var(--radius-md); background: var(--color-canvas-muted); }
.composer-queue__body { min-width: 0; flex: 1; }
.composer-queue__label { margin: 0; color: var(--color-ink-secondary); font-size: var(--type-caption); line-height: 1.4; }
.composer-queue__text { margin: var(--space-1) 0 0; color: var(--color-ink); font-size: var(--type-body-sm); line-height: 1.5; overflow-wrap: anywhere; }
.composer-queue__recall { flex: 0 0 auto; min-height: 44px; min-width: 44px; padding: 0 var(--space-3); border: 0; border-radius: var(--radius-md); background: transparent; color: var(--color-ink-secondary); cursor: pointer; font: inherit; font-size: var(--type-caption); }
.composer-queue__recall:hover { color: var(--color-ink); }
.composer-queue__recall:focus-visible { outline: 3px solid color-mix(in srgb, var(--color-focus) 56%, transparent); outline-offset: 2px; }
.composer-wrap { position: sticky; bottom: 0; z-index: 2; min-width: 0; border-top: 1px solid color-mix(in srgb, var(--color-border) 70%, transparent); padding: var(--space-3) var(--space-6) var(--space-4); background: var(--color-frosted); backdrop-filter: saturate(130%) blur(20px); }
/* Jump to latest: shared by both chat surfaces. Hangs off the composer wrap's top
+7 -8
View File
@@ -731,12 +731,7 @@ export default function Home() {
!rectificationSurfaceOpen && !starterHomeVisible,
activeSessionId,
);
const {
restoreConsultationRecovery,
stopResponse,
send,
regenerateLatestAnswer,
} = useConsultationRun({
const { restoreConsultationRecovery, stopResponse, send, regenerateLatestAnswer, queuedDraft, enqueueQueuedDraft, recallQueuedDraft } = useConsultationRun({
account,
activeSession,
activeSessionIdRef,
@@ -752,6 +747,7 @@ export default function Home() {
consultationReplayStarted,
consultationStatusMissingCount,
conversationAnchor,
consultationPhase,
isLoading,
modelCatalog,
onboardingJustCompleted,
@@ -772,6 +768,7 @@ export default function Home() {
setPendingRequestId,
setPendingSessionId,
setProfileNotice,
replyOutcome,
setReplyOutcome,
setRequestError,
setSessionFullPrompt,
@@ -1546,6 +1543,7 @@ export default function Home() {
if (onboardingStep === "name" && presetMessageFinished) void saveOnboardingName();
return;
}
if (isLoading || cancellationPending) return void enqueueQueuedDraft(composerDraftSnapshot());
void send(composerDraftSnapshot(), draftTheme.current ?? undefined, draftEntrypoint.current);
}
@@ -1995,9 +1993,9 @@ export default function Home() {
? "余额不足,可前往会员页充值点数或开通会员"
: "例如:未来半年是否适合换工作?"}
maxLength={!profileComplete && onboardingStep === "name" ? 80 : 500}
inputDisabled={isLoading || sessionMessagesLoading || cancellationPending || rectificationSurfaceOpen || (!profileComplete && (onboardingStep !== "name" || !presetMessageFinished || profileSaving))}
inputDisabled={sessionMessagesLoading || rectificationSurfaceOpen || (!profileComplete && (onboardingStep !== "name" || !presetMessageFinished || profileSaving))}
submitLabel={!profileComplete && onboardingStep === "name" ? "确认称呼" : "发送"}
submitBlocked={Boolean(pendingSessionId) || sessionMessagesLoading || cancellationPending || rectificationSurfaceOpen || !account || !modelCatalog || (!profileComplete && (onboardingStep !== "name" || !presetMessageFinished || profileSaving))}
submitBlocked={Boolean(pendingSessionId) || Boolean(queuedDraft) || sessionMessagesLoading || rectificationSurfaceOpen || !account || !modelCatalog || (!profileComplete && (onboardingStep !== "name" || !presetMessageFinished || profileSaving))}
stopVisible={isLoading}
stopLabel={consultationPhase === "undo" ? "撤回发送,本次不扣点" : activeStreamingText ? "停止回答,保留已生成内容并退回本次点数" : "停止回答并申请退回本次点数"}
stopTitle={consultationPhase === "undo" ? "撤回发送,本次不扣点" : activeStreamingText ? "停止回答,保留现有内容并申请退回本次点数" : "停止回答"}
@@ -2009,6 +2007,7 @@ export default function Home() {
setComposerNotice("");
}}
onKeyDown={handleComposerKeyDown}
queued={queuedDraft ? { text: queuedDraft, onRecall: recallQueuedDraft } : undefined}
onStop={() => void stopResponse()}
/>
<div className="composer-footer">