-
- {messages.length === 0 && (
-
-
Hi! I'm Gregor's AI recruiter assistant.
-
Ask me about his skills and experience, or schedule a meeting!
-
- )}
-
- {messages.map((message) => (
- <>
- {message.role == 'assistant' &&
}
- {message.role == 'user' && }
- >
- ))}
-
- {isLoading && (
-
- )}
-
-
-
+ {messages &&
+
+ }
{error && (
@@ -89,19 +111,20 @@ export default function ChatInterface({ sessionId, initialMessages }: ChatInterf
? 'OpenAI quota exceeded. Please try again later.'
: `Error: ${error.message}`}
-
+
+
)}
-
)
diff --git a/src/app/chat/_components/Messages.tsx b/src/app/chat/_components/Messages.tsx
new file mode 100644
index 0000000..2095112
--- /dev/null
+++ b/src/app/chat/_components/Messages.tsx
@@ -0,0 +1,25 @@
+import { type UIMessage } from 'ai'
+import * as Card from "~/components/ui/card"
+import AnimateTextIn from '~/app/_components/Animated/AnimateIn';
+import { UserMessage } from './UserMessage';
+import { AssistantMessage } from './AssistantMessage';
+import { ScrollArea } from '~/components/ui/scroll-area';
+import { useTimeLine } from '~/app/_providers/GsapProvicer';
+import {
+ memo
+ } from 'react';
+const Messages = memo(({ messages}: { messages: UIMessage[]}) => {
+ return (
+
+ {messages.map((message, i) => (
+
+
+ {message.role == 'assistant' && }
+ {message.role == 'user' && }
+
+
+ ))}
+ )
+})
+
+export default Messages;
diff --git a/src/app/chat/page.tsx b/src/app/chat/page.tsx
index 00eb1bd..7c7eac0 100644
--- a/src/app/chat/page.tsx
+++ b/src/app/chat/page.tsx
@@ -2,26 +2,22 @@
import ChatInterface from './_components/ChatInterface'
import { trpc } from '../_trpc/Client';
import { Skeleton } from '~/components/ui/skeleton';
+import AnimatedPageTitle from '../_components/Animated/AnimatedPageTitle';
+import { useTimeLine } from '../_providers/GsapProvicer';
export default function ChatPage() {
const { data: session, error, isLoading } = trpc.chat.getSession.useQuery();
+ useTimeLine(session)
return (
-
-
-
-
-
AI Recruiter
-
- Chat with Gregor's AI assistant
-
-
-
-
- {session &&
}
+
+
+ Talk To My AI-Assistant
+
+
+
{error &&
{error.message}
}
{isLoading &&
}
-
-
+
)
}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 4775241..a71e990 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -38,7 +38,7 @@ export default async function RootLayout({
return (
-
+
@@ -47,7 +47,7 @@ export default async function RootLayout({
-
+
{children}
{modal}
diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx
index 2160864..8e33bc5 100644
--- a/src/components/ui/card.tsx
+++ b/src/components/ui/card.tsx
@@ -1,4 +1,4 @@
-import { useGSAP } from "@gsap/react";import * as React from "react"
+import { useGSAP } from "@gsap/react"; import * as React from "react"
import { useRef } from "react";
import { useGsapContext } from "~/app/_providers/GsapProvicer";
import gsap from 'gsap'
@@ -26,20 +26,40 @@ function AnimatedCard({
className,
position = 0,
size = "default",
+ tlId = undefined,
+ scrollOnly = false,
...props
-}: React.ComponentProps<"div"> & { size?: "default" | "sm", position: gsap.Position }) {
+}: React.ComponentProps<"div"> & { size?: "default" | "sm", position: gsap.Position, tlId?: string, scrollOnly?: boolean }) {
const gsapContext = useGsapContext()
- const ref = useRef(null)
+ const ref = useRef(null)
useGSAP(() => {
const rect = ref.current?.getBoundingClientRect()
- const isInView = rect && rect.top < window.innerHeight
+ const scroller = gsapContext?.getScroller()
+ console.log(scroller)
+ let viewportTop = 0
+ let viewportBottom = window.innerHeight
+ if (scroller && scroller instanceof Element) {
+ const scrollerRect = scroller.getBoundingClientRect()
+ viewportTop = scrollerRect.top
+ viewportBottom = scrollerRect.top + scrollerRect.height
+ }
+ const isInView = rect && rect.bottom > viewportTop && rect.top < viewportBottom
+ console.log(isInView)
const fromVars = { x: -100, opacity: 0, duration: 0.5 }
- if (isInView) {
- gsapContext?.addAnimation(gsap.from(ref.current, fromVars), position)
+ if (isInView && !scrollOnly) {
+ gsapContext?.addAnimation(gsap.from(ref.current, fromVars), position, tlId)
} else {
- const scroller = gsapContext?.getScroller()
- console.log('scroller:', scroller)
- gsap.from(ref.current, { ...fromVars, scrollTrigger: { trigger: ref.current, start: 'top 85%', scroller } })
+ gsap.from(ref.current,
+ {
+ ...fromVars,
+ scrollTrigger: {
+ trigger: ref.current,
+ start: 'top bottom',
+ end: 'bottom top',
+ toggleActions: "play reverse play reverse",
+ scroller
+ }
+ })
}
}, { dependencies: [] })
return (
diff --git a/src/server/routers/chat.ts b/src/server/routers/chat.ts
index 97e9a5d..c628fbc 100644
--- a/src/server/routers/chat.ts
+++ b/src/server/routers/chat.ts
@@ -2,45 +2,71 @@ import { auth } from '@clerk/nextjs/server'
import { publicProcedure, router } from "../trpc";
import { TRPCError } from "@trpc/server";
import { db } from '~/server/db'
-import { chatSession, systemSettings } from "../dbschema/schema";
+import { chatMessage,
+chatSession, systemSettings } from "../dbschema/schema";
import { isAdmin } from '~/app/actions';
import { z } from 'zod';
+import { eq } from 'drizzle-orm';
export const chatRouter = router({
getSession: publicProcedure.query(async () => {
const { userId } = await auth();
if (userId == null) {
- throw new TRPCError({message: "chat is only available to signed in users",code: 'UNAUTHORIZED'});
+ throw new TRPCError({ message: "chat is only available to signed in users", code: 'UNAUTHORIZED' });
}
let session = await db.query.chatSession.findFirst({
- with: {
- messages: true
- },
where(fields, operators) {
- return operators.eq(fields.userId,userId)
+ return operators.eq(fields.userId, userId)
},
})
if (session !== undefined) {
return session;
}
- let newSession = await db.insert(chatSession).values({userId: userId}).returning().execute().then((r) => r.at(0));
+ let newSession = await db.insert(chatSession).values({ userId: userId }).returning().execute().then((r) => r.at(0));
if (newSession == undefined) {
- throw new TRPCError({message: "failed to create session", code:"INTERNAL_SERVER_ERROR"});
+ throw new TRPCError({ message: "failed to create session", code: "INTERNAL_SERVER_ERROR" });
}
session = await db.query.chatSession.findFirst({
with: {
messages: true
},
where(fields, operators) {
- return operators.eq(fields.id,newSession.id)
+ return operators.eq(fields.id, newSession.id)
},
})
if (session == undefined) {
- throw new TRPCError({message: "session not found", code:"NOT_FOUND"});
+ throw new TRPCError({ message: "session not found", code: "NOT_FOUND" });
}
if (session !== undefined) {
return session;
}
}),
+ getMessages: publicProcedure.input(z.string()).query(async ({input}) => {
+ let res = await db.query.chatMessage.findMany({
+ where(fields,operators) {
+ return operators.eq(fields.sessionId,input)
+ }
+ })
+ return res;
+ }),
+ clearChat: publicProcedure.mutation(async () => {
+ console.log("deleting session")
+ const { userId } = await auth();
+ if (userId == null) {
+ throw new TRPCError({ message: "chat is only available to signed in users", code: 'UNAUTHORIZED' });
+ }
+ let session = await db.query.chatSession.findFirst({
+ with: {
+ messages: true
+ },
+ where(fields, operators) {
+ return operators.eq(fields.userId, userId)
+ },
+ })
+ if (session != undefined) {
+ db.delete(chatMessage).where(eq(chatMessage.sessionId,session.id)).execute()
+ }
+
+ }),
getSystemPrompt: publicProcedure.query(async () => {
const row = await db.select().from(systemSettings).limit(1).then((r) => r[0])
return row?.systemPropmt ?? ''