ai modal
This commit is contained in:
97
src/app/api/chat/route.ts
Normal file
97
src/app/api/chat/route.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
import { auth } from '@clerk/nextjs/server'
|
||||
import { createOpenAI } from '@ai-sdk/openai'
|
||||
import { streamText, tool, convertToModelMessages, stepCountIs, type UIMessage } from 'ai'
|
||||
import { z } from 'zod'
|
||||
import { eq, and } from 'drizzle-orm'
|
||||
import { env } from '~/env'
|
||||
import { db } from '~/server/db'
|
||||
import { chatSession, chatMessage } from '~/server/dbschema/schema'
|
||||
import { scheduleMeeting } from '~/app/actions/scheduleMeeting'
|
||||
|
||||
const openai = createOpenAI({ apiKey: env.OPENAI_API_KEY })
|
||||
|
||||
export async function POST(req: Request) {
|
||||
const { userId } = await auth()
|
||||
if (!userId) return new Response('Unauthorized', { status: 401 })
|
||||
|
||||
const { messages, sessionId } = (await req.json()) as {
|
||||
messages: UIMessage[]
|
||||
sessionId: string
|
||||
}
|
||||
|
||||
// Verify this session belongs to the authenticated user
|
||||
const session = await db
|
||||
.select()
|
||||
.from(chatSession)
|
||||
.where(and(eq(chatSession.id, sessionId), eq(chatSession.userId, userId)))
|
||||
.limit(1)
|
||||
.then((r) => r[0])
|
||||
|
||||
if (!session) return new Response('Session not found', { status: 404 })
|
||||
|
||||
// Save the latest user message
|
||||
const lastMessage = messages[messages.length - 1]
|
||||
if (lastMessage?.role === 'user') {
|
||||
const content = lastMessage.parts
|
||||
.filter((p): p is { type: 'text'; text: string } => p.type === 'text')
|
||||
.map((p) => p.text)
|
||||
.join('')
|
||||
if (content) {
|
||||
await db.insert(chatMessage).values({ sessionId, role: 'user', content })
|
||||
}
|
||||
}
|
||||
|
||||
const result = streamText({
|
||||
model: openai('gpt-4o'),
|
||||
system: `You are an AI recruiter assistant on Gregor Lohaus's personal portfolio website.
|
||||
Your role is to help visitors learn about Gregor's background, skills, and experience, and to schedule meetings.
|
||||
|
||||
About Gregor:
|
||||
- Fullstack developer specialising in TypeScript, React, Next.js, and modern web technologies
|
||||
- Experienced with Drizzle ORM, tRPC, PostgreSQL, server components, and the T3 stack
|
||||
- Also experienced with React Native, Expo, Java/Spring, gRPC, AWS, and Linux/Debian server administration
|
||||
- Open to new opportunities and collaboration
|
||||
|
||||
Be professional, friendly, and concise. When a visitor wants to schedule a meeting, collect the necessary details (preferred date/time, duration, and optionally their name/email) and use the scheduleMeeting tool.`,
|
||||
messages: await convertToModelMessages(messages),
|
||||
tools: {
|
||||
scheduleMeeting: tool({
|
||||
description: 'Schedule a meeting with Gregor Lohaus and add it to his Google Calendar',
|
||||
inputSchema: z.object({
|
||||
title: z.string().describe('Meeting title'),
|
||||
description: z.string().describe('Meeting description / agenda'),
|
||||
dateTime: z
|
||||
.string()
|
||||
.describe(
|
||||
'ISO 8601 datetime for the meeting start, e.g. 2025-04-01T10:00:00',
|
||||
),
|
||||
durationMinutes: z
|
||||
.number()
|
||||
.int()
|
||||
.min(15)
|
||||
.max(120)
|
||||
.describe('Duration of the meeting in minutes'),
|
||||
attendeeEmail: z
|
||||
.string()
|
||||
.email()
|
||||
.optional()
|
||||
.describe('Email of the visitor to invite (if provided)'),
|
||||
attendeeName: z.string().optional().describe('Name of the visitor'),
|
||||
}),
|
||||
execute: async (input) => scheduleMeeting({ ...input, userId }),
|
||||
}),
|
||||
},
|
||||
stopWhen: stepCountIs(5),
|
||||
onFinish: async ({ text, finishReason }) => {
|
||||
if (text && finishReason === 'stop') {
|
||||
await db.insert(chatMessage).values({
|
||||
sessionId,
|
||||
role: 'assistant',
|
||||
content: text,
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
return result.toUIMessageStreamResponse()
|
||||
}
|
||||
Reference in New Issue
Block a user