fix: reorder playground messages, fix scroll behavior (#7928)

* updated vite-env to stop svg loading lint errors

* added scroll direction package

* added new Chat Scroll Anchor

* removed scroll handling from chat message

* added scroll handling on chat view

* removed console.log

* Removed validator from table and added on messagebase

* removed validator from model

* changed to scroll down after error

* [autofix.ci] apply automated fixes

* fixed not scrolling to bottom

* fix constant

* refactor: update MessageTable model configuration for validation and type allowance

* refactor: update properties type in MessageTable model and adjust validation logic

* refactor: update content_blocks type in MessageTable model to allow dict or ContentBlock

* Fix playground failing when it's Run Flow

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Carlos Coelho <80289056+carlosrcoelho@users.noreply.github.com>
Co-authored-by: Gabriel Luiz Freitas Almeida <gabriel@langflow.org>
Co-authored-by: Edwin Jose <edwin.jose@datastax.com>
This commit is contained in:
Lucas Oliveira 2025-05-08 16:41:54 -03:00 • committed by GitHub
commit be1caf2b61
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 139 additions and 65 deletions

View file

@ -27,6 +27,7 @@
"@radix-ui/react-switch": "^1.0.3",
"@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-tooltip": "^1.0.7",
"@smakss/react-scroll-direction": "^4.2.0",
"@tabler/icons-react": "^3.6.0",
"@tailwindcss/forms": "^0.5.7",
"@tailwindcss/line-clamp": "^0.4.4",
@ -4307,6 +4308,18 @@
"url": "https://github.com/sindresorhus/is?sponsor=1"
}
},
"node_modules/@smakss/react-scroll-direction": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/@smakss/react-scroll-direction/-/react-scroll-direction-4.2.0.tgz",
"integrity": "sha512-Nim3WSq53mvTfXOpYAJMaXTbXD2vL9Zjip87FHRXtb84uLJn83dBkF8/nKENCCvWqL4p4xcdRPVXsX3DhHJbVA==",
"license": "MIT",
"engines": {
"node": ">=18.0.0"
},
"peerDependencies": {
"react": ">=16.8.0"
}
},
"node_modules/@socket.io/component-emitter": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",

View file

@ -22,6 +22,7 @@
"@radix-ui/react-switch": "^1.0.3",
"@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-tooltip": "^1.0.7",
"@smakss/react-scroll-direction": "^4.2.0",
"@tabler/icons-react": "^3.6.0",
"@tailwindcss/forms": "^0.5.7",
"@tailwindcss/line-clamp": "^0.4.4",

View file

