100 lines
3.2 KiB
TypeScript
100 lines
3.2 KiB
TypeScript
import { auth } from '@clerk/nextjs/server'
|
|
import { createOpenAI } from '@ai-sdk/openai'
|
|
import { streamText, tool, convertToModelMessages, stepCountIs, type UIMessage } from 'ai'
|
|
import { success, 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 { servTrpc } from '~/app/_trpc/ServerClient'
|
|
import { scheduleMeeting } from '~/app/actions/scheduleMeeting'
|
|
import currentTime from '~/app/actions/currentTime';
|
|
|
|
const openai = createOpenAI({ apiKey: env.OPENAI_API_KEY })
|
|
|
|
export async function POST(req: Request) {
|
|
const { userId } = await auth()
|
|
if (userId == null) 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 })
|
|
|
|
const systemPrompt = await servTrpc.chat.getSystemPrompt() || 'You are an AI recruiter assistant.'
|
|
|
|
// 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-5-mini'),
|
|
system: systemPrompt,
|
|
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, make something up if not provided'),
|
|
description: z.string().describe('Meeting description / agenda, make something up if not provided'),
|
|
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, if none provided ask if 20 minutes is ok'),
|
|
attendeeEmail: z
|
|
.string()
|
|
.email()
|
|
.optional()
|
|
.describe('Optional Email of the visitor to invite (if provided)'),
|
|
attendeeName: z.string().optional().describe('Name of the visitor'),
|
|
}),
|
|
execute: async (input) => scheduleMeeting({ ...input }),
|
|
}),
|
|
getCurrentUnixTime: tool({
|
|
description: 'Get the current unix time to reference for meeting dates',
|
|
inputSchema: z.object({
|
|
none: z.string().optional().describe("no inputs are needed")
|
|
}),
|
|
execute: async () => currentTime()
|
|
})
|
|
},
|
|
stopWhen: stepCountIs(5),
|
|
onFinish: async ({ text, finishReason }) => {
|
|
if (text && finishReason === 'stop') {
|
|
await db.insert(chatMessage).values({
|
|
sessionId,
|
|
role: 'assistant',
|
|
content: text,
|
|
})
|
|
}
|
|
},
|
|
})
|
|
|
|
return result.toUIMessageStreamResponse()
|
|
}
|