chat interface
This commit is contained in:
@@ -2,6 +2,8 @@
|
|||||||
import { useRouter } from 'next/navigation'
|
import { useRouter } from 'next/navigation'
|
||||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '~/components/ui/dialog'
|
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '~/components/ui/dialog'
|
||||||
import ChatInterface from '~/app/chat/_components/ChatInterface'
|
import ChatInterface from '~/app/chat/_components/ChatInterface'
|
||||||
|
import { useMessages } from '~/app/_providers/MessagesProvider';
|
||||||
|
import { Spinner } from '~/components/ui/spinner';
|
||||||
|
|
||||||
type DBMessage = {
|
type DBMessage = {
|
||||||
id: string
|
id: string
|
||||||
@@ -9,14 +11,9 @@ type DBMessage = {
|
|||||||
content: string
|
content: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ChatModalProps {
|
export default function ChatModal() {
|
||||||
sessionId?: string
|
|
||||||
// initialMessages: DBMessage[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function ChatModal({ sessionId }: ChatModalProps) {
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const {messages,session,clearChat,clearingChat,isLoading,error,refetchMessages} = useMessages()
|
||||||
return (
|
return (
|
||||||
<Dialog modal={true} open onOpenChange={() => router.back()}>
|
<Dialog modal={true} open onOpenChange={() => router.back()}>
|
||||||
<DialogContent className="w-full max-w-full rounded-none sm:max-w-full h-[100svh] lg:max-w-3xl lg:rounded-xl lg:h-[80vh] flex flex-col p-0 gap-0">
|
<DialogContent className="w-full max-w-full rounded-none sm:max-w-full h-[100svh] lg:max-w-3xl lg:rounded-xl lg:h-[80vh] flex flex-col p-0 gap-0">
|
||||||
@@ -24,7 +21,15 @@ export default function ChatModal({ sessionId }: ChatModalProps) {
|
|||||||
<DialogTitle>Talk To My AI-Assistant</DialogTitle>
|
<DialogTitle>Talk To My AI-Assistant</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="flex-1 overflow-hidden min-h-0">
|
<div className="flex-1 overflow-hidden min-h-0">
|
||||||
<ChatInterface sessionId={sessionId} />
|
{messages && session?.id &&
|
||||||
|
<ChatInterface sessionId={session.id} dbMessages={messages}/>
|
||||||
|
}
|
||||||
|
{isLoading &&
|
||||||
|
<><Spinner/> Loading Messages...</>
|
||||||
|
}
|
||||||
|
{error &&
|
||||||
|
<div> {error} </div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export default function AssistantModalPage() {
|
|||||||
const { data: session, error, isLoading } = trpc.chat.getSession.useQuery();
|
const { data: session, error, isLoading } = trpc.chat.getSession.useQuery();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ChatModal sessionId={session?.id} />
|
<ChatModal/>
|
||||||
{error && <div>{error.message}</div>}
|
{error && <div>{error.message}</div>}
|
||||||
{isLoading && <Skeleton />}
|
{isLoading && <Skeleton />}
|
||||||
</>
|
</>
|
||||||
|
|||||||
69
src/app/_providers/MessagesProvider.tsx
Normal file
69
src/app/_providers/MessagesProvider.tsx
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
'use client'
|
||||||
|
import type { inferRouterOutputs } from '@trpc/server';
|
||||||
|
import { createContext, useContext, useEffect, useState, type ReactNode } from 'react'
|
||||||
|
import { trpc } from '~/app/_trpc/Client'
|
||||||
|
import { type ChatRouter } from '~/server/routers/chat'
|
||||||
|
const MessageContext = createContext<{
|
||||||
|
session?: inferRouterOutputs<ChatRouter>['getSession']
|
||||||
|
messages?: inferRouterOutputs<ChatRouter>['getMessages']
|
||||||
|
refetchMessages: () => void
|
||||||
|
clearChat: (callback?: () => void) => void
|
||||||
|
error: string|null
|
||||||
|
isLoading: boolean
|
||||||
|
clearingChat: boolean
|
||||||
|
clearedChat: boolean
|
||||||
|
}>({
|
||||||
|
session: undefined,
|
||||||
|
messages: undefined,
|
||||||
|
refetchMessages: () => undefined,
|
||||||
|
clearChat: () => undefined,
|
||||||
|
error: null,
|
||||||
|
isLoading: true,
|
||||||
|
clearingChat: false,
|
||||||
|
clearedChat: false
|
||||||
|
})
|
||||||
|
export const useMessages = () => useContext(MessageContext)
|
||||||
|
export const MessagesProvider = ({children}:{children:ReactNode}) => {
|
||||||
|
const [error,setError] = useState<string|null>(null)
|
||||||
|
const [isLoading,setIsLoading] = useState<boolean>(true)
|
||||||
|
const { data: session,error:sessionError,isLoading:sessionLoading} = trpc.chat.getSession.useQuery()
|
||||||
|
const { data: messages, refetch, error:messageError, isLoading:messagesLoading } = trpc.chat.getMessages.useQuery(session?.id ? session.id : "")
|
||||||
|
const { mutate ,isPending:clearingChat,isSuccess:clearedChat } = trpc.chat.clearChat.useMutation()
|
||||||
|
const utils = trpc.useUtils()
|
||||||
|
const refetchMessages = () => {
|
||||||
|
utils.chat.getMessages.invalidate()
|
||||||
|
refetch()
|
||||||
|
}
|
||||||
|
const clearChat = (callback?: () => void) => {
|
||||||
|
mutate(undefined,{onSuccess: () => {
|
||||||
|
if (callback) {
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
utils.chat.getMessages.invalidate()
|
||||||
|
}})
|
||||||
|
}
|
||||||
|
useEffect(() => {
|
||||||
|
messageError && setError(messageError.message)
|
||||||
|
sessionError && setError(sessionError.message)
|
||||||
|
},[messageError,sessionError])
|
||||||
|
useEffect(() => {
|
||||||
|
!sessionLoading && !messagesLoading && setIsLoading(false)
|
||||||
|
sessionLoading || messagesLoading && setIsLoading(true)
|
||||||
|
},[sessionLoading,messagesLoading])
|
||||||
|
return (
|
||||||
|
<MessageContext.Provider value={
|
||||||
|
{
|
||||||
|
session,
|
||||||
|
messages,
|
||||||
|
refetchMessages,
|
||||||
|
clearChat,
|
||||||
|
error,
|
||||||
|
isLoading,
|
||||||
|
clearingChat,
|
||||||
|
clearedChat
|
||||||
|
}
|
||||||
|
}>
|
||||||
|
{children}
|
||||||
|
</MessageContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -16,7 +16,7 @@ export const AssistantMessage = (props: { message: UIMessage }) => {
|
|||||||
{message.parts.map((part, i) => {
|
{message.parts.map((part, i) => {
|
||||||
if (part.type === 'text') {
|
if (part.type === 'text') {
|
||||||
return (
|
return (
|
||||||
<Markdown key={i}>
|
<Markdown>
|
||||||
{part.text}
|
{part.text}
|
||||||
</Markdown>
|
</Markdown>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -9,9 +9,8 @@ import {
|
|||||||
} from '~/app/_providers/GsapProvicer';
|
} from '~/app/_providers/GsapProvicer';
|
||||||
import Messages from './Messages'
|
import Messages from './Messages'
|
||||||
import { DeleteIcon } from 'lucide-react';
|
import { DeleteIcon } from 'lucide-react';
|
||||||
import { trpc } from '~/app/_trpc/Client'
|
|
||||||
import { Spinner } from '~/components/ui/spinner';
|
import { Spinner } from '~/components/ui/spinner';
|
||||||
import { Skeleton } from '~/components/ui/skeleton';
|
import { useMessages } from '~/app/_providers/MessagesProvider';
|
||||||
interface DBMessage {
|
interface DBMessage {
|
||||||
id: string
|
id: string
|
||||||
role: 'user' | 'assistant'
|
role: 'user' | 'assistant'
|
||||||
@@ -19,8 +18,8 @@ interface DBMessage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface ChatInterfaceProps {
|
interface ChatInterfaceProps {
|
||||||
sessionId?: string
|
sessionId: string,
|
||||||
// initialMessages: DBMessage[]
|
dbMessages: DBMessage[],
|
||||||
}
|
}
|
||||||
|
|
||||||
function toUIMessages(dbMessages: DBMessage[]): UIMessage[] {
|
function toUIMessages(dbMessages: DBMessage[]): UIMessage[] {
|
||||||
@@ -30,28 +29,10 @@ function toUIMessages(dbMessages: DBMessage[]): UIMessage[] {
|
|||||||
parts: [{ type: 'text' as const, text: m.content }],
|
parts: [{ type: 'text' as const, text: m.content }],
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
export default function ChatInterface({ sessionId }: ChatInterfaceProps) {
|
|
||||||
if (!sessionId) {
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col h-full">
|
|
||||||
<Skeleton className="w-full"/>
|
|
||||||
<Skeleton className="w-[20%]"/>
|
|
||||||
<Skeleton className='w-[45%]'/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
const utils = trpc.useUtils();
|
|
||||||
const { data: dbMessages, refetch: refetchMessages } = trpc.chat.getMessages.useQuery(sessionId)
|
|
||||||
const [messages, setMessages] = useState<UIMessage[]>([]);
|
|
||||||
function addMessage(newMessage: UIMessage) {
|
|
||||||
setMessages(prev => [...prev, newMessage]);
|
|
||||||
}
|
|
||||||
useEffect(() => {
|
|
||||||
setMessages(toUIMessages(dbMessages ?? []));
|
|
||||||
}, [dbMessages]);
|
|
||||||
|
|
||||||
if (messages.at(0)?.id != 'init') {
|
function addInitMessage(messageArray: UIMessage[]) {
|
||||||
messages.unshift({
|
if (messageArray.at(0)?.id != 'init') {
|
||||||
|
messageArray.unshift({
|
||||||
id: "init",
|
id: "init",
|
||||||
role: 'assistant',
|
role: 'assistant',
|
||||||
parts: [{
|
parts: [{
|
||||||
@@ -60,46 +41,31 @@ export default function ChatInterface({ sessionId }: ChatInterfaceProps) {
|
|||||||
}],
|
}],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ChatInterface({ dbMessages, sessionId }: ChatInterfaceProps) {
|
||||||
const [input, setInput] = useState('')
|
const [input, setInput] = useState('')
|
||||||
const { sendMessage, status, error, clearError } = useChat({
|
const { clearingChat, clearChat, refetchMessages } = useMessages();
|
||||||
|
const initialMessages = toUIMessages(dbMessages)
|
||||||
|
addInitMessage(initialMessages)
|
||||||
|
const { messages, sendMessage, status, error, clearError, setMessages } = useChat({
|
||||||
transport: new DefaultChatTransport({
|
transport: new DefaultChatTransport({
|
||||||
api: '/api/chat', body: { sessionId },
|
api: '/api/chat', body: { sessionId },
|
||||||
}),
|
}),
|
||||||
messages: messages,
|
messages: initialMessages,
|
||||||
})
|
})
|
||||||
const handleSend = () => {
|
|
||||||
const text = input.trim()
|
|
||||||
if (!text || status != 'ready' || sessionId == undefined) return
|
|
||||||
setInput('')
|
|
||||||
sendMessage({ text })
|
|
||||||
addMessage({
|
|
||||||
id: "", role: "user", parts: [
|
|
||||||
{ type: 'text', text }
|
|
||||||
]
|
|
||||||
})
|
|
||||||
addMessage({
|
|
||||||
id: "", role: "assistant", parts: [
|
|
||||||
{ type: 'text', text: "Thinking..." }
|
|
||||||
]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
const clearChatMutation = trpc.chat.clearChat.useMutation()
|
|
||||||
const handleClear = () => {
|
|
||||||
clearChatMutation.mutate(undefined, {
|
|
||||||
onSuccess: () => {
|
|
||||||
utils.chat.getMessages.invalidate()
|
|
||||||
refetchMessages()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
const gsapContext = useGsapContext()
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log(status)
|
return () => {
|
||||||
if (status == 'ready') {
|
|
||||||
utils.chat.getMessages.invalidate();
|
|
||||||
refetchMessages()
|
refetchMessages()
|
||||||
}
|
}
|
||||||
}, [status])
|
}, [])
|
||||||
|
const handleSend = () => {
|
||||||
|
const text = input.trim()
|
||||||
|
if (!text || status != 'ready' || clearingChat) return
|
||||||
|
setInput('')
|
||||||
|
sendMessage({ text })
|
||||||
|
}
|
||||||
|
const gsapContext = useGsapContext()
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let scroller = gsapContext?.getScroller()
|
let scroller = gsapContext?.getScroller()
|
||||||
if (scroller instanceof Window) {
|
if (scroller instanceof Window) {
|
||||||
@@ -111,9 +77,8 @@ export default function ChatInterface({ sessionId }: ChatInterfaceProps) {
|
|||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-full">
|
<div className="flex flex-col h-full">
|
||||||
{messages &&
|
{messages &&
|
||||||
<Messages messages={messages} />
|
<Messages status={status} messages={messages} />
|
||||||
}
|
}
|
||||||
|
|
||||||
{error && (
|
{error && (
|
||||||
<div className="mx-4 mb-2 flex items-start gap-2 rounded-lg border border-destructive/50 bg-destructive/10 px-3 py-2 text-sm text-destructive">
|
<div className="mx-4 mb-2 flex items-start gap-2 rounded-lg border border-destructive/50 bg-destructive/10 px-3 py-2 text-sm text-destructive">
|
||||||
<span className="flex-1">
|
<span className="flex-1">
|
||||||
@@ -156,10 +121,16 @@ export default function ChatInterface({ sessionId }: ChatInterfaceProps) {
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant='destructive'
|
variant='destructive'
|
||||||
onClick={handleClear}
|
onClick={() => {
|
||||||
disabled={status != "ready" || clearChatMutation.isPending}
|
clearChat(() => {
|
||||||
|
let messages: UIMessage[] = [];
|
||||||
|
addInitMessage(messages);
|
||||||
|
setMessages(messages)
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
disabled={status != "ready" || clearingChat}
|
||||||
>
|
>
|
||||||
{clearChatMutation.isPending ?
|
{clearingChat ?
|
||||||
<Spinner /> :
|
<Spinner /> :
|
||||||
"Clear Chat"
|
"Clear Chat"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,35 @@
|
|||||||
import { type UIMessage } from 'ai'
|
import { type ChatStatus, type UIMessage } from 'ai'
|
||||||
import * as Card from "~/components/ui/card"
|
import * as Card from "~/components/ui/card"
|
||||||
import AnimateTextIn from '~/app/_components/Animated/AnimateIn';
|
|
||||||
import { UserMessage } from './UserMessage';
|
import { UserMessage } from './UserMessage';
|
||||||
import { AssistantMessage } from './AssistantMessage';
|
import { AssistantMessage } from './AssistantMessage';
|
||||||
import { ScrollArea } from '~/components/ui/scroll-area';
|
import { ScrollArea } from '~/components/ui/scroll-area';
|
||||||
import { useTimeLine } from '~/app/_providers/GsapProvicer';
|
import { memo } from 'react';
|
||||||
import {
|
const Messages = memo(({messages,status}: { messages: UIMessage[],status:ChatStatus}) => {
|
||||||
memo
|
|
||||||
} from 'react';
|
|
||||||
const Messages = memo(({ messages}: { messages: UIMessage[]}) => {
|
|
||||||
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 h-[90%] max-w-4xl mx-auto">
|
||||||
{messages.map((message, i) => (
|
{messages.map((message, i) => (
|
||||||
<Card.AnimatedCard scrollOnly={true} tlId='chat' position={i * 0.2} key={i}>
|
<Card.AnimatedCard scrollOnly={true} key={i}>
|
||||||
<Card.CardContent>
|
<Card.CardContent>
|
||||||
{message.role == 'assistant' && <AssistantMessage message={message} />}
|
{message.role == 'assistant' && <AssistantMessage message={message} />}
|
||||||
{message.role == 'user' && <UserMessage message={message} />}
|
{message.role == 'user' && <UserMessage message={message} />}
|
||||||
</Card.CardContent>
|
</Card.CardContent>
|
||||||
</Card.AnimatedCard>
|
</Card.AnimatedCard>
|
||||||
))}
|
))}
|
||||||
|
{status == 'submitted' &&
|
||||||
|
<Card.AnimatedCard scrollOnly={true}>
|
||||||
|
<Card.CardContent>
|
||||||
|
<AssistantMessage message={{
|
||||||
|
id:"",
|
||||||
|
role:"assistant",
|
||||||
|
parts:[{
|
||||||
|
type:'text',
|
||||||
|
text:'Thinking ...'
|
||||||
|
}]
|
||||||
|
}}/>
|
||||||
|
</Card.CardContent>
|
||||||
|
</Card.AnimatedCard>
|
||||||
|
|
||||||
|
}
|
||||||
</ScrollArea>)
|
</ScrollArea>)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -4,19 +4,28 @@ import { trpc } from '../_trpc/Client';
|
|||||||
import { Skeleton } from '~/components/ui/skeleton';
|
import { Skeleton } from '~/components/ui/skeleton';
|
||||||
import AnimatedPageTitle from '../_components/Animated/AnimatedPageTitle';
|
import AnimatedPageTitle from '../_components/Animated/AnimatedPageTitle';
|
||||||
import { useTimeLine } from '../_providers/GsapProvicer';
|
import { useTimeLine } from '../_providers/GsapProvicer';
|
||||||
|
import { useMessages } from '../_providers/MessagesProvider';
|
||||||
|
import { Spinner } from '~/components/ui/spinner';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
export default function ChatPage() {
|
export default function ChatPage() {
|
||||||
const { data: session, error, isLoading } = trpc.chat.getSession.useQuery();
|
const {messages,session,clearChat,clearingChat,isLoading,error,refetchMessages} = useMessages()
|
||||||
useTimeLine(session)
|
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">
|
||||||
<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%] my-auto w-full'>
|
<div className='flex items-center h-[80%] w-full my-auto w-full'>
|
||||||
<ChatInterface sessionId={session?.id} />
|
{messages && session?.id &&
|
||||||
{error && <div>{error.message}</div>}
|
<ChatInterface sessionId={session.id} dbMessages={messages}/>
|
||||||
{isLoading && <Skeleton/>}
|
}
|
||||||
|
{isLoading &&
|
||||||
|
<><Spinner/> Loading Messages...</>
|
||||||
|
}
|
||||||
|
{error &&
|
||||||
|
<div> {error} </div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import TrpcProvider from "./_trpc/TrpcProvider";
|
|||||||
// const ThemeProvider = dynamic(() => import("./_providers/ThemeProvider"),{ssr:true})
|
// const ThemeProvider = dynamic(() => import("./_providers/ThemeProvider"),{ssr:true})
|
||||||
import ThemeProvider from './_providers/ThemeProvider'
|
import ThemeProvider from './_providers/ThemeProvider'
|
||||||
import GsapProvider from "./_providers/GsapProvicer";
|
import GsapProvider from "./_providers/GsapProvicer";
|
||||||
|
import {MessagesProvider} from "./_providers/MessagesProvider";
|
||||||
import { CodeHighlightStyle } from "./_components/CodeHighlightSyle";
|
import { CodeHighlightStyle } from "./_components/CodeHighlightSyle";
|
||||||
import { cn } from "~/lib/utils";
|
import { cn } from "~/lib/utils";
|
||||||
import AnimatedBackGroundContainer from "./_components/Animated/AnimatedBackGroundContainer";
|
import AnimatedBackGroundContainer from "./_components/Animated/AnimatedBackGroundContainer";
|
||||||
@@ -45,14 +46,16 @@ export default async function RootLayout({
|
|||||||
</head>
|
</head>
|
||||||
<body className="flex flex-col bg-background text-foreground">
|
<body className="flex flex-col bg-background text-foreground">
|
||||||
<ThemeProvider>
|
<ThemeProvider>
|
||||||
<AnimatedBackGroundContainer followSpeed={0.003} particleCount={100} orbitRadius={2000}>
|
<MessagesProvider>
|
||||||
<TopNav />
|
<AnimatedBackGroundContainer followSpeed={0.003} particleCount={100} orbitRadius={2000}>
|
||||||
<main className="absolute lg:top-10 h-screen lg:h-[calc(100vh-var(--spacing)*10)] w-screen">
|
<TopNav />
|
||||||
{children}
|
<main className="absolute lg:top-10 h-screen lg:h-[calc(100vh-var(--spacing)*10)] w-screen">
|
||||||
</main>
|
{children}
|
||||||
{modal}
|
</main>
|
||||||
</AnimatedBackGroundContainer>
|
{modal}
|
||||||
|
</AnimatedBackGroundContainer>
|
||||||
<ChatFAB />
|
<ChatFAB />
|
||||||
|
</MessagesProvider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -26,10 +26,9 @@ function AnimatedCard({
|
|||||||
className,
|
className,
|
||||||
position = 0,
|
position = 0,
|
||||||
size = "default",
|
size = "default",
|
||||||
tlId = undefined,
|
|
||||||
scrollOnly = false,
|
scrollOnly = false,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<"div"> & { size?: "default" | "sm", position: gsap.Position, tlId?: string, scrollOnly?: boolean }) {
|
}: React.ComponentProps<"div"> & { size?: "default" | "sm", position?: gsap.Position, scrollOnly?: boolean }) {
|
||||||
const gsapContext = useGsapContext()
|
const gsapContext = useGsapContext()
|
||||||
const ref = useRef<HTMLDivElement | null>(null)
|
const ref = useRef<HTMLDivElement | null>(null)
|
||||||
useGSAP(() => {
|
useGSAP(() => {
|
||||||
@@ -47,7 +46,7 @@ function AnimatedCard({
|
|||||||
console.log(isInView)
|
console.log(isInView)
|
||||||
const fromVars = { x: -100, opacity: 0, duration: 0.5 }
|
const fromVars = { x: -100, opacity: 0, duration: 0.5 }
|
||||||
if (isInView && !scrollOnly) {
|
if (isInView && !scrollOnly) {
|
||||||
gsapContext?.addAnimation(gsap.from(ref.current, fromVars), position, tlId)
|
gsapContext?.addAnimation(gsap.from(ref.current, fromVars), position)
|
||||||
} else {
|
} else {
|
||||||
gsap.from(ref.current,
|
gsap.from(ref.current,
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import type { UseTRPCQueryResult } from "node_modules/@trpc/react-query/dist/getQueryKey.d-CruH3ncI.mjs"
|
import type { UseTRPCQueryResult } from "node_modules/@trpc/react-query/dist/getQueryKey.d-CruH3ncI.mjs"
|
||||||
import { useEffect, useState } from "react"
|
import { useEffect, useRef, useState } from "react"
|
||||||
|
|
||||||
function useRelationShipSuccess<T extends Record<string,any> & {id:string},K extends keyof T>(
|
function useRelationShipSuccess<T extends Record<string,any> & {id:string},K extends keyof T>(
|
||||||
relationShipData: T[] | undefined,
|
relationShipData: T[] | undefined,
|
||||||
@@ -56,3 +56,10 @@ export function makeUseRelationShipWithNameIndex<K extends string>(key:K) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function usePrevious<T>(value:T,initialValue:T) {
|
||||||
|
const ref = useRef(initialValue)
|
||||||
|
useEffect(() => {
|
||||||
|
ref.current = value
|
||||||
|
})
|
||||||
|
return ref.current;
|
||||||
|
}
|
||||||
|
|||||||
@@ -75,3 +75,5 @@ export const chatRouter = router({
|
|||||||
await db.insert(systemSettings).values({ systemPropmt: input.prompt })
|
await db.insert(systemSettings).values({ systemPropmt: input.prompt })
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export type ChatRouter = typeof chatRouter;
|
||||||
|
|||||||
Reference in New Issue
Block a user