@ -5,7 +5,6 @@ import { CustomProfileIcon } from "@/customization/components/custom-profile-ico
import { ENABLE_DATASTAX_LANGFLOW } from "@/customization/feature-flags";
import useFlowsManagerStore from "@/stores/flowsManagerStore";
import useFlowStore from "@/stores/flowStore";
import { useUtilityStore } from "@/stores/utilityStore";
import Convert from "ansi-to-html";
import { useEffect, useRef, useState } from "react";
import Robot from "../../../../../assets/robot.png";
@ -14,7 +13,6 @@ import IconComponent, {
} from "../../../../../components/common/genericIconComponent";
import SanitizedHTMLWrapper from "../../../../../components/common/sanitizedHTMLWrapper";
import { EMPTY_INPUT_SEND_MESSAGE } from "../../../../../constants/constants";
import useTabVisibility from "../../../../../shared/hooks/use-tab-visibility";
import useAlertStore from "../../../../../stores/alertStore";
import { chatMessagePropsType } from "../../../../../types/components";
import { cn } from "../../../../../utils/utils";
@ -58,13 +56,6 @@ export default function ChatMessage({
chatMessageRef.current = chatMessage;
}, [chat, isBuilding]);
const playgroundScrollBehaves = useUtilityStore(
(state) => state.playgroundScrollBehaves,
);
const setPlaygroundScrollBehaves = useUtilityStore(
(state) => state.setPlaygroundScrollBehaves,
);
// The idea now is that chat.stream_url MAY be a URL if we should stream the output of the chat
// probably the message is empty when we have a stream_url
// what we need is to update the chat_message with the SSE data
@ -119,22 +110,6 @@ export default function ChatMessage({
};
}, []);
const isTabHidden = useTabVisibility();
useEffect(() => {
const element = document.getElementById("last-chat-message");
if (element && isTabHidden) {
if (playgroundScrollBehaves === "instant") {
element.scrollIntoView({ behavior: playgroundScrollBehaves });
setPlaygroundScrollBehaves("smooth");
} else {
setTimeout(() => {
element.scrollIntoView({ behavior: playgroundScrollBehaves });
}, 200);
}
}
}, [lastMessage, chat]);
useEffect(() => {
if (chat.category === "error") {
// Short delay before showing error to allow for loading animation
@ -374,7 +349,7 @@ export default function ChatMessage({
className="h-8 w-8 animate-pulse"
/>
) : (
<div className="w-full">
<div className="min-h-8 w-full">
{editMessage ? (
<EditMessageField
key={`edit-message-${chat.id}`}

View file

@ -0,0 +1,50 @@
import { useUtilityStore } from "@/stores/utilityStore";
import { ChatMessageType, ChatType } from "@/types/chat";
import { useEffect, useRef } from "react";
interface ChatScrollAnchorProps {
trackVisibility: ChatMessageType;
canScroll: boolean;
}
export function ChatScrollAnchor({
trackVisibility,
canScroll,
}: ChatScrollAnchorProps) {
const scrollRef = useRef<HTMLDivElement>(null);
const playgroundScrollBehaves = useUtilityStore(
(state) => state.playgroundScrollBehaves,
);
const setPlaygroundScrollBehaves = useUtilityStore(
(state) => state.setPlaygroundScrollBehaves,
);
useEffect(() => {
if (canScroll) {
if (!scrollRef.current) return;
if (
playgroundScrollBehaves === "instant" ||
trackVisibility.category === "error"
) {
scrollRef.current.scrollIntoView({
behavior: playgroundScrollBehaves,
});
setTimeout(() => {
if (!scrollRef.current) return;
scrollRef.current.scrollIntoView({
behavior: "smooth",
});
}, 400);
setPlaygroundScrollBehaves("smooth");
} else {
scrollRef.current.scrollIntoView({
behavior: playgroundScrollBehaves,
});
}
}
}, [canScroll, trackVisibility]);
return <div ref={scrollRef} className="h-px w-full" />;
}

View file

@ -6,6 +6,10 @@ import { useMessagesStore } from "@/stores/messagesStore";
import { useUtilityStore } from "@/stores/utilityStore";
import { useVoiceStore } from "@/stores/voiceStore";
import { cn } from "@/utils/utils";
import useDetectScroll, {
Axis,
Direction,
} from "@smakss/react-scroll-direction";
import { memo, useEffect, useMemo, useRef, useState } from "react";
import { v5 as uuidv5 } from "uuid";
import useTabVisibility from "../../../../../shared/hooks/use-tab-visibility";
@ -18,6 +22,7 @@ import ChatInput from "../chatInput/chat-input";
import useDragAndDrop from "../chatInput/hooks/use-drag-and-drop";
import { useFileHandler } from "../chatInput/hooks/use-file-handler";
import ChatMessage from "../chatMessage/chat-message";
import { ChatScrollAnchor } from "./chat-scroll-anchor";
const MemoizedChatMessage = memo(ChatMessage, (prevProps, nextProps) => {
return (
@ -115,12 +120,6 @@ export default function ChatView({
setChatHistory(finalChatHistory);
}, [messages, visibleSession]);
useEffect(() => {
if (messagesRef.current) {
messagesRef.current.scrollTop = messagesRef.current.scrollHeight;
}
}, []);
const ref = useRef<HTMLDivElement | null>(null);
useEffect(() => {
@ -171,6 +170,44 @@ export default function ChatView({
(state) => state.isVoiceAssistantActive,
);
const [customElement, setCustomElement] = useState<HTMLDivElement>();
useEffect(() => {
if (messagesRef.current) {
setCustomElement(messagesRef.current);
}
}, [messagesRef]);
const { scrollDir } = useDetectScroll({
target: customElement,
axis: Axis.Y,
thr: 0,
});
const [canScroll, setCanScroll] = useState<boolean>(false);
const [scrolledUp, setScrolledUp] = useState<boolean>(false);
const handleScroll = () => {
if (!messagesRef.current) return;
const { scrollTop, scrollHeight, clientHeight } = messagesRef.current;
const atBottom = scrollHeight - clientHeight <= scrollTop + 3;
if (scrollDir === Direction.Up) {
setCanScroll(false);
setScrolledUp(true);
} else {
if (atBottom || !scrolledUp) {
setCanScroll(true);
}
setScrolledUp(false);
}
};
useEffect(() => {
setCanScroll(true);
}, [chatHistory?.length]);
return (
<div
className={cn(
@ -185,7 +222,11 @@ export default function ChatView({
onDragLeave={dragLeave}
onDrop={onDrop}
>
<div ref={messagesRef} className="chat-message-div">
<div
ref={messagesRef}
onScroll={handleScroll}
className="chat-message-div"
>
{chatHistory &&
(isBuilding || chatHistory?.length > 0 ? (
<>
@ -199,6 +240,12 @@ export default function ChatView({
playgroundPage={playgroundPage}
/>
))}
{chatHistory?.length > 0 && (
<ChatScrollAnchor
trackVisibility={chatHistory?.[chatHistory.length - 1]}
canScroll={canScroll}
/>
)}
</>
) : (
<>

View file

@ -1,4 +1,5 @@
/// <reference types="vite/client" />
/// <reference types="vite-plugin-svgr/client" />
declare module "*.svg" {
const content: string;