By Nan Jiang

Building and Deploying a Personal AI Chatbot

A comprehensive guide on creating a custom AI chatbot using modern technologies and deploying it on a personal website

aitypescriptreactopenaisupabase

Building a personal AI chatbot and integrating it into your website can significantly enhance user engagement and provide interactive assistance to your visitors. In this post, I'll walk through the process of creating and deploying a custom chatbot using modern web technologies.

Tech Stack Overview

For this project, we'll use:

  • OpenAI API: For natural language processing and response generation
  • React: For building the chat interface
  • TypeScript: For type-safe development
  • Supabase: For storing chat history and managing API keys
  • Shadcn UI: For building a beautiful chat interface
  • Remix: For server-side rendering and API routes

Setting Up the Foundation

1. Project Structure

First, let's organize our chatbot-related code:

app/
├── components/
│   └── chat/
│       ├── chat-window.tsx
│       ├── message-input.tsx
│       ├── message-list.tsx
│       └── message-bubble.tsx
├── lib/
│   └── chat/
│       ├── types.ts
│       ├── openai.ts
│       └── chat-store.ts
└── routes/
    └── api/
        └── chat.ts

2. Data Model

In Supabase, we'll create tables for storing chat history:

create table chat_messages (
  id uuid default uuid_generate_v4() primary key,
  session_id uuid not null,
  content text not null,
  role text not null,
  created_at timestamp with time zone default timezone('utc'::text, now())
);

create table chat_sessions (
  id uuid default uuid_generate_v4() primary key,
  user_id uuid references auth.users,
  created_at timestamp with time zone default timezone('utc'::text, now())
);

Building the Chat Interface

1. Chat Components

The chat interface consists of several key components:

interface Message {
  id: string;
  content: string;
  role: 'user' | 'assistant';
  createdAt: Date;
}

export function ChatWindow() {
  const [messages, setMessages] = useState<Message[]>([]);
  const [isLoading, setIsLoading] = useState(false);

  return (
    <div className="flex flex-col h-[600px] w-full max-w-2xl mx-auto rounded-lg border">
      <MessageList messages={messages} />
      <MessageInput
        onSend={async content => {
          // Handle message sending
        }}
        isLoading={isLoading}
      />
    </div>
  );
}

2. API Integration

We'll create a server-side API route to handle chat requests:

import { OpenAI } from 'openai';
import { json } from '@remix-run/node';

export async function action({ request }: ActionArgs) {
  const { content } = await request.json();

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

  const completion = await openai.chat.completions.create({
    model: 'gpt-3.5-turbo',
    messages: [{ role: 'user', content }],
  });

  return json({
    message: completion.choices[0].message,
  });
}

Adding Advanced Features

1. Context Management

To make the chatbot more intelligent, we'll maintain conversation context:

interface ChatContext {
  messages: Message[];
  systemPrompt: string;
}

function buildChatContext(messages: Message[]): ChatContext {
  return {
    messages,
    systemPrompt: `You are a helpful assistant on my personal website. 
    You can help visitors learn about my projects, experience, and interests.`,
  };
}

2. Stream Responses

Implementing streaming for a more dynamic experience:

export async function streamResponse(messages: Message[]) {
  const stream = await openai.chat.completions.create({
    model: 'gpt-3.5-turbo',
    messages: messages.map(m => ({ role: m.role, content: m.content })),
    stream: true,
  });

  return new ReadableStream({
    async start(controller) {
      for await (const chunk of stream) {
        controller.enqueue(chunk.choices[0]?.delta?.content || '');
      }
      controller.close();
    },
  });
}

Deployment and Integration

1. Environment Setup

Create a .env file with necessary configurations:

OPENAI_API_KEY=your_api_key
SUPABASE_URL=your_supabase_url
SUPABASE_ANON_KEY=your_supabase_key

2. Chat Widget Integration

Add the chat widget to your website layout:

export function Layout({ children }: { children: React.ReactNode }) {
  const [isChatOpen, setIsChatOpen] = useState(false);

  return (
    <div className="min-h-screen">
      {children}
      <button
        onClick={() => setIsChatOpen(true)}
        className="fixed bottom-4 right-4 p-4 rounded-full bg-primary"
      >
        <MessageCircle className="w-6 h-6" />
      </button>
      {isChatOpen && (
        <Dialog open={isChatOpen} onOpenChange={setIsChatOpen}>
          <DialogContent>
            <ChatWindow />
          </DialogContent>
        </Dialog>
      )}
    </div>
  );
}

Performance Optimization

  1. Lazy Loading

    • Load chat components only when needed
    • Use React.lazy() for code splitting
  2. Caching

    • Cache common responses in Supabase
    • Implement rate limiting for API calls
  3. Error Handling

    • Graceful fallbacks for API failures
    • Clear error messages for users

Security Considerations

  1. API Key Protection

    • Never expose API keys in client-side code
    • Use server-side API routes for OpenAI calls
  2. Rate Limiting

    • Implement request limits per session
    • Monitor usage patterns for abuse
  3. Data Privacy

    • Clear data retention policies
    • Option for users to clear chat history

Future Improvements

Here are some ways to enhance the chatbot:

  1. Personalization

    • Train on your personal content
    • Add memory of past conversations
  2. Multi-modal Support

    • Handle image inputs
    • Support voice interactions
  3. Analytics

    • Track common questions
    • Measure response quality

Conclusion

Building a personal chatbot is an exciting way to make your website more interactive and helpful for visitors. The key is to start simple and gradually add features based on user feedback and needs.

Feel free to check out the chatbot in action by clicking the chat icon in the bottom right corner of this page. The source code is available on my GitHub repository!