import { generateId, type UIMessage } from "ai"; import { logger } from "../src/utils/logger"; /** * Example script to test the /api/chat endpoint * This demonstrates how to use the chat API with streaming responses */ const API_BASE_URL = "http://localhost:3031"; async function testChatAPI() { try { logger.info("๐Ÿงช Testing Chat API endpoint..."); // Create a new chat ID const chatId = generateId(); logger.info(`Using chat ID: ${chatId}`); // Create user message in UIMessage format const userMessage: UIMessage = { id: generateId(), role: "user", parts: [ { type: "text", text: "Hello! Can you help me understand how TypeScript generics work? Please provide a simple example.", }, ], metadata: { source: "example", priority: "normal", }, }; const payload = { chatId, messages: [userMessage], model: "openai:::gpt-4o-mini", mode: "ask", }; const response = await fetch(`${API_BASE_URL}/api/chat`, { method: "POST", headers: { "Content-Type": "application/json", Accept: "text/plain", // For streaming response }, body: JSON.stringify(payload), }); if (!response.ok) { const errorText = await response.text(); throw new Error( `HTTP error! status: ${response.status}, body: ${errorText}` ); } logger.info("โœ… API call successful, processing streaming response..."); // Handle streaming response const reader = response.body?.getReader(); if (!reader) { throw new Error("No response body reader available"); } let fullResponse = ""; logger.info("\n๐Ÿ“ Streaming response:"); logger.info("โ•".repeat(60)); try { while (true) { const { done, value } = await reader.read(); if (done) break; const chunk = new TextDecoder().decode(value); fullResponse += chunk; process.stdout.write(chunk); // Stream output in real-time } } finally { reader.releaseLock(); } logger.info("\nโ•".repeat(60)); logger.info("๐ŸŽ‰ Chat response completed successfully!"); return { chatId, fullResponse }; } catch (error) { logger.error("โŒ Chat API test failed:", error); throw error; } } async function testContinuedConversation() { try { logger.info("\n๐Ÿงช Testing continued conversation..."); // Use the same chat ID for continuation const chatId = generateId(); // Initial conversation const initialMessages: UIMessage[] = [ { id: generateId(), role: "user", parts: [{ type: "text", text: "What is React?" }], }, { id: generateId(), role: "assistant", parts: [ { type: "text", text: "React is a JavaScript library for building user interfaces, developed by Facebook. It allows developers to create reusable UI components and manage application state efficiently.", }, ], }, ]; // Add follow-up question const followUpMessage: UIMessage = { id: generateId(), role: "user", parts: [ { type: "text", text: "Can you show me a simple React component example?", }, ], }; const allMessages = [...initialMessages, followUpMessage]; const payload = { chatId, messages: allMessages, model: "openai:::gpt-4o-mini", mode: "ask", }; const response = await fetch(`${API_BASE_URL}/api/chat`, { method: "POST", headers: { "Content-Type": "application/json", Accept: "text/plain", }, body: JSON.stringify(payload), }); if (!response.ok) { const errorText = await response.text(); throw new Error( `HTTP error! status: ${response.status}, body: ${errorText}` ); } logger.info("โœ… Continued conversation API call successful"); logger.info("\n๐Ÿ“ Follow-up response:"); logger.info("โ•".repeat(60)); const reader = response.body?.getReader(); if (!reader) { throw new Error("No response body reader available"); } try { while (true) { const { done, value } = await reader.read(); if (done) break; const chunk = new TextDecoder().decode(value); process.stdout.write(chunk); } } finally { reader.releaseLock(); } logger.info("\nโ•".repeat(60)); logger.info("๐ŸŽ‰ Continued conversation completed!"); } catch (error) { logger.error("โŒ Continued conversation test failed:", error); throw error; } } async function testWriteMode() { try { logger.info("\n๐Ÿงช Testing write mode..."); const chatId = generateId(); const userMessage: UIMessage = { id: generateId(), role: "user", parts: [ { type: "text", text: "Create a simple TypeScript function that calculates the factorial of a number. Save it to a file called factorial.ts", }, ], }; const payload = { chatId, messages: [userMessage], model: "openai:::gpt-4o-mini", mode: "write", // Using write mode for file operations }; const response = await fetch(`${API_BASE_URL}/api/chat`, { method: "POST", headers: { "Content-Type": "application/json", Accept: "text/plain", }, body: JSON.stringify(payload), }); if (!response.ok) { const errorText = await response.text(); throw new Error( `HTTP error! status: ${response.status}, body: ${errorText}` ); } logger.info("โœ… Write mode API call successful"); logger.info("\n๐Ÿ“ Write mode response:"); logger.info("โ•".repeat(60)); const reader = response.body?.getReader(); if (!reader) { throw new Error("No response body reader available"); } try { while (true) { const { done, value } = await reader.read(); if (done) break; const chunk = new TextDecoder().decode(value); process.stdout.write(chunk); } } finally { reader.releaseLock(); } logger.info("\nโ•".repeat(60)); logger.info("๐ŸŽ‰ Write mode completed! Check for created files."); } catch (error) { logger.error("โŒ Write mode test failed:", error); throw error; } } async function testLoadChatMessages() { try { logger.info("\n๐Ÿงช Testing load chat messages (GET)..."); const chatId = "test-chat-id"; // Use a known chat ID or one from previous tests const response = await fetch(`${API_BASE_URL}/api/chat?chatId=${chatId}`, { method: "GET", headers: { Accept: "application/json", }, }); if (!response.ok) { logger.info( `โ„น๏ธ Expected failure for non-existent chat: ${response.status}` ); return; } const result = await response.json(); logger.info("โœ… Load messages successful"); logger.info(`๐Ÿ“‹ Chat ID: ${result.chatId}`); logger.info(`๐Ÿ“‹ Messages count: ${result.messages?.length || 0}`); if (result.messages && result.messages.length > 0) { logger.info("\n๐Ÿ“ First message:"); logger.info(JSON.stringify(result.messages[0], null, 2)); } } catch (error) { logger.error("โŒ Load messages test failed:", error); // Don't throw here as this test might fail if no chat exists } } async function testCORSHeaders() { try { logger.info("\n๐Ÿงช Testing CORS preflight request..."); const response = await fetch(`${API_BASE_URL}/api/chat`, { method: "OPTIONS", }); if (!response.ok) { throw new Error(`CORS preflight failed! status: ${response.status}`); } const corsHeaders = { "Access-Control-Allow-Origin": response.headers.get( "Access-Control-Allow-Origin" ), "Access-Control-Allow-Methods": response.headers.get( "Access-Control-Allow-Methods" ), "Access-Control-Allow-Headers": response.headers.get( "Access-Control-Allow-Headers" ), }; logger.info("โœ… CORS headers:", corsHeaders); } catch (error) { logger.error("โŒ CORS test failed:", error); throw error; } } async function testErrorHandling() { try { logger.info("\n๐Ÿงช Testing error handling..."); // Test missing required fields const invalidPayload = { chatId: "test", // Missing messages, model, mode }; const response = await fetch(`${API_BASE_URL}/api/chat`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(invalidPayload), }); if (response.ok) { throw new Error("Expected error response for invalid payload"); } const errorResult = await response.json(); logger.info("โœ… Error handling works correctly"); logger.info(`๐Ÿ“‹ Error message: ${errorResult.error}`); logger.info(`๐Ÿ“‹ Status code: ${response.status}`); } catch (error) { logger.error("โŒ Error handling test failed:", error); throw error; } } async function main() { logger.info("๐Ÿš€ Starting Chat API tests..."); logger.info(`๐Ÿ“ก API Base URL: ${API_BASE_URL}`); try { await testCORSHeaders(); await testErrorHandling(); await testChatAPI(); await testContinuedConversation(); await testWriteMode(); await testLoadChatMessages(); logger.info("\n๐ŸŽ‰ All Chat API tests completed successfully!"); } catch (error) { logger.error("\n๐Ÿ’ฅ Chat API test suite failed:", error); process.exit(1); } } // Helper function to create a simple UIMessage export function createUserMessage( text: string, metadata?: Record ): UIMessage { return { id: generateId(), role: "user", parts: [{ type: "text", text }], ...(metadata && { metadata }), }; } // Helper function to create assistant message (for conversation continuation) export function createAssistantMessage( text: string, metadata?: Record ): UIMessage { return { id: generateId(), role: "assistant", parts: [{ type: "text", text }], ...(metadata && { metadata }), }; } if (import.meta.main) { main(); } export { testChatAPI, testContinuedConversation, testWriteMode, testLoadChatMessages, testCORSHeaders, testErrorHandling, };