4 Commits

Author SHA1 Message Date
d85cc205de blog slug scroll 2026-06-18 02:57:36 +02:00
95666e20e9 Merge branch 'chat-scroll-behaiviour' 2026-06-18 02:53:20 +02:00
993137068e chat scroll to bottom by default 2026-06-18 02:53:06 +02:00
5755bd3184 fix chat mobile cutoff 2026-06-18 02:49:05 +02:00
6 changed files with 21 additions and 14 deletions

View File

@@ -32,7 +32,7 @@ export default async function BlogPostPage({ params }: Props) {
const date = typeof parsed.data.date === "string" ? parsed.data.date : post.date; const date = typeof parsed.data.date === "string" ? parsed.data.date : post.date;
return ( return (
<main className="mx-auto max-w-2xl px-4 py-12"> <main className="mx-auto h-full max-w-2xl overflow-y-auto px-4 py-12">
<header className="mb-8"> <header className="mb-8">
<h1 className="text-3xl font-bold">{title}</h1> <h1 className="text-3xl font-bold">{title}</h1>
{date && ( {date && (

View File

@@ -6,7 +6,7 @@ export default async function BlogPage() {
const posts = await servTrpc.blog.list(); const posts = await servTrpc.blog.list();
return ( return (
<main className="mx-auto max-w-2xl px-4 py-12"> <main className="mx-auto h-full max-w-2xl overflow-y-auto px-4 py-12">
<h1 className="mb-8 text-3xl font-bold">Blog</h1> <h1 className="mb-8 text-3xl font-bold">Blog</h1>
{posts.length === 0 ? ( {posts.length === 0 ? (
<p className="text-muted-foreground">No posts yet.</p> <p className="text-muted-foreground">No posts yet.</p>

View File

@@ -1,5 +1,5 @@
'use client' 'use client'
import { useState, useEffect } from 'react' import { useState, useEffect, useRef } from 'react'
import { useChat } from '@ai-sdk/react' import { useChat } from '@ai-sdk/react'
import { DefaultChatTransport, type UIMessage } from 'ai' import { DefaultChatTransport, type UIMessage } from 'ai'
import { Button } from '~/components/ui/button' import { Button } from '~/components/ui/button'
@@ -83,14 +83,21 @@ function AuthenticatedChatInterface({ dbMessages, sessionId }: ChatInterfaceProp
sendMessage({ text }) sendMessage({ text })
} }
const gsapContext = useGsapContext() const gsapContext = useGsapContext()
const didInitialScroll = useRef(false)
useEffect(() => { useEffect(() => {
let scroller = gsapContext?.getScroller() const scroller = gsapContext?.getScroller()
if (scroller instanceof Window) { if (!scroller || scroller instanceof Window) {
return; return
} }
console.log(scroller?.scrollHeight) // Jump instantly on first open so the chat starts pinned to the bottom;
scroller?.scrollTo({ behavior: 'smooth', top: scroller.scrollHeight }) // animate subsequent updates. Defer a frame so the messages have laid out
}, [messages]) // (and any streaming content has grown) before we measure scrollHeight.
const behavior: ScrollBehavior = didInitialScroll.current ? 'smooth' : 'auto'
didInitialScroll.current = true
requestAnimationFrame(() => {
scroller.scrollTo({ behavior, top: scroller.scrollHeight })
})
}, [messages, status])
return ( return (
<div className="flex flex-col h-full"> <div className="flex flex-col h-full">
{messages && {messages &&
@@ -114,7 +121,7 @@ function AuthenticatedChatInterface({ dbMessages, sessionId }: ChatInterfaceProp
</div> </div>
)} )}
<div className="p-4 border-t flex flex-row gap-2"> <div className="p-4 border-t flex flex-row gap-2 shrink-0">
<Textarea <Textarea
name='message' name='message'
value={input} value={input}

View File

@@ -6,7 +6,7 @@ import { ScrollArea } from '~/components/ui/scroll-area';
import { memo } from 'react'; import { memo } from 'react';
const Messages = memo(({messages,status}: { messages: UIMessage[],status:ChatStatus}) => { const Messages = memo(({messages,status}: { messages: UIMessage[],status:ChatStatus}) => {
return ( return (
<ScrollArea data-scroller-priority='1' className="w-full h-[90%] max-w-4xl mx-auto"> <ScrollArea data-scroller-priority='1' className="w-full flex-1 min-h-0 max-w-4xl mx-auto">
{messages.map((message, i) => ( {messages.map((message, i) => (
<Card.AnimatedCard scrollOnly={true} key={i}> <Card.AnimatedCard scrollOnly={true} key={i}>
<Card.CardContent> <Card.CardContent>

View File

@@ -9,11 +9,11 @@ export default function ChatPage() {
const {messages,session,isLoading,error} = useMessages() const {messages,session,isLoading,error} = useMessages()
useTimeLine(messages) useTimeLine(messages)
return ( return (
<div className="flex flex-col px-10 lg:px-0 w-full h-full max-w-4xl mx-auto pt-10"> <div className="flex flex-col px-10 lg:px-0 w-full h-full max-w-4xl mx-auto pt-10 pb-4">
<AnimatedPageTitle position={0}> <AnimatedPageTitle position={0}>
<span>Talk To My </span> <span> AI-Assistant</span> <span>Talk To My </span> <span> AI-Assistant</span>
</AnimatedPageTitle> </AnimatedPageTitle>
<div className='flex items-center h-[80%] w-full my-auto w-full'> <div className='flex flex-1 min-h-0 w-full'>
{!isLoading && {!isLoading &&
<ChatInterface sessionId={session?.id} dbMessages={messages ?? []}/> <ChatInterface sessionId={session?.id} dbMessages={messages ?? []}/>
} }

View File

@@ -53,7 +53,7 @@ export default async function RootLayout({
<MusicPlayerProvider> <MusicPlayerProvider>
<AnimatedBackGroundContainer followSpeed={0.003} particleCount={100} orbitRadius={2000}> <AnimatedBackGroundContainer followSpeed={0.003} particleCount={100} orbitRadius={2000}>
<TopNav /> <TopNav />
<main className="absolute lg:top-10 h-screen lg:h-[calc(100vh-var(--spacing)*10)] w-screen"> <main className="absolute lg:top-10 h-[100dvh] lg:h-[calc(100vh-var(--spacing)*10)] w-screen">
{children} {children}
</main> </main>
{modal} {modal}