Tutorial

Building Your First AI Chatbot with Next.js and OpenAI

Learn how to create an intelligent chatbot using the latest AI technologies and modern web development practices.

Sarah Cheung
15 min read
1/15/2024

In today's digital landscape, AI chatbots have become essential tools for businesses and developers alike. They provide instant customer support, engage users with personalized experiences, and automate routine tasks. With the power of OpenAI's GPT models and the modern React framework Next.js, building a sophisticated chatbot has never been more accessible. This comprehensive tutorial will guide you through creating your first AI chatbot from scratch. We'll cover everything from setting up your development environment to deploying a production-ready chatbot that can handle real conversations. Whether you're a beginner looking to explore AI development or an experienced developer wanting to add conversational AI to your toolkit, this guide provides practical, hands-on learning. By the end of this tutorial, you'll have a fully functional chatbot with a beautiful user interface, proper error handling, and the ability to maintain context across conversations. Let's dive in and build something amazing together.

1Understanding AI Chatbots and OpenAI

Before we start coding, let's understand what makes AI chatbots powerful and how OpenAI's technology enables intelligent conversations.

What is an AI Chatbot? An AI chatbot is a computer program that uses artificial intelligence to simulate human-like conversations. Unlike traditional rule-based chatbots that follow predetermined scripts, AI chatbots can understand context, generate creative responses, and learn from interactions.

OpenAI's GPT Models OpenAI's Generative Pre-trained Transformer (GPT) models are large language models trained on vast amounts of text data. They excel at understanding natural language and generating human-like responses. The latest models like GPT-4 and GPT-3.5-turbo offer:

- Advanced natural language understanding - Context awareness across long conversations - Ability to follow instructions and maintain personality - Support for multiple languages - Integration with function calling for enhanced capabilities

Why Next.js for Chatbot Development? Next.js provides several advantages for building chatbots:

- Server-side rendering for better SEO and performance - API routes for secure backend functionality - Built-in optimization for images, fonts, and scripts - TypeScript support for better code quality - Easy deployment with Vercel and other platforms - React ecosystem with rich component libraries

Architecture Overview Our chatbot will follow a modern three-tier architecture: 1. Frontend: React components for the chat interface 2. Backend: Next.js API routes for OpenAI integration 3. Storage: Session management and conversation history

Code Example

// Basic chatbot message interface
interface ChatMessage {
  id: string;
  role: 'user' | 'assistant' | 'system';
  content: string;
  timestamp: Date;
  metadata?: {
    tokens?: number;
    model?: string;
    latency?: number;
  };
}

// Chatbot configuration
interface ChatbotConfig {
  model: 'gpt-4' | 'gpt-3.5-turbo';
  maxTokens: number;
  temperature: number;
  systemPrompt: string;
  conversationHistory: ChatMessage[];
}

// Example system prompt for a helpful assistant
const SYSTEM_PROMPT = `You are a helpful AI assistant created to provide accurate, friendly, and informative responses. You should:
- Be conversational and engaging
- Provide clear and detailed explanations
- Ask clarifying questions when needed
- Admit when you don't know something
- Be respectful and professional at all times`;
            

2Setting Up Your Development Environment

Let's set up a robust development environment for building our AI chatbot with all the necessary tools and dependencies.

Prerequisites Before we begin, ensure you have: - Node.js 18.0 or later installed - A code editor (VS Code recommended) - Git for version control - An OpenAI API account and API key

Project Initialization We'll create a new Next.js project with TypeScript and Tailwind CSS for a modern development experience:

Environment Configuration Proper environment management is crucial for API key security and different deployment stages. We'll set up environment variables for development, staging, and production environments.

Development Tools Setup To ensure code quality and consistency, we'll configure: - ESLint for code linting - Prettier for code formatting - Husky for Git hooks - TypeScript for type safety - Tailwind CSS for styling

