Studio: prevent long reasoning from jumping the chat on completion (#7388)

This commit is contained in:
oobabooga 2026-07-25 07:35:03 -03:00 committed by GitHub
commit b9d92c41b3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 28 additions and 1 deletions

View file

@ -346,6 +346,7 @@ const ReasoningGroupImpl: ReasoningGroupComponent = ({
const [manualOpen, setManualOpen] = useState(false);
const [dismissedWhileStreaming, setDismissedWhileStreaming] = useState(false);
const [retainStreamingHeight, setRetainStreamingHeight] = useState(false);
const [duration, setDuration] = useState<number>(0);
const startTimeRef = useRef<number | null>(null);
@ -368,6 +369,17 @@ const ReasoningGroupImpl: ReasoningGroupComponent = ({
}
}, [isReasoningStreaming]);
// Keep the streaming height cap until the automatic close finishes. Removing
// it on the completion frame expands long reasoning to its full height before
// the collapsible can close, which makes the entire chat jump.
useEffect(() => {
const timeout = window.setTimeout(
() => setRetainStreamingHeight(isReasoningStreaming),
isReasoningStreaming ? 0 : ANIMATION_DURATION,
);
return () => window.clearTimeout(timeout);
}, [isReasoningStreaming]);
// Open while streaming (unless dismissed), or once manually opened.
const isOpen = (isReasoningStreaming && !dismissedWhileStreaming) || manualOpen;
const variant = isOpen ? "outline" : "ghost";
@ -378,6 +390,9 @@ const ReasoningGroupImpl: ReasoningGroupComponent = ({
if (isReasoningStreaming) {
setDismissedWhileStreaming(!open);
} else {
if (open) {
setRetainStreamingHeight(false);
}
setManualOpen(open);
}
},
@ -407,7 +422,9 @@ const ReasoningGroupImpl: ReasoningGroupComponent = ({
aria-busy={isReasoningStreaming}
streaming={isReasoningStreaming}
>
<ReasoningText streaming={isReasoningStreaming}>
<ReasoningText
streaming={isReasoningStreaming || retainStreamingHeight}
>
{children}
</ReasoningText>
</ReasoningContent>

View file

@ -75,6 +75,16 @@ def test_response_model_badge_is_user_configurable_and_rendered_once_per_message
assert 'className="min-w-0 flex-1"' in reasoning_src
def test_reasoning_keeps_streaming_height_cap_through_automatic_collapse():
src = REASONING_TSX.read_text()
assert "const [retainStreamingHeight, setRetainStreamingHeight]" in src
assert "setRetainStreamingHeight(false)" in src
assert "setRetainStreamingHeight(isReasoningStreaming)" in src
assert "isReasoningStreaming ? 0 : ANIMATION_DURATION" in src
assert "streaming={isReasoningStreaming || retainStreamingHeight}" in src
def test_response_details_metadata_is_persisted_without_backend_schema_change():
src = ADAPTER_TS.read_text()
assert "interface ResponseDetailsMetadata" in src