Build your first AI chat interface in minutes using the ShipSafe AI-SaaS boilerplate.

Overview

This tutorial walks you through creating a simple AI chat interface that:

  • Sends messages to the AI
  • Receives responses
  • Displays conversation history
  • Handles errors gracefully

Step 1: Set Up API Keys

Add your OpenAI API key to .env.local:

OPENAI_API_KEY=sk-your-key-here

Get your API key:

  1. Go to OpenAI Platform
  2. Navigate to API Keys
  3. Create a new secret key
  4. Copy it to your .env.local file

Step 2: Create the Chat Page

Create src/app/chat/page.tsx:

"use client";

import { useState } from "react";
import { useAuth } from "@core/src/lib/firebase/client";
import { useRouter } from "next/navigation";

interface Message {
  role: "user" | "assistant";
  content: string;
}

export default function ChatPage() {
  const { user, loading } = useAuth();
  const router = useRouter();
  const [messages, setMessages] = useState<Message[]>([]);
  const [input, setInput] = useState("");
  const [isLoading, setIsLoading] = useState(false);

  // Redirect if not authenticated
  if (!loading && !user) {
    router.push("/auth");
    return null;
  }

  const handleSend = async () => {
    if (!input.trim() || isLoading) return;

    const userMessage: Message = { role: "user", content: input };
    setMessages((prev) => [...prev, userMessage]);
    setInput("");
    setIsLoading(true);

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

      const data = await response.json();

      if (!response.ok) {
        throw new Error(data.error || "Failed to get response");
      }

      const assistantMessage: Message = {
        role: "assistant",
        content: data.data.response,
      };

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

  return (
    <div className="min-h-screen bg-base-200 p-4">
      <div className="max-w-4xl mx-auto">
        <h1 className="text-3xl font-bold mb-6">AI Chat</h1>

        {/* Messages */}
        <div className="bg-base-100 rounded-lg p-4 mb-4 min-h-[400px] max-h-[600px] overflow-y-auto">
          {messages.length === 0 ? (
            <p className="text-base-content/70 text-center py-8">
              Start a conversation by typing a message below.
            </p>
          ) : (
            <div className="space-y-4">
              {messages.map((msg, idx) => (
                <div
                  key={idx}
                  className={`flex ${
                    msg.role === "user" ? "justify-end" : "justify-start"
                  }`}
                >
                  <div
                    className={`max-w-[80%] rounded-lg p-3 ${
                      msg.role === "user"
                        ? "bg-primary text-primary-content"
                        : "bg-base-300 text-base-content"
                    }`}
                  >
                    <p className="whitespace-pre-wrap">{msg.content}</p>
                  </div>
                </div>
              ))}
            </div>
          )}
        </div>

        {/* Input */}
        <div className="flex gap-2">
          <input
            type="text"
            value={input}
            onChange={(e) => setInput(e.target.value)}
            onKeyPress={(e) => e.key === "Enter" && handleSend()}
            placeholder="Type your message..."
            className="flex-1 input input-bordered"
            disabled={isLoading}
          />
          <button
            onClick={handleSend}
            disabled={isLoading || !input.trim()}
            className="btn btn-primary"
          >
            {isLoading ? (
              <span className="loading loading-spinner"></span>
            ) : (
              "Send"
            )}
          </button>
        </div>
      </div>
    </div>
  );
}

Step 3: Add Authentication Check

The chat endpoint requires authentication. Make sure users are logged in:

// The API route already handles this, but you can add client-side check:
import { useAuth } from "@core/src/lib/firebase/client";

const { user, loading } = useAuth();

if (loading) {
  return <div>Loading...</div>;
}

if (!user) {
  router.push("/auth");
  return null;
}

Step 4: Test Your Chat

  1. Start the dev server:

    npm run dev
    
  2. Navigate to /chat

  3. Log in if not already authenticated

  4. Send a message like "Hello, how are you?"

  5. See the AI response

Step 5: Enhance the UI

Add Message Timestamps

interface Message {
  role: "user" | "assistant";
  content: string;
  timestamp: Date;
}

// When creating messages:
const userMessage: Message = {
  role: "user",
  content: input,
  timestamp: new Date(),
};

Add Typing Indicator

{isLoading && (
  <div className="flex justify-start">
    <div className="bg-base-300 rounded-lg p-3">
      <span className="loading loading-dots"></span>
    </div>
  </div>
)}

Add Error Handling

try {
  const response = await fetch("/api/ai/chat", { ... });
  
  if (response.status === 403) {
    // Subscription required
    const data = await response.json();
    alert(data.message || "Subscription required. Please upgrade your plan.");
  } else if (response.status === 400) {
    // Invalid input
    const data = await response.json();
    alert(data.error || "Invalid request");
  }
} catch (error) {
  // Network or other errors
  console.error("Chat error:", error);
  alert("Failed to connect. Please check your internet connection.");
}

Common Issues

"Subscription required" Error

Solution: The API requires an active subscription. For development:

  • Make sure you have an active subscription, or
  • Modify the subscription check temporarily for testing (not recommended for production)

"API key not found" Error

Solution:

  • Check .env.local has OPENAI_API_KEY
  • Restart the dev server after adding the key
  • Verify the key is valid

Messages Not Appearing

Solution:

  • Check browser console for errors
  • Verify authentication is working
  • Check network tab for API responses

Next Steps