mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-08-07 13:29:15 +00:00
* fix(frontend): hide stale follow-up chips while a turn is streaming (#3395) Follow-up suggestion chips are generated when a turn finishes streaming, but `showFollowups` did not exclude the streaming state. If a user sent a new message before the previous response finished, the old chips (and the lone close button) stayed mounted and overlapped the To-dos panel and the input box. Gate `showFollowups` on `status !== "streaming"` so stale chips are never shown while a response is in progress. * fix(frontend): suppress follow-up suggestions for user-interrupted turns (#3395) Gating showFollowups on status alone was not enough: stopping a streaming turn (or sending a new message mid-stream, which also stops it) flips status back to a non-streaming state and triggers the follow-up generation effect on that streaming->ready transition, producing chips for a half-finished, interrupted response. Track user interruption with a ref set in the stop path, and have the generation effect skip that transition (and clear/hide any pending chips), so follow-ups are only generated for turns that finished on their own.
This commit is contained in:
parent
4795452102
commit
c0f1cfef69
@ -417,6 +417,9 @@ export function InputBox({
|
|||||||
useState<string | null>(null);
|
useState<string | null>(null);
|
||||||
const lastGeneratedForAiIdRef = useRef<string | null>(null);
|
const lastGeneratedForAiIdRef = useRef<string | null>(null);
|
||||||
const wasStreamingRef = useRef(false);
|
const wasStreamingRef = useRef(false);
|
||||||
|
// Set when the user stops a streaming turn. Such a turn ends on a
|
||||||
|
// half-finished response, so we must NOT generate follow-up suggestions for it.
|
||||||
|
const stoppedByUserRef = useRef(false);
|
||||||
const messagesRef = useRef(thread.messages);
|
const messagesRef = useRef(thread.messages);
|
||||||
|
|
||||||
const clearVoiceRestartTimer = useCallback(() => {
|
const clearVoiceRestartTimer = useCallback(() => {
|
||||||
@ -1148,6 +1151,16 @@ export function InputBox({
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleStopStreaming = useCallback(() => {
|
||||||
|
// Mark the in-progress turn as user-interrupted so the next
|
||||||
|
// streaming->ready transition does not suggest follow-ups for it.
|
||||||
|
stoppedByUserRef.current = true;
|
||||||
|
setFollowups([]);
|
||||||
|
setFollowupsHidden(true);
|
||||||
|
setFollowupsLoading(false);
|
||||||
|
onStop?.();
|
||||||
|
}, [onStop]);
|
||||||
|
|
||||||
const handleSubmit = useCallback(
|
const handleSubmit = useCallback(
|
||||||
async (message: PromptInputMessage) => {
|
async (message: PromptInputMessage) => {
|
||||||
if (status === "streaming") {
|
if (status === "streaming") {
|
||||||
@ -1200,7 +1213,7 @@ export function InputBox({
|
|||||||
return handleCompactCommand();
|
return handleCompactCommand();
|
||||||
}
|
}
|
||||||
if (submitAction.kind === "stop") {
|
if (submitAction.kind === "stop") {
|
||||||
onStop?.();
|
handleStopStreaming();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (submitAction.kind === "empty") {
|
if (submitAction.kind === "empty") {
|
||||||
@ -1215,7 +1228,7 @@ export function InputBox({
|
|||||||
abortVoiceInput,
|
abortVoiceInput,
|
||||||
handleCompactCommand,
|
handleCompactCommand,
|
||||||
handleGoalCommand,
|
handleGoalCommand,
|
||||||
onStop,
|
handleStopStreaming,
|
||||||
selectedSlashSkill,
|
selectedSlashSkill,
|
||||||
status,
|
status,
|
||||||
submitThreadMessage,
|
submitThreadMessage,
|
||||||
@ -1923,6 +1936,10 @@ export function InputBox({
|
|||||||
!showSkillSuggestions &&
|
!showSkillSuggestions &&
|
||||||
!selectedSlashSkill &&
|
!selectedSlashSkill &&
|
||||||
!followupsHidden &&
|
!followupsHidden &&
|
||||||
|
// Never show stale follow-up chips while a turn is streaming: a message
|
||||||
|
// sent before the previous response finished would otherwise leave the
|
||||||
|
// old chips (and the lone close button) overlapping the input box.
|
||||||
|
status !== "streaming" &&
|
||||||
(followupsLoading || followups.length > 0);
|
(followupsLoading || followups.length > 0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -1945,6 +1962,13 @@ export function InputBox({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The turn was interrupted by the user, so skip generating follow-ups for
|
||||||
|
// this half-finished response.
|
||||||
|
if (stoppedByUserRef.current) {
|
||||||
|
stoppedByUserRef.current = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (disabled || isMock) {
|
if (disabled || isMock) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -2666,7 +2690,7 @@ export function InputBox({
|
|||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
if (status === "streaming") {
|
if (status === "streaming") {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
onStop?.();
|
handleStopStreaming();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user