Project Structure Our project will follow Next.js 13+ app directory structure: - `app/` - App router and pages - `components/` - Reusable React components - `lib/` - Utility functions and configurations - `types/` - TypeScript type definitions - `styles/` - Global styles and Tailwind config

Code Example

// Create new Next.js project
npx create-next-app@latest ai-chatbot --typescript --tailwind --eslint --app

// Navigate to project directory
cd ai-chatbot

// Install additional dependencies
npm install openai lucide-react @radix-ui/react-dialog @radix-ui/react-toast

// Install development dependencies
npm install -D @types/node @types/react @types/react-dom

// Create environment variables file
// .env.local
OPENAI_API_KEY=your_openai_api_key_here
NEXT_PUBLIC_APP_NAME=AI Chatbot
NEXT_PUBLIC_APP_VERSION=1.0.0

// .env.example (for version control)
OPENAI_API_KEY=your_api_key_here
NEXT_PUBLIC_APP_NAME=AI Chatbot
NEXT_PUBLIC_APP_VERSION=1.0.0

// package.json scripts
{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint",
    "lint:fix": "next lint --fix",
    "format": "prettier --write .",
    "type-check": "tsc --noEmit"
  }
}

// tsconfig.json paths configuration
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./*"],
      "@/components/*": ["components/*"],
      "@/lib/*": ["lib/*"],
      "@/types/*": ["types/*"],
      "@/styles/*": ["styles/*"]
    }
  }
}
            

3Building the Chat Interface Components

Now let's create beautiful and functional React components for our chatbot interface. We'll build a modern chat UI with proper styling, animations, and user experience considerations.

Component Architecture Our chat interface will consist of several key components: - `ChatContainer` - Main wrapper component - `MessageList` - Displays conversation history - `MessageBubble` - Individual message display - `ChatInput` - User input field and send button - `TypingIndicator` - Shows when AI is thinking - `ChatHeader` - Title and controls

Design Principles - Mobile-first responsive design - Accessibility compliance with proper ARIA labels - Smooth animations for better user experience - Clear visual hierarchy between user and AI messages - Loading states to indicate processing - Error handling with user-friendly messages

Key Features - Auto-scrolling to latest messages - Message timestamps - Typing indicators - Copy message functionality - Message regeneration - Conversation clearing - Responsive layout for all screen sizes

Styling Approach We'll use Tailwind CSS for utility-first styling, creating a modern and clean interface that works across all devices and screen sizes.

Code Example

// components/chat/ChatContainer.tsx
'use client';
import React, { useState, useRef, useEffect } from 'react';
import { MessageList } from './MessageList';
import { ChatInput } from './ChatInput';
import { ChatHeader } from './ChatHeader';
import { ChatMessage } from '@/types/chat';

interface ChatContainerProps {
  className?: string;
}

export function ChatContainer({ className = '' }: ChatContainerProps) {
  const [messages, setMessages] = useState<ChatMessage[]>([]);
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const messagesEndRef = useRef<HTMLDivElement>(null);

  // Auto-scroll to bottom when new messages arrive
  useEffect(() => {
    messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
  }, [messages]);

  const handleSendMessage = async (content: string) => {
    if (!content.trim() || isLoading) return;

    const userMessage: ChatMessage = {
      id: Date.now().toString(),
      role: 'user',
      content: content.trim(),
      timestamp: new Date(),
    };

    setMessages(prev => [...prev, userMessage]);
    setIsLoading(true);
    setError(null);

    try {
      const response = await fetch('/api/chat', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          messages: [...messages, userMessage],
        }),
      });

      if (!response.ok) {
        throw new Error('Failed to get response');
      }

      const data = await response.json();
      
      const assistantMessage: ChatMessage = {
        id: (Date.now() + 1).toString(),
        role: 'assistant',
        content: data.message,
        timestamp: new Date(),
        metadata: data.metadata,
      };

      setMessages(prev => [...prev, assistantMessage]);
    } catch (err) {
      setError('Failed to send message. Please try again.');
      console.error('Chat error:', err);
    } finally {
      setIsLoading(false);
    }
  };

  const handleClearChat = () => {
    setMessages([]);
    setError(null);
  };

  return (
    <div className={`flex flex-col h-full bg-white rounded-lg shadow-lg ${className}`}>
      <ChatHeader onClearChat={handleClearChat} messageCount={messages.length} />
      
      <div className="flex-1 overflow-hidden">
        <MessageList 
          messages={messages} 
          isLoading={isLoading}
          error={error}
        />
        <div ref={messagesEndRef} />
      </div>

      <ChatInput 
        onSendMessage={handleSendMessage}
        isLoading={isLoading}
        disabled={!!error}
      />
    </div>
  );
}

// components/chat/MessageBubble.tsx
import React from 'react';
import { ChatMessage } from '@/types/chat';
import { User, Bot, Copy, RefreshCw } from 'lucide-react';
import { Button } from '@/components/ui/button';

interface MessageBubbleProps {
  message: ChatMessage;
  onCopy?: (content: string) => void;
  onRegenerate?: (messageId: string) => void;
}

export function MessageBubble({ message, onCopy, onRegenerate }: MessageBubbleProps) {
  const isUser = message.role === 'user';
  
  const handleCopy = () => {
    navigator.clipboard.writeText(message.content);
    onCopy?.(message.content);
  };

  return (
    <div className={`flex ${isUser ? 'justify-end' : 'justify-start'} mb-4 group`}>
      <div className={`max-w-[80%] ${isUser ? 'order-2' : 'order-1'}`}>
        <div className={`flex items-start gap-3 ${isUser ? 'flex-row-reverse' : 'flex-row'}`}>
          {/* Avatar */}
          <div className={`flex-shrink-0 w-8 h-8 rounded-full flex items-center justify-center ${
            isUser 
              ? 'bg-blue-500 text-white' 
              : 'bg-gray-100 text-gray-600 border-2 border-gray-200'
          }`}>
            {isUser ? <User size={16} /> : <Bot size={16} />}
          </div>

          {/* Message Content */}
          <div className={`relative ${isUser ? 'text-right' : 'text-left'}`}>
            <div className={`inline-block px-4 py-2 rounded-2xl ${
              isUser
                ? 'bg-blue-500 text-white rounded-br-md'
                : 'bg-gray-100 text-gray-800 rounded-bl-md border border-gray-200'
            }`}>
              <p className="text-sm leading-relaxed whitespace-pre-wrap">
                {message.content}
              </p>
            </div>

            {/* Timestamp and Actions */}
            <div className={`flex items-center gap-2 mt-1 text-xs text-gray-500 ${
              isUser ? 'justify-end' : 'justify-start'
            }`}>
              <span>{message.timestamp.toLocaleTimeString()}</span>
              
              {/* Action buttons (shown on hover) */}
              <div className="opacity-0 group-hover:opacity-100 transition-opacity flex gap-1">
                <Button
                  variant="ghost"
                  size="sm"
                  onClick={handleCopy}
                  className="h-6 w-6 p-0 hover:bg-gray-200"
                  title="Copy message"
                >
                  <Copy size={12} />
                </Button>
                
                {!isUser && onRegenerate && (
                  <Button
                    variant="ghost"
                    size="sm"
                    onClick={() => onRegenerate(message.id)}
                    className="h-6 w-6 p-0 hover:bg-gray-200"
                    title="Regenerate response"
                  >
                    <RefreshCw size={12} />
                  </Button>
                )}
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}
            

