fix(ui): reasoning display glitches and scroll button persistence (#1206)
This commit is contained in:
parent
a1844c8294
commit
b229241854
3 changed files with 46 additions and 37 deletions
|
|
@ -1,5 +1,6 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
import {
|
import {
|
||||||
Reasoning,
|
Reasoning,
|
||||||
ReasoningTrigger,
|
ReasoningTrigger,
|
||||||
|
|
@ -15,10 +16,18 @@ export function MessageReasoning({
|
||||||
isLoading,
|
isLoading,
|
||||||
reasoning,
|
reasoning,
|
||||||
}: MessageReasoningProps) {
|
}: MessageReasoningProps) {
|
||||||
|
const [hasBeenStreaming, setHasBeenStreaming] = useState(isLoading);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isLoading) {
|
||||||
|
setHasBeenStreaming(true);
|
||||||
|
}
|
||||||
|
}, [isLoading]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Reasoning
|
<Reasoning
|
||||||
isStreaming={isLoading}
|
isStreaming={isLoading}
|
||||||
defaultOpen={true}
|
defaultOpen={hasBeenStreaming}
|
||||||
data-testid="message-reasoning"
|
data-testid="message-reasoning"
|
||||||
>
|
>
|
||||||
<ReasoningTrigger />
|
<ReasoningTrigger />
|
||||||
|
|
|
||||||
|
|
@ -38,9 +38,6 @@ import { SelectItem } from '@/components/ui/select';
|
||||||
import * as SelectPrimitive from '@radix-ui/react-select';
|
import * as SelectPrimitive from '@radix-ui/react-select';
|
||||||
import equal from 'fast-deep-equal';
|
import equal from 'fast-deep-equal';
|
||||||
import type { UseChatHelpers } from '@ai-sdk/react';
|
import type { UseChatHelpers } from '@ai-sdk/react';
|
||||||
import { AnimatePresence, motion } from 'framer-motion';
|
|
||||||
import { ArrowDown } from 'lucide-react';
|
|
||||||
import { useScrollToBottom } from '@/hooks/use-scroll-to-bottom';
|
|
||||||
import type { VisibilityType } from './visibility-selector';
|
import type { VisibilityType } from './visibility-selector';
|
||||||
import type { Attachment, ChatMessage } from '@/lib/types';
|
import type { Attachment, ChatMessage } from '@/lib/types';
|
||||||
import type { AppUsage } from '@/lib/usage';
|
import type { AppUsage } from '@/lib/usage';
|
||||||
|
|
@ -234,40 +231,8 @@ function PureMultimodalInput({
|
||||||
[setAttachments],
|
[setAttachments],
|
||||||
);
|
);
|
||||||
|
|
||||||
const { isAtBottom, scrollToBottom } = useScrollToBottom();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (status === 'submitted') {
|
|
||||||
scrollToBottom();
|
|
||||||
}
|
|
||||||
}, [status, scrollToBottom]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex relative flex-col gap-4 w-full">
|
<div className="flex relative flex-col gap-4 w-full">
|
||||||
<AnimatePresence>
|
|
||||||
{!isAtBottom && (
|
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0, y: 10 }}
|
|
||||||
animate={{ opacity: 1, y: 0 }}
|
|
||||||
exit={{ opacity: 0, y: 10 }}
|
|
||||||
transition={{ type: 'spring', stiffness: 300, damping: 20 }}
|
|
||||||
className="absolute -top-12 left-1/2 z-50 -translate-x-1/2"
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
data-testid="scroll-to-bottom-button"
|
|
||||||
className="rounded-full"
|
|
||||||
size="icon"
|
|
||||||
variant="outline"
|
|
||||||
onClick={(event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
scrollToBottom();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ArrowDown />
|
|
||||||
</Button>
|
|
||||||
</motion.div>
|
|
||||||
)}
|
|
||||||
</AnimatePresence>
|
|
||||||
|
|
||||||
{messages.length === 0 &&
|
{messages.length === 0 &&
|
||||||
attachments.length === 0 &&
|
attachments.length === 0 &&
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,42 @@ export function useScrollToBottom() {
|
||||||
|
|
||||||
// Check if we are within 100px of the bottom (like v0 does)
|
// Check if we are within 100px of the bottom (like v0 does)
|
||||||
setIsAtBottom(scrollTop + clientHeight >= scrollHeight - 100);
|
setIsAtBottom(scrollTop + clientHeight >= scrollHeight - 100);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!containerRef.current) return;
|
||||||
|
|
||||||
|
const container = containerRef.current;
|
||||||
|
|
||||||
|
const resizeObserver = new ResizeObserver(() => {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
handleScroll();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const mutationObserver = new MutationObserver(() => {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
handleScroll();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
resizeObserver.observe(container);
|
||||||
|
mutationObserver.observe(container, {
|
||||||
|
childList: true,
|
||||||
|
subtree: true,
|
||||||
|
attributes: true,
|
||||||
|
attributeFilter: ['style', 'class', 'data-state']
|
||||||
|
});
|
||||||
|
|
||||||
|
handleScroll();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
resizeObserver.disconnect();
|
||||||
|
mutationObserver.disconnect();
|
||||||
|
};
|
||||||
|
}, [handleScroll]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const container = containerRef.current;
|
const container = containerRef.current;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue