From a3802348fa8ba5292fc481922d6d4a28ad9462eb Mon Sep 17 00:00:00 2001 From: josh <144584931+dancer@users.noreply.github.com> Date: Sat, 29 Nov 2025 13:02:24 +0000 Subject: [PATCH] fix: resolve scroll flickering on message send and improve thinking state animation (#1333) --- app/(chat)/actions.ts | 2 +- app/(chat)/api/history/route.ts | 2 +- app/globals.css | 4 +- components/app-sidebar.tsx | 16 ++- components/data-stream-handler.tsx | 6 +- components/elements/context.tsx | 2 +- components/message.tsx | 40 ++++---- components/messages.tsx | 85 ++++++---------- components/multimodal-input.tsx | 48 ++++----- components/preview-attachment.tsx | 6 +- components/weather.tsx | 153 ++++++++++++++++++++--------- lib/ai/prompts.ts | 2 +- lib/ai/tools/get-weather.ts | 31 +++--- lib/db/queries.ts | 2 +- 14 files changed, 225 insertions(+), 174 deletions(-) diff --git a/app/(chat)/actions.ts b/app/(chat)/actions.ts index 5bc9b4c..dde2290 100644 --- a/app/(chat)/actions.ts +++ b/app/(chat)/actions.ts @@ -3,8 +3,8 @@ import { generateText, type UIMessage } from "ai"; import { cookies } from "next/headers"; import type { VisibilityType } from "@/components/visibility-selector"; -import { myProvider } from "@/lib/ai/providers"; import { titlePrompt } from "@/lib/ai/prompts"; +import { myProvider } from "@/lib/ai/providers"; import { deleteMessagesByChatIdAfterTimestamp, getMessageById, diff --git a/app/(chat)/api/history/route.ts b/app/(chat)/api/history/route.ts index 2525a9a..23615e3 100644 --- a/app/(chat)/api/history/route.ts +++ b/app/(chat)/api/history/route.ts @@ -1,6 +1,6 @@ import type { NextRequest } from "next/server"; import { auth } from "@/app/(auth)/auth"; -import { getChatsByUserId, deleteAllChatsByUserId } from "@/lib/db/queries"; +import { deleteAllChatsByUserId, getChatsByUserId } from "@/lib/db/queries"; import { ChatSDKError } from "@/lib/errors"; export async function GET(request: NextRequest) { diff --git a/app/globals.css b/app/globals.css index 70285a3..814c76b 100644 --- a/app/globals.css +++ b/app/globals.css @@ -1,11 +1,9 @@ @import "tailwindcss"; +@import "katex/dist/katex.min.css"; /* include utility classes in streamdown */ @source '../node_modules/streamdown/dist/index.js'; -/* Add KaTeX CSS for math rendering */ -@import 'katex/dist/katex.min.css'; - /* custom variant for setting dark mode programmatically */ @custom-variant dark (&:is(.dark, .dark *)); diff --git a/components/app-sidebar.tsx b/components/app-sidebar.tsx index 5210804..117089e 100644 --- a/components/app-sidebar.tsx +++ b/components/app-sidebar.tsx @@ -8,7 +8,10 @@ import { toast } from "sonner"; import { useSWRConfig } from "swr"; import { unstable_serialize } from "swr/infinite"; import { PlusIcon, TrashIcon } from "@/components/icons"; -import { SidebarHistory, getChatHistoryPaginationKey } from "@/components/sidebar-history"; +import { + getChatHistoryPaginationKey, + SidebarHistory, +} from "@/components/sidebar-history"; import { SidebarUserNav } from "@/components/sidebar-user-nav"; import { Button } from "@/components/ui/button"; import { @@ -19,7 +22,6 @@ import { SidebarMenu, useSidebar, } from "@/components/ui/sidebar"; -import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip"; import { AlertDialog, AlertDialogAction, @@ -30,6 +32,7 @@ import { AlertDialogHeader, AlertDialogTitle, } from "./ui/alert-dialog"; +import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip"; export function AppSidebar({ user }: { user: User | undefined }) { const router = useRouter(); @@ -118,13 +121,16 @@ export function AppSidebar({ user }: { user: User | undefined }) { {user && } - + Delete all chats? - This action cannot be undone. This will permanently delete all your - chats and remove them from our servers. + This action cannot be undone. This will permanently delete all + your chats and remove them from our servers. diff --git a/components/data-stream-handler.tsx b/components/data-stream-handler.tsx index 65aa0c4..0d5eeeb 100644 --- a/components/data-stream-handler.tsx +++ b/components/data-stream-handler.tsx @@ -1,12 +1,12 @@ "use client"; -import { useEffect, useRef } from "react"; +import { useEffect } from "react"; import { initialArtifactData, useArtifact } from "@/hooks/use-artifact"; import { artifactDefinitions } from "./artifact"; import { useDataStream } from "./data-stream-provider"; export function DataStreamHandler() { - const { dataStream,setDataStream } = useDataStream(); + const { dataStream, setDataStream } = useDataStream(); const { artifact, setArtifact, setMetadata } = useArtifact(); @@ -77,7 +77,7 @@ export function DataStreamHandler() { } }); } - }, [dataStream, setArtifact, setMetadata, artifact]); + }, [dataStream, setArtifact, setMetadata, artifact, setDataStream]); return null; } diff --git a/components/elements/context.tsx b/components/elements/context.tsx index 96af118..8d5d6db 100644 --- a/components/elements/context.tsx +++ b/components/elements/context.tsx @@ -114,7 +114,7 @@ export const Context = ({ className, usage, ...props }: ContextProps) => { className={cn( "inline-flex select-none items-center gap-1 rounded-md text-sm", "cursor-pointer bg-background text-foreground", - "focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 outline-none ring-offset-background", + "outline-none ring-offset-background focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2", className )} type="button" diff --git a/components/message.tsx b/components/message.tsx index 41fd613..5598821 100644 --- a/components/message.tsx +++ b/components/message.tsx @@ -1,7 +1,6 @@ "use client"; import type { UseChatHelpers } from "@ai-sdk/react"; import equal from "fast-deep-equal"; -import { motion } from "framer-motion"; import { memo, useState } from "react"; import type { Vote } from "@/lib/db/schema"; import type { ChatMessage } from "@/lib/types"; @@ -33,7 +32,7 @@ const PurePreviewMessage = ({ setMessages, regenerate, isReadonly, - requiresScrollPadding, + requiresScrollPadding: _requiresScrollPadding, }: { chatId: string; message: ChatMessage; @@ -53,12 +52,10 @@ const PurePreviewMessage = ({ useDataStream(); return ( -
p.type === "text" && p.text?.trim() ), - "min-h-96": message.role === "assistant" && requiresScrollPadding, "w-full": (message.role === "assistant" && message.parts?.some( @@ -282,7 +278,7 @@ const PurePreviewMessage = ({ )}
-
+ ); }; @@ -310,30 +306,30 @@ export const PreviewMessage = memo( ); export const ThinkingMessage = () => { - const role = "assistant"; - return ( -
- +
+ +
-
- Thinking... +
+ Thinking + + . + . + . +
- +
); }; - diff --git a/components/messages.tsx b/components/messages.tsx index 8e0ef08..d9a3e16 100644 --- a/components/messages.tsx +++ b/components/messages.tsx @@ -1,13 +1,11 @@ import type { UseChatHelpers } from "@ai-sdk/react"; import equal from "fast-deep-equal"; -import { AnimatePresence } from "framer-motion"; import { ArrowDownIcon } from "lucide-react"; -import { memo, useEffect } from "react"; +import { memo } from "react"; import { useMessages } from "@/hooks/use-messages"; import type { Vote } from "@/lib/db/schema"; import type { ChatMessage } from "@/lib/types"; import { useDataStream } from "./data-stream-provider"; -import { Conversation, ConversationContent } from "./elements/conversation"; import { Greeting } from "./greeting"; import { PreviewMessage, ThinkingMessage } from "./message"; @@ -31,7 +29,7 @@ function PureMessages({ setMessages, regenerate, isReadonly, - selectedModelId, + selectedModelId: _selectedModelId, }: MessagesProps) { const { containerRef: messagesContainerRef, @@ -45,62 +43,41 @@ function PureMessages({ useDataStream(); - useEffect(() => { - if (status === "submitted") { - requestAnimationFrame(() => { - const container = messagesContainerRef.current; - if (container) { - container.scrollTo({ - top: container.scrollHeight, - behavior: "smooth", - }); - } - }); - } - }, [status, messagesContainerRef]); - return (
- - - {messages.length === 0 && } +
+ {messages.length === 0 && } - {messages.map((message, index) => ( - vote.messageId === message.id) - : undefined - } - /> - ))} - - - {status === "submitted" && } - - -
( + vote.messageId === message.id) + : undefined + } /> - - + ))} + + {status === "submitted" && } + +
+
{!isAtBottom && (