4Implementing OpenAI API Integration

Now let's implement the backend logic to connect our chatbot with OpenAI's powerful language models. We'll create a robust API route that handles authentication, rate limiting, error handling, and conversation management.

API Route Structure Our Next.js API route will handle: - Request validation and sanitization - OpenAI API communication - Context management for conversations - Error handling and logging - Response streaming for better UX - Usage tracking and rate limiting

Key Implementation Features - Streaming responses for real-time chat experience - Context preservation across conversation turns - Token counting and usage optimization - Error recovery with fallback strategies - Input sanitization for security - Response caching for common queries

OpenAI Best Practices - Proper system prompt engineering - Temperature and parameter tuning - Token limit management - Content filtering and safety - Cost optimization strategies - Performance monitoring

Security Considerations - API key protection - Input validation and sanitization - Rate limiting per user/IP - Content filtering - Audit logging - CORS configuration

Code Example

// app/api/chat/route.ts
import { NextRequest, NextResponse } from 'next/server';
import OpenAI from 'openai';
import { ChatMessage } from '@/types/chat';

// Initialize OpenAI client
const openai = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY,
});

// System prompt for the chatbot
const SYSTEM_PROMPT = `You are a helpful AI assistant built with Next.js and OpenAI. You should:
- Provide accurate, helpful, and friendly responses
- Be conversational and engaging
- Ask clarifying questions when needed
- Admit when you don't know something
- Keep responses concise but informative
- Maintain context throughout the conversation`;

