diff --git a/packages/chat/src/components/message.tsx b/packages/chat/src/components/message.tsx
index dc785a6..51705ff 100644
--- a/packages/chat/src/components/message.tsx
+++ b/packages/chat/src/components/message.tsx
@@ -29,8 +29,24 @@ function MessageComponent({
user: User | null;
}) {
const actuallyFailed = message.failed && message.message_state === "awaiting";
+ const [hasFadedIn, setHasFadedIn] = useState(false);
+ const opacityClass = !hasFadedIn
+ ? "opacity-0"
+ : actuallyFailed || message.message_state === "awaiting"
+ ? "opacity-50"
+ : "opacity-100";
const [isValidURL, setIsValidURL] = useState(false);
+ useEffect(() => {
+ const timeout = window.setTimeout(() => {
+ setHasFadedIn(true);
+ }, 100);
+
+ return () => {
+ window.clearTimeout(timeout);
+ };
+ }, []);
+
useEffect(() => {
try {
if (message.content.split(" ").length > 1) throw new Error();
@@ -45,7 +61,7 @@ function MessageComponent({
return (
(null);
+ const smoothScrollTargetRef = React.useRef(0);
const [messageUsers, setMessageUsers] = React.useState<
Record
@@ -356,17 +357,52 @@ export default function Screen() {
return;
}
+ smoothScrollTargetRef.current = element.scrollTop;
+
+ const animateScroll = () => {
+ const distance = smoothScrollTargetRef.current - element.scrollTop;
+ if (Math.abs(distance) < 0.5) {
+ element.scrollTop = smoothScrollTargetRef.current;
+ smoothScrollFrameRef.current = null;
+ handleContainerScroll();
+ return;
+ }
+
+ element.scrollTop += distance * 0.35;
+ handleContainerScroll();
+ smoothScrollFrameRef.current = requestAnimationFrame(animateScroll);
+ };
+
const handleWheel = (event: WheelEvent) => {
event.preventDefault();
event.stopPropagation();
- element.scrollTop -= event.deltaY;
- handleContainerScroll();
+
+ if (smoothScrollFrameRef.current === null) {
+ smoothScrollTargetRef.current = element.scrollTop;
+ }
+
+ const maxScrollTop = Math.max(
+ 0,
+ element.scrollHeight - element.clientHeight,
+ );
+ smoothScrollTargetRef.current = Math.min(
+ Math.max(0, smoothScrollTargetRef.current - event.deltaY),
+ maxScrollTop,
+ );
+
+ if (smoothScrollFrameRef.current === null) {
+ smoothScrollFrameRef.current = requestAnimationFrame(animateScroll);
+ }
};
element.addEventListener("wheel", handleWheel, { passive: false });
return () => {
element.removeEventListener("wheel", handleWheel);
+ if (smoothScrollFrameRef.current !== null) {
+ cancelAnimationFrame(smoothScrollFrameRef.current);
+ smoothScrollFrameRef.current = null;
+ }
};
}, [handleContainerScroll]);
@@ -391,19 +427,6 @@ export default function Screen() {
}}
onScroll={handleContainerScroll}
>
- {messagesQuery.isPending && (
-
- )}
- {messagesQuery.isFetchingNextPage && (
-
-
-
- )}