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:
@@ -1,7 +1,12 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
import type { Dispatch, MutableRefObject, RefObject, SetStateAction } from "react";
|
||||
|
||||
import { composerDraftSnapshot } from "@/lib/composer-draft";
|
||||
import { appendQueuedText, queuedDraftSettleAction } from "@/lib/queued-draft";
|
||||
import { useQueuedMessage } from "@/hooks/use-queued-message";
|
||||
|
||||
import { showChatNotice as setComposerNotice } from "@/lib/chat-notice";
|
||||
import { parseAgentReply, isGenericSessionTitle, resolveSessionTitle } from "@/lib/agent-reply";
|
||||
import type { ConsultationBirthTimeMode } from "@/lib/consultation-birth-time-mode";
|
||||
@@ -95,6 +100,7 @@ export type ConsultationRunParams = {
|
||||
consultationReplayStarted: MutableRefObject<string | null>;
|
||||
consultationStatusMissingCount: MutableRefObject<number>;
|
||||
conversationAnchor: ConversationAnchor;
|
||||
consultationPhase: "undo" | "streaming" | "recovering" | null;
|
||||
isLoading: boolean;
|
||||
modelCatalog: PublicLanguageModelCatalog | null;
|
||||
onboardingJustCompleted: boolean;
|
||||
@@ -115,6 +121,7 @@ export type ConsultationRunParams = {
|
||||
setPendingRequestId: Dispatch<SetStateAction<string | null>>;
|
||||
setPendingSessionId: Dispatch<SetStateAction<string | null>>;
|
||||
setProfileNotice: Dispatch<SetStateAction<string>>;
|
||||
replyOutcome: ReplyOutcome | null;
|
||||
setReplyOutcome: Dispatch<SetStateAction<ReplyOutcome | null>>;
|
||||
setRequestError: Dispatch<SetStateAction<RequestError | null>>;
|
||||
setSessionFullPrompt: Dispatch<SetStateAction<{ question: string; theme: Theme } | null>>;
|
||||
@@ -151,6 +158,7 @@ export function useConsultationRun(params: ConsultationRunParams) {
|
||||
consultationReplayStarted,
|
||||
consultationStatusMissingCount,
|
||||
conversationAnchor,
|
||||
consultationPhase,
|
||||
isLoading,
|
||||
modelCatalog,
|
||||
onboardingJustCompleted,
|
||||
@@ -171,6 +179,7 @@ export function useConsultationRun(params: ConsultationRunParams) {
|
||||
setPendingRequestId,
|
||||
setPendingSessionId,
|
||||
setProfileNotice,
|
||||
replyOutcome,
|
||||
setReplyOutcome,
|
||||
setRequestError,
|
||||
setSessionFullPrompt,
|
||||
@@ -189,6 +198,19 @@ export function useConsultationRun(params: ConsultationRunParams) {
|
||||
openAccountDialog,
|
||||
openRectificationFromHomepage,
|
||||
} = params;
|
||||
const queued = useQueuedMessage();
|
||||
|
||||
function enqueueQueuedDraft(incoming: string) {
|
||||
if (queued.enqueue(incoming)) setDraft("");
|
||||
}
|
||||
|
||||
function recallQueuedDraft() {
|
||||
const recalled = queued.take();
|
||||
if (!recalled) return;
|
||||
setDraft(appendQueuedText(composerDraftSnapshot(), recalled));
|
||||
composerInput.current?.focus();
|
||||
}
|
||||
|
||||
|
||||
function restoreConsultationRecovery(
|
||||
session: ChatSession,
|
||||
@@ -1066,6 +1088,24 @@ export function useConsultationRun(params: ConsultationRunParams) {
|
||||
setComposerNotice("后台未找到本次咨询请求,已停止恢复,请重新发送。");
|
||||
});
|
||||
};
|
||||
useEffect(() => {
|
||||
if (!queued.text) return;
|
||||
const settlePhase = consultationPhase === "recovering"
|
||||
? "recovering" as const
|
||||
: replyOutcome?.phase === "completed" || replyOutcome?.phase === "stopped" || replyOutcome?.phase === "failed"
|
||||
? replyOutcome.phase
|
||||
: null;
|
||||
if (!settlePhase) return;
|
||||
if (settlePhase === "completed" && (isLoading || cancellationPending)) return;
|
||||
const next = queued.take();
|
||||
if (!next) return;
|
||||
if (queuedDraftSettleAction(settlePhase) === "send") {
|
||||
void send(next);
|
||||
return;
|
||||
}
|
||||
setDraft(appendQueuedText(composerDraftSnapshot(), next));
|
||||
}, [cancellationPending, consultationPhase, isLoading, queued.text, replyOutcome?.phase]);
|
||||
|
||||
return {
|
||||
restoreConsultationRecovery,
|
||||
requestCancellation,
|
||||
@@ -1074,5 +1114,8 @@ export function useConsultationRun(params: ConsultationRunParams) {
|
||||
completeConsultationInterface,
|
||||
send,
|
||||
regenerateLatestAnswer,
|
||||
queuedDraft: queued.text,
|
||||
enqueueQueuedDraft,
|
||||
recallQueuedDraft,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useRef, useState } from "react";
|
||||
import { appendQueuedText } from "@/lib/queued-draft";
|
||||
|
||||
export function useQueuedMessage() {
|
||||
const [text, setText] = useState("");
|
||||
const textRef = useRef("");
|
||||
|
||||
const enqueue = useCallback((incoming: string) => {
|
||||
const nextIncoming = incoming.trim();
|
||||
if (!nextIncoming) return false;
|
||||
const next = appendQueuedText(textRef.current, nextIncoming);
|
||||
textRef.current = next;
|
||||
setText(next);
|
||||
return true;
|
||||
}, []);
|
||||
|
||||
const take = useCallback(() => {
|
||||
const current = textRef.current;
|
||||
if (!current) return "";
|
||||
textRef.current = "";
|
||||
setText("");
|
||||
return current;
|
||||
}, []);
|
||||
|
||||
return { text, enqueue, take };
|
||||
}
|
||||
Reference in New Issue
Block a user