export async function POST(request: NextRequest) {
  try {
    // Parse and validate request
    const body = await request.json();
    const { messages } = body;

    if (!messages || !Array.isArray(messages)) {
      return NextResponse.json(
        { error: 'Invalid messages format' },
        { status: 400 }
      );
    }

    // Validate message format
    const validMessages = messages.filter((msg: ChatMessage) => 
      msg.content && 
      msg.content.trim().length > 0 && 
      ['user', 'assistant', 'system'].includes(msg.role)
    );

    if (validMessages.length === 0) {
      return NextResponse.json(
        { error: 'No valid messages provided' },
        { status: 400 }
      );
    }

    // Prepare messages for OpenAI
    const openaiMessages = [
      { role: 'system' as const, content: SYSTEM_PROMPT },
      ...validMessages.map((msg: ChatMessage) => ({
        role: msg.role as 'user' | 'assistant',
        content: msg.content,
      })),
    ];

    // Call OpenAI API
    const startTime = Date.now();
    const completion = await openai.chat.completions.create({
      model: 'gpt-3.5-turbo',
      messages: openaiMessages,
      max_tokens: 1000,
      temperature: 0.7,
      presence_penalty: 0.1,
      frequency_penalty: 0.1,
    });

    const endTime = Date.now();
    const latency = endTime - startTime;

    // Extract response
    const assistantMessage = completion.choices[0]?.message?.content;

    if (!assistantMessage) {
      throw new Error('No response from OpenAI');
    }

    // Return response with metadata
    return NextResponse.json({
      message: assistantMessage,
      metadata: {
        model: completion.model,
        tokens: completion.usage?.total_tokens || 0,
        latency,
        timestamp: new Date().toISOString(),
      },
    });

  } catch (error) {
    console.error('Chat API error:', error);

    // Handle different types of errors
    if (error instanceof Error) {
      if (error.message.includes('rate limit')) {
        return NextResponse.json(
          { error: 'Rate limit exceeded. Please try again later.' },
          { status: 429 }
        );
      }

      if (error.message.includes('insufficient_quota')) {
        return NextResponse.json(
          { error: 'API quota exceeded. Please contact support.' },
          { status: 503 }
        );
      }
    }

    return NextResponse.json(
      { error: 'Failed to process your message. Please try again.' },
      { status: 500 }
    );
  }
}

// lib/openai-config.ts
export const OPENAI_CONFIG = {
  models: {
    'gpt-4': {
      maxTokens: 8192,
      costPer1KTokens: 0.03,
      description: 'Most capable model, best for complex tasks',
    },
    'gpt-3.5-turbo': {
      maxTokens: 4096,
      costPer1KTokens: 0.002,
      description: 'Fast and efficient for most conversations',
    },
  },
  defaultParams: {
    temperature: 0.7,
    max_tokens: 1000,
    presence_penalty: 0.1,
    frequency_penalty: 0.1,
  },
  systemPrompts: {
    helpful: `You are a helpful AI assistant...`,
    creative: `You are a creative AI assistant...`,
    analytical: `You are an analytical AI assistant...`,
  },
} as const;

