UNPKG

create-automaticgpt-template

Version:

AutomaticGPT - A production-ready Expo template with AI chat, authentication, conversation management, analytics, and sharing features

198 lines (178 loc) 6.41 kB
import { openai } from '@ai-sdk/openai'; import { streamText, tool } from 'ai'; import { z } from 'zod'; import { ChatRequestSchema, WeatherToolSchema, CelsiusConvertToolSchema, WeatherResultSchema, CelsiusConvertResultSchema, } from '@/types/api'; import { db } from '@/lib/supabase'; import { randomUUID } from 'crypto'; export async function POST(req: Request) { try { const requestBody = await req.json(); const validatedRequest = ChatRequestSchema.parse(requestBody); const { messages, conversationId, userId, saveMessages = true } = validatedRequest; console.log('post messages:', messages); console.log('conversation ID:', conversationId); console.log('user ID:', userId); // Save user message to database if persistence is enabled let userMessageId: string | null = null; if (saveMessages && conversationId && userId && messages.length > 0) { const lastMessage = messages[messages.length - 1]; if (lastMessage.role === 'user') { try { const { data: messageData } = await db.createMessage({ conversation_id: conversationId, content: lastMessage.content, role: lastMessage.role, metadata: { timestamp: new Date().toISOString(), client_id: lastMessage.id || randomUUID(), }, }); userMessageId = messageData?.id || null; console.log('Saved user message:', userMessageId); } catch (error) { console.error('Failed to save user message:', error); } } } // Track timing for assistant response const startTime = Date.now(); const result = streamText({ model: openai('gpt-4o'), messages: messages.map((msg) => ({ role: msg.role, content: msg.content || '', ...(msg.id && { id: msg.id }), })), tools: { // https://ai-sdk.dev/docs/getting-started/expo#enhance-your-chatbot-with-tools weather: tool({ description: 'Get the weather in a location (fahrenheit)', parameters: WeatherToolSchema, async execute({ location }) { const temperature = Math.round(Math.random() * (90 - 32) + 32); const result = { location, temperature, }; // Validate result against schema return WeatherResultSchema.parse(result); }, }), convertFahrenheitToCelsius: tool({ description: 'Convert a temperature in fahrenheit to celsius', parameters: CelsiusConvertToolSchema, async execute({ temperature }) { const celsius = Math.round((temperature - 32) * (5 / 9)); const result = { temperature, celsius, }; // Validate result against schema return CelsiusConvertResultSchema.parse(result); }, }), }, onFinish: async (completion) => { console.log('Stream finished'); // Save assistant response to database if (saveMessages && conversationId && completion.text.trim()) { const endTime = Date.now(); const responseTime = endTime - startTime; try { const { data: assistantMessage } = await db.createMessage({ conversation_id: conversationId, content: completion.text.trim(), role: 'assistant', metadata: { timestamp: new Date().toISOString(), response_time_ms: responseTime, user_message_id: userMessageId, finish_reason: completion.finishReason, usage: completion.usage, }, model_used: 'gpt-4o', tokens_used: completion.usage?.totalTokens || null, response_time_ms: responseTime, tool_calls: completion.toolCalls || [], tool_results: completion.toolResults || [], }); console.log('Saved assistant message:', assistantMessage?.id); // Auto-generate conversation title if this is the first user message if (messages.length <= 2) { // user message + assistant response try { const generatedTitle = await db.generateConversationTitle(conversationId); if (generatedTitle.data) { await db.updateConversation(conversationId, { title: generatedTitle.data }); console.log('Updated conversation title:', generatedTitle.data); } } catch (titleError) { console.error('Failed to generate conversation title:', titleError); } } } catch (error) { console.error('Failed to save assistant message:', error); } } }, }); return result.toDataStreamResponse({ getErrorMessage: __DEV__ ? errorHandler : undefined, headers: { // Issue with iOS NSURLSession that requires Content-Type set in order to enable streaming. // https://github.com/expo/expo/issues/32950#issuecomment-2508297646 'Content-Type': 'application/octet-stream', 'Content-Encoding': 'none', }, }); } catch (error) { console.error('Chat API error:', error); if (error instanceof z.ZodError) { return new Response( JSON.stringify({ error: { message: 'Invalid request format', type: 'validation_error', details: error.errors, }, }), { status: 400, headers: { 'Content-Type': 'application/json' }, } ); } return new Response( JSON.stringify({ error: { message: 'Internal server error', type: 'server_error', }, }), { status: 500, headers: { 'Content-Type': 'application/json' }, } ); } } // Prevent cryptic errors in development. // https://ai-sdk.dev/docs/troubleshooting/use-chat-an-error-occurred function errorHandler(error: unknown): string { if (error == null) { return 'unknown error'; } if (typeof error === 'string') { return error; } if (error instanceof Error) { return error.message; } return JSON.stringify(error); }