DEV Community

Cover image for Creating an AI Chatbot with CopilotKit: A Step-by-Step Guide Using Flask and React
Abu Taher Siddik
Abu Taher Siddik

Posted on

Creating an AI Chatbot with CopilotKit: A Step-by-Step Guide Using Flask and React

Creating an AI chatbot using CopilotKit integrated with a Flask backend can be an exciting project. Below is a step-by-step guide on how to set up your project, including the necessary components for both the frontend (using React and CopilotKit) and the backend (using Flask).

Image description

Project Overview

Components

  1. Flask Backend: Handles API requests and serves the frontend.
  2. React Frontend: Uses CopilotKit to create an interactive AI chatbot interface.
  3. AI Integration: Connects to an AI model (like Google's Gemini) for generating responses.

Step 1: Set Up the Flask Backend

1. Install Flask

First, create a virtual environment and install Flask:

mkdir chatbot-backend
cd chatbot-backend
python -m venv venv
source venv/bin/activate  # On Windows use `venv\Scripts\activate`
pip install Flask flask-cors
Enter fullscreen mode Exit fullscreen mode

2. Create the Flask App

Create a file named app.py:

from flask import Flask, request, jsonify
from flask_cors import CORS
import openai  # Make sure to install OpenAI SDK if you're using it

app = Flask(__name__)
CORS(app)  # Allow CORS for all domains

# Set your OpenAI API key
openai.api_key = 'YOUR_OPENAI_API_KEY'

@app.route('/api/chat', methods=['POST'])
def chat():
    user_message = request.json.get('message')

    # Call the AI model (e.g., OpenAI GPT)
    response = openai.ChatCompletion.create(
        model="gpt-3.5-turbo",
        messages=[{"role": "user", "content": user_message}]
    )

    bot_message = response.choices[0].message['content']
    return jsonify({'response': bot_message})

if __name__ == '__main__':
    app.run(debug=True)
Enter fullscreen mode Exit fullscreen mode

3. Run the Flask App

Run your Flask app:

python app.py
Enter fullscreen mode Exit fullscreen mode

Your backend should now be running on http://127.0.0.1:5000.

Step 2: Set Up the React Frontend with CopilotKit

1. Create a React App

In a new directory, create your React app:

npx create-react-app chatbot-frontend
cd chatbot-frontend
Enter fullscreen mode Exit fullscreen mode

2. Install CopilotKit

Install CopilotKit and Axios for making API calls:

npm install @copilotkit/react axios
Enter fullscreen mode Exit fullscreen mode

3. Create the Chatbot Component

Create a file named Chatbot.js in the src directory:

import React, { useState, useEffect } from 'react';
import axios from 'axios';
import { CopilotSidebar, useCopilotChat } from '@copilotkit/react';
import './Chatbot.css'; // Importing CSS file for styling

const Chatbot = () => {
    const [message, setMessage] = useState('');
    const [chatHistory, setChatHistory] = useState([]);
    const [selectedPrompt, setSelectedPrompt] = useState('');
    const [conversations, setConversations] = useState([]);
    const [prompts, setPrompts] = useState([]);
    const [newPrompt, setNewPrompt] = useState('');
    const [searchTerm, setSearchTerm] = useState('');
    const [showPromptInput, setShowPromptInput] = useState(false); // State to toggle prompt input visibility
    const { addMessage } = useCopilotChat();

    useEffect(() => {
        // Load conversations and prompts from local storage on component mount
        const storedChats = JSON.parse(localStorage.getItem('chatHistory')) || [];
        const storedPrompts = JSON.parse(localStorage.getItem('prompts')) || [];
        setConversations(storedChats);
        setPrompts(storedPrompts);

        // Load the most recent conversation into chat history if available
        if (storedChats.length > 0) {
            setChatHistory(storedChats[storedChats.length - 1].history);
        }
    }, []);

    const handleSendMessage = async () => {
        const userMessage = { sender: 'User', text: message };
        const updatedChatHistory = [...chatHistory, userMessage];
        setChatHistory(updatedChatHistory);
        setMessage('');

        try {
            const response = await axios.post('http://127.0.0.1:5000/api/chat', { message });
            const botMessage = { sender: 'Bot', text: response.data.response };
            updatedChatHistory.push(botMessage);
            setChatHistory(updatedChatHistory);

            // Add messages to Copilot
            addMessage(userMessage);
            addMessage(botMessage);

            // Save updated chat history to local storage
            localStorage.setItem('chatHistory', JSON.stringify(updatedChatHistory));
        } catch (error) {
            console.error('Error sending message:', error);
        }
    };

    const handlePromptSelect = (prompt) => {
        setSelectedPrompt(prompt);
        setMessage(prompt);
    };

    const handleConversationClick = (conversation) => {
        setChatHistory(conversation.history);
    };

    const handleSaveConversation = () => {
        const title = prompt("Enter a title for this conversation:");
        if (title) {
            const newConversation = { title, history: chatHistory };
            const updatedConversations = [...conversations, newConversation];
            setConversations(updatedConversations);
            localStorage.setItem('chatHistory', JSON.stringify(updatedConversations));
        }
    };

    const handleAddPrompt = () => {
        if (newPrompt.trim()) {
            const updatedPrompts = [...prompts, newPrompt.trim()];
            setPrompts(updatedPrompts);
            localStorage.setItem('prompts', JSON.stringify(updatedPrompts));
            setNewPrompt('');
            setShowPromptInput(false); // Hide the input after adding
        }
    };

    // Function to delete a conversation
    const handleDeleteConversation = (index) => {
        const updatedConversations = conversations.filter((_, i) => i !== index);
        setConversations(updatedConversations);
        localStorage.setItem('chatHistory', JSON.stringify(updatedConversations));

        // Optionally reset chat history if the deleted conversation was currently loaded
        if (chatHistory === conversations[index].history) {
            setChatHistory([]);
        }
    };

    // Filtered prompts based on search term
    const filteredPrompts = prompts.filter(prompt => 
        prompt.toLowerCase().includes(searchTerm.toLowerCase())
    );

    // Filtered conversations based on search term
    const filteredConversations = conversations.filter(conversation =>
        conversation.title.toLowerCase().includes(searchTerm.toLowerCase())
    );

    return (
        <div className="chatbot-container">
            <CopilotSidebar title="Recent Conversations">
                <input 
                    type="text" 
                    placeholder="Search Conversations..." 
                    value={searchTerm} 
                    onChange={(e) => setSearchTerm(e.target.value)} 
                    className="search-input"
                />
                {filteredConversations.map((conversation, index) => (
                    <div key={index} className="conversation-title">
                        <span onClick={() => handleConversationClick(conversation)}>
                            {conversation.title}
                        </span>
                        <button className="delete-button" onClick={() => handleDeleteConversation(index)}>
                            🗑️ {/* Delete icon */}
                        </button>
                    </div>
                ))}
                <button onClick={handleSaveConversation} className="save-button">Save Conversation</button>
            </CopilotSidebar>
            <div className="chat-area">
                <h2>AI Chatbot</h2>
                <div className="chat-history">
                    {chatHistory.map((chat, index) => (
                        <div key={index} className={`message ${chat.sender === 'User' ? 'user' : 'bot'}`}>
                            <strong>{chat.sender}:</strong> {chat.text}
                        </div>
                    ))}
                </div>
                <div className="input-area">
                    <select 
                        value={selectedPrompt} 
                        onChange={(e) => handlePromptSelect(e.target.value)} 
                        className="prompt-select"
                    >
                        <option value="">Select a prompt...</option>
                        {filteredPrompts.map((prompt, index) => (
                            <option key={index} value={prompt}>{prompt}</option>
                        ))}
                    </select>

                    {/* Button to toggle visibility of the new prompt input */}
                    <button onClick={() => setShowPromptInput(!showPromptInput)} className="toggle-prompt-button">
                        {showPromptInput ? "Hide Prompt Input" : "Add New Prompt"}
                    </button>

                    {/* New Prompt Input Field */}
                    {showPromptInput && (
                        <input 
                            type="text" 
                            value={newPrompt} 
                            onChange={(e) => setNewPrompt(e.target.value)} 
                            placeholder="Add a new prompt..." 
                            className="new-prompt-input"
                        />
                    )}

                    {/* Button to add the new prompt */}
                    {showPromptInput && (
                        <button onClick={handleAddPrompt} className="add-prompt-button">Add Prompt</button>
                    )}

                    {/* Message Input Field */}
                    <input 
                        type="text" 
                        value={message} 
                        onChange={(e) => setMessage(e.target.value)} 
                        placeholder="Type your message..." 
                        className="message-input"
                    />
                    <button onClick={handleSendMessage} className="send-button">Send</button>
                </div>
            </div>
        </div>
    );
};

export default Chatbot;
Enter fullscreen mode Exit fullscreen mode

4. Update App.js

Replace the contents of src/App.js with:

import React from 'react';
import Chatbot from './Chatbot';

function App() {
    return (
        <div className="App">
            <Chatbot />
        </div>
    );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

5. Create Chatbot.css

.chatbot-container {
    display: flex;
    height: 100vh;
}

.chat-area {
    margin-left: 20px;
    flex-grow: 1;
    padding: 20px;
    background-color: #f9f9f9;
    border-radius: 8px;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}

.chat-history {
    border: 1px solid #ccc;
    padding: 10px;
    height: calc(100% - 120px); /* Adjust height based on input area */
    overflow-y: scroll;
    border-radius: 8px;
}

.message {
    margin-bottom: 10px;
}

.user {
    text-align: right;
}

.bot {
    text-align: left;
}

.input-area {
    display: flex;
    align-items: center;
}

.prompt-select {
    margin-right: 10px;
}

.message-input {
    flex-grow: 1;
    padding: 10px;
    border-radius: 4px;
    border: 1px solid #ccc;
}

.send-button {
    padding: 10px 15px;
    background-color: #007bff;
    color: white;
    border: none;
    border-radius: 4px;
    cursor: pointer;
}

.send-button:hover {
    background-color: #0056b3;
}

.conversation-title {
    padding: 10px;
    cursor: pointer;
}

.conversation-title:hover {
    background-color: #f0f0f0;
}

.save-button {
    margin-top: 10px;
    padding: 5px 10px;
    background-color: #28a745;
    color: white;
    border: none;
    border-radius: 4px;
    cursor: pointer;
}

.save-button:hover {
    background-color: #218838;
}

.prompt-select,
.new-prompt-input,
.message-input {
    margin-right: 10px;
}

.search-input {
    margin-bottom: 10px;
}

.new-prompt-input {
   flex-grow: 1; /* Allow it to take remaining space */
   padding: 10px;
   border-radius: 4px;
   border: 1px solid #ccc;
}

.add-prompt-button,
.send-button,
.save-button {
   padding: 10px 15px;
   background-color: #007bff;
   color: white;
   border: none;
   border-radius: 4px;
   cursor: pointer;
}

.add-prompt-button:hover,
.send-button:hover,
.save-button:hover {
   background-color: #0056b3; /* Darker shade for hover */
}

.conversation-title {
   padding: 10px;
   cursor: pointer;
}

.conversation-title:hover {
   background-color: #f0f0f0; /* Highlight on hover */
}

.toggle-prompt-button {
   margin-right: 10px;
   padding: 10px 15px;
   background-color: #17a2b8; /* Different color for visibility */
   color: white;
   border: none;
   border-radius: 4px;
   cursor: pointer;
}

.toggle-prompt-button:hover {
   background-color: #138496; /* Darker shade for hover */
}
Enter fullscreen mode Exit fullscreen mode

5. Run the React App

Start your React app:

npm start
Enter fullscreen mode Exit fullscreen mode

Your frontend should now be running on http://localhost:3000.

Step 3: Testing Your Chatbot

  1. Ensure both your Flask backend and React frontend are running.
  2. Open your browser and navigate to http://localhost:3000.
  3. Type messages into the input field and see responses from the AI chatbot.

To include a GitHub repository URL in your dev.to post about building a chatbot with CopilotKit, Flask, and React, you can format it as follows:


Building an AI Chatbot with CopilotKit, Flask, and React

In this post, we'll walk through the steps to create an interactive AI chatbot using CopilotKit for the frontend, Flask as the backend, and React for the user interface.

GitHub Repository

You can find the complete code for this project on GitHub: Chatbot with CopilotKit

Conclusion

You now have a basic AI chatbot built using CopilotKit for the frontend and Flask for the backend! You can enhance this project by adding features like user authentication, saving chat history, or improving UI/UX design.

Top comments (0)