// lib/token-counter.ts
export function estimateTokenCount(text: string): number {
  // Rough estimation: 1 token ā‰ˆ 4 characters for English text
  return Math.ceil(text.length / 4);
}

export function validateTokenLimit(messages: ChatMessage[], maxTokens: number): boolean {
  const totalTokens = messages.reduce((sum, msg) => 
    sum + estimateTokenCount(msg.content), 0
  );
  return totalTokens <= maxTokens;
}

export function truncateConversation(messages: ChatMessage[], maxTokens: number): ChatMessage[] {
  let tokenCount = 0;
  const truncated: ChatMessage[] = [];
  
  // Keep recent messages within token limit
  for (let i = messages.length - 1; i >= 0; i--) {
    const msgTokens = estimateTokenCount(messages[i].content);
    if (tokenCount + msgTokens > maxTokens) break;
    
    tokenCount += msgTokens;
    truncated.unshift(messages[i]);
  }
  
  return truncated;
}
            

5Adding Advanced Features

Let's enhance our chatbot with advanced features that provide a superior user experience and demonstrate professional-level development skills.

Streaming Responses Implement real-time response streaming so users see the AI's response as it's being generated, similar to ChatGPT's interface.

Conversation Memory Add persistent conversation storage using browser localStorage or a database, allowing users to resume conversations across sessions.

Multiple AI Personalities Create different chatbot personalities (helpful, creative, analytical) that users can switch between for different use cases.

File Upload Support Enable users to upload documents or images for the AI to analyze and discuss.

Voice Integration Add speech-to-text input and text-to-speech output for hands-free interaction.

Advanced UI Features - Message reactions and ratings - Conversation search and filtering - Export conversations - Dark/light theme toggle - Responsive mobile interface - Keyboard shortcuts

Performance Optimizations - Response caching for common queries - Lazy loading for conversation history - Optimistic UI updates - Error retry mechanisms - Connection status indicators

Code Example

// hooks/useChat.ts - Custom hook for chat functionality
import { useState, useCallback, useEffect } from 'react';
import { ChatMessage, ChatConfig } from '@/types/chat';

export function useChat(config: ChatConfig = {}) {
  const [messages, setMessages] = useState<ChatMessage[]>([]);
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [isConnected, setIsConnected] = useState(true);

  // Load conversation from localStorage on mount
  useEffect(() => {
    const saved = localStorage.getItem('chatbot-conversation');
    if (saved) {
      try {
        const parsed = JSON.parse(saved);
        setMessages(parsed);
      } catch (err) {
        console.error('Failed to load conversation:', err);
      }
    }
  }, []);

  // Save conversation to localStorage when messages change
  useEffect(() => {
    if (messages.length > 0) {
      localStorage.setItem('chatbot-conversation', JSON.stringify(messages));
    }
  }, [messages]);

  const sendMessage = useCallback(async (content: string) => {
    if (!content.trim() || isLoading) return;

    const userMessage: ChatMessage = {
      id: crypto.randomUUID(),
      role: 'user',
      content: content.trim(),
      timestamp: new Date(),
    };

    setMessages(prev => [...prev, userMessage]);
    setIsLoading(true);
    setError(null);

    try {
      const response = await fetch('/api/chat/stream', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          messages: [...messages, userMessage],
          config,
        }),
      });

      if (!response.ok) {
        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
      }

      const reader = response.body?.getReader();
      const decoder = new TextDecoder();

      if (!reader) {
        throw new Error('No response stream available');
      }

      let assistantMessage: ChatMessage = {
        id: crypto.randomUUID(),
        role: 'assistant',
        content: '',
        timestamp: new Date(),
      };

      setMessages(prev => [...prev, assistantMessage]);

      // Read streaming response
      while (true) {
        const { done, value } = await reader.read();
        if (done) break;

        const chunk = decoder.decode(value);
        const lines = chunk.split('\n');

        for (const line of lines) {
          if (line.startsWith('data: ')) {
            const data = line.slice(6);
            if (data === '[DONE]') continue;

            try {
              const parsed = JSON.parse(data);
              const content = parsed.choices[0]?.delta?.content;
              
              if (content) {
                assistantMessage.content += content;
                setMessages(prev => 
                  prev.map(msg => 
                    msg.id === assistantMessage.id 
                      ? { ...msg, content: assistantMessage.content }
                      : msg
                  )
                );
              }
            } catch (err) {
              console.error('Failed to parse stream data:', err);
            }
          }
        }
      }

    } catch (err) {
      setError(err instanceof Error ? err.message : 'Unknown error occurred');
      console.error('Chat error:', err);
    } finally {
      setIsLoading(false);
    }
  }, [messages, isLoading, config]);

  const clearConversation = useCallback(() => {
    setMessages([]);
    setError(null);
    localStorage.removeItem('chatbot-conversation');
  }, []);

  const regenerateLastResponse = useCallback(async () => {
    if (messages.length < 2) return;
    
    // Remove last assistant message and regenerate
    const messagesWithoutLast = messages.slice(0, -1);
    const lastUserMessage = messagesWithoutLast[messagesWithoutLast.length - 1];
    
    if (lastUserMessage?.role === 'user') {
      setMessages(messagesWithoutLast);
      await sendMessage(lastUserMessage.content);
    }
  }, [messages, sendMessage]);

  return {
    messages,
    isLoading,
    error,
    isConnected,
    sendMessage,
    clearConversation,
    regenerateLastResponse,
  };
}

// components/chat/StreamingMessage.tsx
import React, { useEffect, useState } from 'react';

interface StreamingMessageProps {
  content: string;
  isComplete: boolean;
}

export function StreamingMessage({ content, isComplete }: StreamingMessageProps) {
  const [displayedContent, setDisplayedContent] = useState('');
  const [currentIndex, setCurrentIndex] = useState(0);

  useEffect(() => {
    if (isComplete) {
      setDisplayedContent(content);
      return;
    }

    // Simulate typing effect for streaming
    const timer = setInterval(() => {
      if (currentIndex < content.length) {
        setDisplayedContent(content.slice(0, currentIndex + 1));
        setCurrentIndex(prev => prev + 1);
      }
    }, 20);

    return () => clearInterval(timer);
  }, [content, isComplete, currentIndex]);

  return (
    <div className="relative">
      <p className="whitespace-pre-wrap">{displayedContent}</p>
      {!isComplete && (
        <span className="inline-block w-2 h-4 bg-blue-500 animate-pulse ml-1" />
      )}
    </div>
  );
}

// lib/personality-configs.ts
export const PERSONALITY_CONFIGS = {
  helpful: {
    name: 'Helpful Assistant',
    systemPrompt: `You are a helpful AI assistant focused on providing accurate, useful information and assistance. You should be friendly, professional, and always try to be as helpful as possible.`,
    temperature: 0.7,
    emoji: 'šŸ¤',
  },
  creative: {
    name: 'Creative Companion',
    systemPrompt: `You are a creative AI assistant who loves to think outside the box. You should be imaginative, inspiring, and help users explore creative solutions and ideas.`,
    temperature: 0.9,
    emoji: 'šŸŽØ',
  },
  analytical: {
    name: 'Analytical Expert',
    systemPrompt: `You are an analytical AI assistant focused on logical reasoning, data analysis, and systematic problem-solving. You should be precise, methodical, and thorough in your responses.`,
    temperature: 0.3,
    emoji: 'šŸ”',
  },
} as const;
            

6Testing, Deployment, and Best Practices

Now let's ensure our chatbot is production-ready with comprehensive testing, deployment strategies, and adherence to best practices.

Testing Strategy Implement a multi-layered testing approach: - Unit tests for individual components and utilities - Integration tests for API routes and OpenAI integration - End-to-end tests for complete user workflows - Performance tests for response times and scalability - Accessibility tests for inclusive design - Security tests for vulnerability assessment

Deployment Options - Vercel (recommended for Next.js projects) - Netlify for static deployment with serverless functions - AWS with Lambda and CloudFront - Google Cloud Platform with Cloud Run - Docker containers for consistent environments

Production Considerations - Environment variable management - API rate limiting and quotas - Error monitoring and logging - Performance monitoring - Cost tracking and optimization - Security hardening - GDPR and privacy compliance

Best Practices Checklist āœ… Secure API key management āœ… Input validation and sanitization āœ… Error handling and user feedback āœ… Responsive design for all devices āœ… Accessibility compliance (WCAG 2.1) āœ… Performance optimization āœ… SEO optimization āœ… Content security policy āœ… Rate limiting implementation āœ… Monitoring and analytics setup

Monitoring and Analytics Set up comprehensive monitoring to track: - API usage and costs - Response times and errors - User engagement metrics - Conversation quality metrics - System performance and uptime

Code Example

// __tests__/api/chat.test.ts
import { POST } from '@/app/api/chat/route';
import { NextRequest } from 'next/server';

// Mock OpenAI
jest.mock('openai', () => ({
  default: jest.fn().mockImplementation(() => ({
    chat: {
      completions: {
        create: jest.fn().mockResolvedValue({
          choices: [{ message: { content: 'Test response' } }],
          usage: { total_tokens: 50 },
          model: 'gpt-3.5-turbo',
        }),
      },
    },
  })),
}));

describe('/api/chat', () => {
  it('should return a successful response for valid input', async () => {
    const request = new NextRequest('http://localhost:3000/api/chat', {
      method: 'POST',
      body: JSON.stringify({
        messages: [
          { role: 'user', content: 'Hello, world!', id: '1', timestamp: new Date() }
        ],
      }),
    });

    const response = await POST(request);
    const data = await response.json();

    expect(response.status).toBe(200);
    expect(data.message).toBe('Test response');
    expect(data.metadata).toBeDefined();
  });

  it('should handle invalid input gracefully', async () => {
    const request = new NextRequest('http://localhost:3000/api/chat', {
      method: 'POST',
      body: JSON.stringify({ messages: 'invalid' }),
    });

    const response = await POST(request);
    expect(response.status).toBe(400);
  });
});

// __tests__/components/ChatContainer.test.tsx
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { ChatContainer } from '@/components/chat/ChatContainer';

// Mock fetch
global.fetch = jest.fn();

describe('ChatContainer', () => {
  beforeEach(() => {
    (fetch as jest.Mock).mockClear();
  });

  it('should render chat interface correctly', () => {
    render(<ChatContainer />);
    
    expect(screen.getByPlaceholderText(/type your message/i)).toBeInTheDocument();
    expect(screen.getByRole('button', { name: /send/i })).toBeInTheDocument();
  });

  it('should send message and display response', async () => {
    (fetch as jest.Mock).mockResolvedValueOnce({
      ok: true,
      json: async () => ({
        message: 'Hello! How can I help you?',
        metadata: { tokens: 10 },
      }),
    });

    render(<ChatContainer />);
    
    const input = screen.getByRole('textbox');
    const sendButton = screen.getByRole('button', { name: /send/i });

    fireEvent.change(input, { target: { value: 'Hello' } });
    fireEvent.click(sendButton);

    await waitFor(() => {
      expect(screen.getByText('Hello')).toBeInTheDocument();
      expect(screen.getByText('Hello! How can I help you?')).toBeInTheDocument();
    });
  });
});

// next.config.js - Production configuration
/** @type {import('next').NextConfig} */
const nextConfig = {
  // Enable static optimization
  output: 'standalone',
  
  // Security headers
  async headers() {
    return [
      {
        source: '/(.*)',
        headers: [
          {
            key: 'X-Frame-Options',
            value: 'DENY',
          },
          {
            key: 'X-Content-Type-Options',
            value: 'nosniff',
          },
          {
            key: 'Referrer-Policy',
            value: 'origin-when-cross-origin',
          },
          {
            key: 'Content-Security-Policy',
            value: `
              default-src 'self';
              script-src 'self' 'unsafe-eval' 'unsafe-inline';
              style-src 'self' 'unsafe-inline';
              img-src 'self' data: https:;
              font-src 'self';
              connect-src 'self' https://api.openai.com;
            `.replace(/\s+/g, ' ').trim(),
          },
        ],
      },
    ];
  },

  // Environment variables validation
  env: {
    OPENAI_API_KEY: process.env.OPENAI_API_KEY,
  },

  // Bundle analyzer for optimization
  webpack: (config, { dev, isServer }) => {
    if (!dev && !isServer) {
      const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
      config.plugins.push(
        new BundleAnalyzerPlugin({
          analyzerMode: 'static',
          openAnalyzer: false,
        })
      );
    }
    return config;
  },
};

module.exports = nextConfig;

// lib/monitoring.ts
export class ChatbotMonitoring {
  static async logConversation(messages: ChatMessage[], metadata: any) {
    // Log to analytics service (e.g., Google Analytics, Mixpanel)
    if (typeof window !== 'undefined' && window.gtag) {
      window.gtag('event', 'chat_message', {
        message_count: messages.length,
        model_used: metadata.model,
        tokens_used: metadata.tokens,
        response_time: metadata.latency,
      });
    }
  }

  static async trackError(error: Error, context: string) {
    // Log to error tracking service (e.g., Sentry)
    console.error(`[${context}] Error:`, error);
    
    // Send to monitoring service
    if (process.env.NODE_ENV === 'production') {
      // Sentry.captureException(error, { tags: { context } });
    }
  }

  static async trackPerformance(metric: string, value: number) {
    // Track performance metrics
    if (typeof window !== 'undefined' && window.gtag) {
      window.gtag('event', 'timing_complete', {
        name: metric,
        value: Math.round(value),
      });
    }
  }
}
            

Conclusion

Congratulations! You've successfully built a sophisticated AI chatbot using Next.js and OpenAI. This comprehensive tutorial has covered everything from basic setup to advanced features and production deployment.

Your chatbot now includes: - A modern, responsive chat interface built with React and Tailwind CSS - Robust backend integration with OpenAI's GPT models - Advanced features like streaming responses and conversation memory - Comprehensive error handling and user experience considerations - Production-ready code with testing, monitoring, and security best practices

**What's Next?** - Experiment with different AI models and parameters to find the perfect balance for your use case - Implement additional features like voice input, file uploads, or integration with external APIs - Scale your chatbot with database storage, user authentication, and multi-tenant support - Explore fine-tuning custom models for domain-specific conversations - Build chatbot analytics and conversation insights

The AI landscape is evolving rapidly, and chatbots are becoming increasingly sophisticated. By mastering these fundamental concepts and best practices, you're well-equipped to build the next generation of conversational AI applications.

Remember to stay updated with OpenAI's latest model releases, maintain security best practices, and always prioritize user experience in your implementations. Happy coding!

Additional Resources

OpenAI Platform Documentation

Official OpenAI API documentation and guides

Next.js Documentation

Complete Next.js framework documentation

React TypeScript Cheatsheet

Comprehensive guide for using TypeScript with React

Tailwind CSS Documentation

Utility-first CSS framework documentation

Vercel Deployment Guide

Learn how to deploy Next.js apps with Vercel

OpenAI Best Practices

Production best practices for OpenAI applications

Ready to Build Your AI Image Generator?

Start implementing AI-powered image generation in your applications today with our comprehensive tutorials.