Code icon

The App is Under a Quick Maintenance

We apologize for the inconvenience. Please come back later

Menu iconMenu iconOpenAI API Bible Volume 2
OpenAI API Bible Volume 2

Project: Building a Simple Chatbot with Memory

Project Steps

1.  Project Setup

  • Create a new directory for your project:
    mkdir chatbot_project
    cd chatbot_project
  • Create a virtual environment:
    python -m venv venv
  • Activate the virtual environment:
  • On Windows:
    venv\Scripts\activate
  • On macOS and Linux:
    source venv/bin/activate
  • Install the required libraries:
    pip install flask openai flask-session python-dotenv streamlit psycopg2
  • Create a .env file in your project directory and add your OpenAI API key and database url:
    OPENAI_API_KEY=YOUR_OPENAI_API_KEY
    DATABASE_URL=YOUR_DATABASE_URL # e.g., "postgresql://user:password@host:port/database_name"
  • For the postgres database, make sure you have a postgres database running and the url is correctly configured.

2.  Flask Implementation

  • Create a file named app.py with the following code:
    from flask import Flask, request, render_template, session, redirect, url_for
    import openai
    import os
    from dotenv import load_dotenv
    from flask_session import Session
    import psycopg2 #for postgres

    load_dotenv()
    openai.api_key = os.getenv("OPENAI_API_KEY")
    DATABASE_URL = os.getenv("DATABASE_URL")

    app = Flask(__name__)
    app.secret_key = os.urandom(24)
    app.config["SESSION_TYPE"] = "filesystem"  # Or "postgresql", "redis", etc.  For production, use a proper database.
    #app.config["SESSION_TYPE"] = "postgresql"
    #app.config["SESSION_PERMANENT"] = True  # Optional: Make sessions permanent
    #app.config["SESSION_SQLALCHEMY"] = {'url': DATABASE_URL}  #needs  Flask-SQLAlchemy
    Session(app)

    # --- Database Connection (PostgreSQL) ---
    def get_db_connection():
        """Gets a PostgreSQL connection."""
        try:
            conn = psycopg2.connect(DATABASE_URL)
            return conn
        except psycopg2.Error as e:
            print(f"Database connection error: {e}")
            return None

    def create_table():
        """Creates the messages table if it doesn't exist."""
        conn = get_db_connection()
        if conn is not None:
            try:
                cursor = conn.cursor()
                cursor.execute("""
                    CREATE TABLE IF NOT EXISTS messages (
                        id SERIAL PRIMARY KEY,
                        session_id TEXT NOT NULL,
                        role TEXT NOT NULL,
                        content TEXT NOT NULL,
                        created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
                    )
                """)
                conn.commit()
            except psycopg2.Error as e:
                print(f"Error creating table: {e}")
            finally:
                conn.close()

    def store_message(session_id: str, role: str, content: str):
        """Stores a message in the database."""
        conn = get_db_connection()
        if conn is not None:
            try:
                cursor = conn.cursor()
                cursor.execute(
                    "INSERT INTO messages (session_id, role, content) VALUES (%s, %s, %s)",
                    (session_id, role, content),
                )
                conn.commit()
            except psycopg2.Error as e:
                print(f"Error storing message: {e}")
            finally:
                conn.close()

    def get_history_from_db(session_id: str) -> List[Dict[str, str]]:
        """Retrieves the conversation history for a given session ID from the database."""
        conn = get_db_connection()
        if conn is not None:
            try:
                cursor = conn.cursor()
                cursor.execute(
                    "SELECT role, content FROM messages WHERE session_id = %s ORDER BY created_at",
                    (session_id,),
                )
                history = [{"role": row[0], "content": row[1]} for row in cursor.fetchall()]
                return history
            except psycopg2.Error as e:
                print(f"Error retrieving history from database: {e}")
                return []
            finally:
                conn.close()
        return []


    create_table()  # Ensure the table exists

    # --- Chat route ---
    @app.route("/", methods=["GET", "POST"])
    def chat():
        """Handles the main chat functionality."""
        if "session_id" not in session:
            session["session_id"] = os.urandom(16).hex()  # Generate unique session ID

        session_id = session["session_id"]
        history = get_history_from_db(session_id)  # Get history from the database

        if not history:
            history = [{"role": "system", "content": "You are a helpful assistant."}]
            session["history"] = history # Initialize session

        if request.method == "POST":
            user_input = request.form["user_input"]
            store_message(session_id, "user", user_input)  # Store to database
            history.append({"role": "user", "content": user_input})

            try:
                response = openai.ChatCompletion.create(
                    model="gpt-4o",
                    messages=history,
                    temperature=0.6,
                )
                assistant_reply = response.choices[0].message.content
                store_message(session_id, "assistant", assistant_reply)  # Store to database
                history.append({"role": "assistant", "content": assistant_reply})
                session["history"] = history
                session.modified = True # IMPORTANT
                return render_template("chat.html", history=history[1:])  # Pass history to template
            except openai.error.OpenAIError as e:
                error_message = f"OpenAI API Error: {e}"
                print(error_message)
                store_message(session_id, "assistant", error_message)
                history.append({"role": "assistant", "content": error_message})
                session["history"] = history
                session.modified = True
                return render_template("chat.html", history=history[1:])
            except Exception as e:
                error_message = f"An unexpected error occurred: {e}"
                print(error_message)
                store_message(session_id, "assistant", error_message)
                history.append({"role": "assistant", "content": error_message})
                session["history"] = history
                session.modified = True
                return render_template("chat.html", history=history[1:])

        return render_template("chat.html", history=history[1:])
    @app.route("/clear", methods=["POST"])
    def clear_chat():
        session.pop("session_id", None)
        session.pop("history", None)
        return redirect(url_for("chat"))

    if __name__ == "__main__":
        app.run(debug=True)

    Code breakdown:

    • from flask import ...: Imports the necessary Flask modules.
    • import openai: Imports the OpenAI library.
    • import os: Imports the os module for interacting with the operating system, such as accessing environment variables.
    • from dotenv import load_dotenv: Imports the load_dotenv function to load environment variables from a .env file.
    • from flask_session import Session: Imports the Session class from flask_session.
    • import psycopg2: Imports the psycopg2 library for interacting with PostgreSQL.
    • load_dotenv(): Loads the environment variables from a .env file.
    • openai.api_key = os.getenv("OPENAI_API_KEY"): Retrieves the OpenAI API key from the environment variable and sets it for the OpenAI library.
    • DATABASE_URL = os.getenv("DATABASE_URL"): Retrieves the PostgreSQL database URL from the environment variable.
    • app = Flask(__name__): Creates a Flask application instance.
    • app.secret_key = os.urandom(24): Sets a secret key for the Flask application. This is essential for using Flask sessions.
    • app.config["SESSION_TYPE"] = "filesystem|postgresql|redis|mongodb": Configures the session storage type. For this project, we are using a database.
    • Session(app): Initializes the Flask-Session extension, binding it to the Flask app.
    • get_db_connection():
      • Establishes a connection to the PostgreSQL database using the URL from the environment variable.
      • Returns the connection object.
      • Handles potential connection errors with a try...except block.
    • create_table():
      • Creates the "messages" table in the PostgreSQL database if it doesn't exist. The table stores the conversation history, including a unique session_id for each conversation.
    • store_message(session_id, role, content):
      • Stores a message (user or assistant) in the "messages" table, including the session_id.
    • get_history_from_db(session_id):
      • Retrieves the conversation history for a given session_id from the database.
    • @app.route("/", methods=["GET", "POST"]): Defines the route for the application's main page ("/"). The chat() function handles both GET and POST requests to this URL.
    • def chat():
      • if "session_id" not in session:: Checks if a session_id exists in the user's session. If not, it generates a unique ID using os.urandom(16).hex() and stores it in the session.
      • session_id = session["session_id"]: Retrieves the session_id from the session.
      • history = get_history_from_db(session_id): Retrieves the conversation history from the database using the session ID.
      • if not history: checks if the history is empty and initializes it.
      • if request.method == "POST":: Handles POST requests (when the user submits a message).
        • user_input = request.form["user_input"]: Gets the user's input from the form.
        • store_message(session_id, "user", user_input): Stores the user's message in the database, associated with the session ID.
        • The code then calls the OpenAI API to get a response, stores the response in the database, and updates the session.
        • Renders the "chat.html" template, passing the conversation history.
      • return render_template("chat.html", history=history[1:]): Handles GET requests (when the user loads the page). Renders the "chat.html" template, passing the conversation history.
    • @app.route("/clear", methods=["POST"]): Route for clearing the chat session.
    • def clear_chat():: clears the session_id and history from the session.
    • if __name__ == "__main__":: Starts the Flask development server if the script is executed directly.
  • Create a folder named templates in the same directory as app.py.
  • Create a file named chat.html inside the templates folder with the following code:
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>GPT-4o Chat Assistant</title>
        <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
        <style>
            /* --- General Styles --- */
            body {
                font-family: 'Inter', sans-serif;
                background-color: #f3f4f6;
                padding: 20px;
                display: flex;
                justify-content: center;
                align-items: center;
                min-height: 100vh;
                margin: 0;
                color: #1f2937;
            }
            .container {
                max-width: 800px;
                width: 100%;
                background-color: #fff;
                padding: 30px;
                border-radius: 0.75rem;
                box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
            }
            h2 {
                font-size: 1.875rem;
                font-weight: 600;
                margin-bottom: 2rem;
                color: #1e293b;
                text-align: center;
            }
            /* --- Message Styles --- */
            .message {
                margin-bottom: 1.5rem;
                display: flex;
                flex-direction: column;
            }
            .message p {
                padding: 1rem;
                border-radius: 1rem;
                max-width: 80%;
            }
            .user p {
                background-color: #e0f2fe;
                color: #1d4ed8;
                margin-left: auto;
            }
            .assistant p {
                background-color: #f0fdf4;
                color: #15803d;
            }
            .message strong {
                font-size: 0.875rem;
                font-weight: 600;
                margin-bottom: 0.25rem;
            }
            .user strong {
                color: #1e40af;
            }
            .assistant strong {
                color: #16a34a;
            }
            /* --- Form Styles --- */
            form {
                margin-top: 2rem;
                display: flex;
                flex-direction: column;
                gap: 0.5rem;
            }
            textarea {
                width: 100%;
                padding: 0.75rem;
                border-radius: 0.5rem;
                border: 1px solid #d1d5db;
                resize: none;
                font-size: 1rem;
                line-height: 1.5rem;
                margin-bottom: 0.25rem;
                box-shadow: inset 0 2px 4px rgba(0,0,0,0.06);
            }
            textarea:focus {
                outline: none;
                border-color: #3b82f6;
                box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15);
            }
            input[type="submit"] {
                padding: 0.75rem 1.5rem;
                border-radius: 0.5rem;
                background-color: #3b82f6;
                color: #fff;
                font-size: 1rem;
                font-weight: 600;
                cursor: pointer;
                transition: background-color 0.3s ease;
                border: none;
                box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1);
            }
            input[type="submit"]:hover {
                background-color: #2563eb;
            }
            input[type="submit"]:focus {
                outline: none;
                box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.3);
            }
            /* --- Responsive Adjustments --- */
            @media (max-width: 768px) {
                .container {
                    padding: 20px;
                }
                textarea {
                    height: 100px;
                }
            }
            .clear-history-button{
                margin-top: 2rem;
                padding: 0.75rem 1.5rem;
                border-radius: 0.5rem;
                background-color: #3b82f6;
                color: #fff;
                font-size: 1rem;
                font-weight: 600;
                cursor: pointer;
                transition: background-color 0.3s ease;
                border: none;
                box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1);
            }
        </style>
    </head>
    <body>
        <div class="container">
            <h2>🧠 GPT-4o Chat Assistant</h2>
            {% for msg in history %}
                <div class="message {{ msg.role }}">
                    <p><strong>{{ msg.role.capitalize() }}:</strong> {{ msg.content }}</p>
                </div>
            {% endfor %}
            <form method="post">
                <textarea name="user_input" placeholder="Type your message..."></textarea><br>
                <input type="submit" value="Send Message">
            </form>
            <form method="post" action="/clear">
                <input type="submit" value="Clear Chat" class = "clear-history-button">
            </form>
        </div>
    </body>
    </html>
  • This HTML template provides the structure and styling for the chat interface. It includes a form for user input and displays the conversation history.

3.  Streamlit Implementation

Create a file named streamlit_app.py with the following code:

import streamlit as st
import openai
import os
from dotenv import load_dotenv
import psycopg2  # Import psycopg2
import datetime
from typing import List, Dict

load_dotenv()
openai.api_key = os.getenv("OPENAI_API_KEY")
DATABASE_URL = os.getenv("DATABASE_URL")

st.set_page_config(page_title="GPT-4o Chat with Memory", page_icon="🧠")
st.title("🧠 GPT-4o Chatbot with Persistent Memory")

# --- Database Connection (PostgreSQL) ---
def get_db_connection():
    """Gets a PostgreSQL connection."""
    try:
        conn = psycopg2.connect(DATABASE_URL)
        return conn
    except psycopg2.Error as e:
        st.error(f"Database connection error: {e}")
        return None

def create_table():
    """Creates the messages table if it doesn't exist."""
    conn = get_db_connection()
    if conn is not None:
        try:
            cursor = conn.cursor()
            cursor.execute("""
                CREATE TABLE IF NOT EXISTS messages (
                    id SERIAL PRIMARY KEY,
                    session_id TEXT NOT NULL,
                    role TEXT NOT NULL,
                    content TEXT NOT NULL,
                    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
                )
            """)
            conn.commit()
        except psycopg2.Error as e:
            st.error(f"Error creating table: {e}")
        finally:
            conn.close()

def store_message(session_id: str, role: str, content: str):
    """Stores a message in the database."""
    conn = get_db_connection()
    if conn is not None:
        try:
            cursor = conn.cursor()
            cursor.execute(
                "INSERT INTO messages (session_id, role, content) VALUES (%s, %s, %s)",
                (session_id, role, content),
            )
            conn.commit()
        except psycopg2.Error as e:
            st.error(f"Error storing message: {e}")
        finally:
            conn.close()

def get_history_from_db(session_id: str) -> List[Dict[str, str]]:
    """Retrieves the conversation history for a given session ID from the database."""
    conn = get_db_connection()
    if conn is not None:
        try:
            cursor = conn.cursor()
            cursor.execute(
                "SELECT role, content FROM messages WHERE session_id = %s ORDER BY created_at",
                (session_id,),
            )
            history = [{"role": row[0], "content": row[1]} for row in cursor.fetchall()]
            return history
        except psycopg2.Error as e:
            st.error(f"Error retrieving history from database: {e}")
            return []
        finally:
            conn.close()
    return []

create_table()  # Ensure the table exists

# --- Session Management ---
if "session_id" not in st.session_state:
    st.session_state.session_id = os.urandom(16).hex()  # Generate unique session ID

session_id = st.session_state.session_id

# --- Initialize chat history ---
if "messages" not in st.session_state:
    st.session_state.messages = get_history_from_db(session_id)
    if not st.session_state.messages:
        st.session_state.messages = [{"role": "system", "content": "You are a helpful assistant."}]

# --- Display chat history ---
for msg in st.session_state.messages[1:]:  # Skip system message
    speaker = "🧑‍💻 You" if msg["role"] == "user" else "🤖 Assistant"
    with st.chat_message(msg["role"]):
        st.markdown(f"**{speaker}:** {msg['content']}")

# --- User input ---
user_input = st.chat_input("Ask something...", key="user_input")  # Use key for chat_input

if user_input:
    # Store user message
    store_message(session_id, "user", user_input)
    st.session_state.messages.append({"role": "user", "content": user_input})

    # Get assistant reply
    with st.chat_message("assistant"):
        placeholder = st.empty()  # Reserve a placeholder for the response
        with st.spinner("Thinking..."):
            try:
                response = openai.ChatCompletion.create(
                    model="gpt-4o",
                    messages=st.session_state.messages,
                    temperature=0.6,
                )
                reply = response["choices"][0]["message"]["content"]
                store_message(session_id, "assistant", reply)  # Store assistant reply
                st.session_state.messages.append({"role": "assistant", "content": reply})
                placeholder.markdown(f"**🤖 Assistant:** {reply}")  # Replace placeholder with full response
            except openai.error.OpenAIError as e:
                error_message = f"OpenAI API Error: {e}"
                store_message(session_id, "assistant", error_message)
                st.session_state.messages.append({"role": "assistant", "content": error_message})
                st.error(error_message)
                placeholder.markdown(f"**🤖 Assistant:** {error_message}")
            except Exception as e:
                error_message = f"An unexpected error occurred: {e}"
                store_message(session_id, "assistant", error_message)
                st.session_state.messages.append({"role": "assistant", "content": error_message})
                st.error(error_message)
                placeholder.markdown(f"**🤖 Assistant:** {error_message}")
    # Clear the input field after sending the message
    st.session_state.user_input = ""
    st.rerun()

Code brakdown:

  • import streamlit as st: Imports the Streamlit library.
  • import openai: Imports the OpenAI library.
  • import os: Imports the os module for interacting with the operating system, such as accessing environment variables.
  • from dotenv import load_dotenv: Imports the load_dotenv function to load environment variables from a .env file.
  • import psycopg2: Imports the psycopg2 library for interacting with the PostgreSQL database.
  • import datetime: Imports the datetime module.
  • from typing import List, Dict: Imports typing
  • load_dotenv(): Loads the environment variables from a .env file.
  • openai.api_key = os.getenv("OPENAI_API_KEY"): Retrieves the OpenAI API key from the environment variable and sets it for the OpenAI library.
  • DATABASE_URL = os.getenv("DATABASE_URL"): Retrieves the PostgreSQL database URL.
  • st.set_page_config(...): Configures the page title and icon.
  • st.title(...): Sets the title of the Streamlit application.
  • get_db_connection():
    • Establishes a connection to the PostgreSQL database using the URL from the environment variable.
    • Returns the connection object.
    • Handles potential connection errors using a try...except block, displaying an error message using st.error().
  • create_table():
    • Creates the "messages" table in the PostgreSQL database if it doesn't exist. The table stores the conversation history, including a unique session_id for each conversation.
    • Handles potential errors during table creation using a try...except block.
  • store_message(session_id, role, content):
    • Stores a message (user or assistant) in the "messages" table, along with the session_id.
    • Handles potential errors during message storage.
  • get_history_from_db(session_id):
    • Retrieves the conversation history for a specific session_id from the database.
  • The main part of the code:
    • if "session_id" not in st.session_state:: Checks if a session ID exists in Streamlit's session state. If not, it generates a unique ID using os.urandom(16).hex().
    • session_id = st.session_state.session_id: Retrieves the session ID.
    • if "messages" not in st.session_state::
      • Retrieves the conversation history from the database.
      • If no history exists in the database for the current session, it initializes the chat history with the system message.
    • The code then displays the chat history.
    • user_input = st.chat_input(...): Creates a text input field for the user to type their message.
    • When the user submits a message:
      • The message is stored in the database using the store_message() function.
      • The message is added to the chat history in st.session_state.
      • The application calls the OpenAI API to get a response.
      • The assistant's response is stored in the database and added to the chat history.
      • The assistant's response is displayed in the chat interface.

4. Run the Applications

  • Flask:
    • Open a terminal, navigate to the project directory, and make sure your virtual environment is activated.
    • Run the Flask application:
      python app.py
    • Open a web browser and go to http://localhost:5000 to interact with the chatbot.
  • Streamlit:
    • Open a terminal, navigate to the project directory, and make sure your virtual environment is activated.
    • Run the Streamlit application:
      streamlit run streamlit_app.py
    • A new browser tab will open with the Streamlit chatbot.

5.  Enhancements (Optional)

  • Implement user authentication:
    • Add secure login/registration system using JWT or session tokens
    • Create user profiles to store conversation preferences
    • Enable private conversations tied to specific user accounts
  • Add a "Clear Chat" button feature:
    • Implement both complete and selective chat history clearing
    • Add confirmation dialogs to prevent accidental deletions
    • Include option to export chat history before clearing
  • Enhance the front-end interface:
    • Create responsive layouts using modern CSS frameworks
    • Add real-time message updates using WebSocket
    • Implement markdown support and code highlighting
  • Deploy to production:
    • Set up CI/CD pipelines for automated deployment
    • Configure SSL certificates for secure communication
    • Implement monitoring and logging systems
  • Add vector database capabilities:
    • Implement embedding generation using models like BERT or USE
    • Set up vector similarity search using Pinecone or Milvus
    • Create semantic search functionality for chat history

This project serves as an extensive foundation for developing a chatbot with memory capabilities. Here's what you'll gain from completing it:

First, you'll get practical experience with modern web frameworks - both Flask for traditional web development and Streamlit for rapid prototyping of data applications. You'll learn how these frameworks differ and when to use each one.

Second, you'll master working with the OpenAI API, understanding how to structure prompts, handle responses, and manage API interactions effectively. This knowledge is crucial for building any AI-powered application.

Third, you'll learn database integration principles, including how to design schemas, manage connections, and handle data persistence. This ensures your chatbot can maintain conversations across sessions and scale effectively.

Finally, this project will prepare you for building more sophisticated chatbot applications, such as those incorporating advanced features like sentiment analysis, multiple AI models, or real-time processing. The skills you develop here form the building blocks for more complex AI applications.

Project Steps

1.  Project Setup

  • Create a new directory for your project:
    mkdir chatbot_project
    cd chatbot_project
  • Create a virtual environment:
    python -m venv venv
  • Activate the virtual environment:
  • On Windows:
    venv\Scripts\activate
  • On macOS and Linux:
    source venv/bin/activate
  • Install the required libraries:
    pip install flask openai flask-session python-dotenv streamlit psycopg2
  • Create a .env file in your project directory and add your OpenAI API key and database url:
    OPENAI_API_KEY=YOUR_OPENAI_API_KEY
    DATABASE_URL=YOUR_DATABASE_URL # e.g., "postgresql://user:password@host:port/database_name"
  • For the postgres database, make sure you have a postgres database running and the url is correctly configured.

2.  Flask Implementation

  • Create a file named app.py with the following code:
    from flask import Flask, request, render_template, session, redirect, url_for
    import openai
    import os
    from dotenv import load_dotenv
    from flask_session import Session
    import psycopg2 #for postgres

    load_dotenv()
    openai.api_key = os.getenv("OPENAI_API_KEY")
    DATABASE_URL = os.getenv("DATABASE_URL")

    app = Flask(__name__)
    app.secret_key = os.urandom(24)
    app.config["SESSION_TYPE"] = "filesystem"  # Or "postgresql", "redis", etc.  For production, use a proper database.
    #app.config["SESSION_TYPE"] = "postgresql"
    #app.config["SESSION_PERMANENT"] = True  # Optional: Make sessions permanent
    #app.config["SESSION_SQLALCHEMY"] = {'url': DATABASE_URL}  #needs  Flask-SQLAlchemy
    Session(app)

    # --- Database Connection (PostgreSQL) ---
    def get_db_connection():
        """Gets a PostgreSQL connection."""
        try:
            conn = psycopg2.connect(DATABASE_URL)
            return conn
        except psycopg2.Error as e:
            print(f"Database connection error: {e}")
            return None

    def create_table():
        """Creates the messages table if it doesn't exist."""
        conn = get_db_connection()
        if conn is not None:
            try:
                cursor = conn.cursor()
                cursor.execute("""
                    CREATE TABLE IF NOT EXISTS messages (
                        id SERIAL PRIMARY KEY,
                        session_id TEXT NOT NULL,
                        role TEXT NOT NULL,
                        content TEXT NOT NULL,
                        created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
                    )
                """)
                conn.commit()
            except psycopg2.Error as e:
                print(f"Error creating table: {e}")
            finally:
                conn.close()

    def store_message(session_id: str, role: str, content: str):
        """Stores a message in the database."""
        conn = get_db_connection()
        if conn is not None:
            try:
                cursor = conn.cursor()
                cursor.execute(
                    "INSERT INTO messages (session_id, role, content) VALUES (%s, %s, %s)",
                    (session_id, role, content),
                )
                conn.commit()
            except psycopg2.Error as e:
                print(f"Error storing message: {e}")
            finally:
                conn.close()

    def get_history_from_db(session_id: str) -> List[Dict[str, str]]:
        """Retrieves the conversation history for a given session ID from the database."""
        conn = get_db_connection()
        if conn is not None:
            try:
                cursor = conn.cursor()
                cursor.execute(
                    "SELECT role, content FROM messages WHERE session_id = %s ORDER BY created_at",
                    (session_id,),
                )
                history = [{"role": row[0], "content": row[1]} for row in cursor.fetchall()]
                return history
            except psycopg2.Error as e:
                print(f"Error retrieving history from database: {e}")
                return []
            finally:
                conn.close()
        return []


    create_table()  # Ensure the table exists

    # --- Chat route ---
    @app.route("/", methods=["GET", "POST"])
    def chat():
        """Handles the main chat functionality."""
        if "session_id" not in session:
            session["session_id"] = os.urandom(16).hex()  # Generate unique session ID

        session_id = session["session_id"]
        history = get_history_from_db(session_id)  # Get history from the database

        if not history:
            history = [{"role": "system", "content": "You are a helpful assistant."}]
            session["history"] = history # Initialize session

        if request.method == "POST":
            user_input = request.form["user_input"]
            store_message(session_id, "user", user_input)  # Store to database
            history.append({"role": "user", "content": user_input})

            try:
                response = openai.ChatCompletion.create(
                    model="gpt-4o",
                    messages=history,
                    temperature=0.6,
                )
                assistant_reply = response.choices[0].message.content
                store_message(session_id, "assistant", assistant_reply)  # Store to database
                history.append({"role": "assistant", "content": assistant_reply})
                session["history"] = history
                session.modified = True # IMPORTANT
                return render_template("chat.html", history=history[1:])  # Pass history to template
            except openai.error.OpenAIError as e:
                error_message = f"OpenAI API Error: {e}"
                print(error_message)
                store_message(session_id, "assistant", error_message)
                history.append({"role": "assistant", "content": error_message})
                session["history"] = history
                session.modified = True
                return render_template("chat.html", history=history[1:])
            except Exception as e:
                error_message = f"An unexpected error occurred: {e}"
                print(error_message)
                store_message(session_id, "assistant", error_message)
                history.append({"role": "assistant", "content": error_message})
                session["history"] = history
                session.modified = True
                return render_template("chat.html", history=history[1:])

        return render_template("chat.html", history=history[1:])
    @app.route("/clear", methods=["POST"])
    def clear_chat():
        session.pop("session_id", None)
        session.pop("history", None)
        return redirect(url_for("chat"))

    if __name__ == "__main__":
        app.run(debug=True)

    Code breakdown:

    • from flask import ...: Imports the necessary Flask modules.
    • import openai: Imports the OpenAI library.
    • import os: Imports the os module for interacting with the operating system, such as accessing environment variables.
    • from dotenv import load_dotenv: Imports the load_dotenv function to load environment variables from a .env file.
    • from flask_session import Session: Imports the Session class from flask_session.
    • import psycopg2: Imports the psycopg2 library for interacting with PostgreSQL.
    • load_dotenv(): Loads the environment variables from a .env file.
    • openai.api_key = os.getenv("OPENAI_API_KEY"): Retrieves the OpenAI API key from the environment variable and sets it for the OpenAI library.
    • DATABASE_URL = os.getenv("DATABASE_URL"): Retrieves the PostgreSQL database URL from the environment variable.
    • app = Flask(__name__): Creates a Flask application instance.
    • app.secret_key = os.urandom(24): Sets a secret key for the Flask application. This is essential for using Flask sessions.
    • app.config["SESSION_TYPE"] = "filesystem|postgresql|redis|mongodb": Configures the session storage type. For this project, we are using a database.
    • Session(app): Initializes the Flask-Session extension, binding it to the Flask app.
    • get_db_connection():
      • Establishes a connection to the PostgreSQL database using the URL from the environment variable.
      • Returns the connection object.
      • Handles potential connection errors with a try...except block.
    • create_table():
      • Creates the "messages" table in the PostgreSQL database if it doesn't exist. The table stores the conversation history, including a unique session_id for each conversation.
    • store_message(session_id, role, content):
      • Stores a message (user or assistant) in the "messages" table, including the session_id.
    • get_history_from_db(session_id):
      • Retrieves the conversation history for a given session_id from the database.
    • @app.route("/", methods=["GET", "POST"]): Defines the route for the application's main page ("/"). The chat() function handles both GET and POST requests to this URL.
    • def chat():
      • if "session_id" not in session:: Checks if a session_id exists in the user's session. If not, it generates a unique ID using os.urandom(16).hex() and stores it in the session.
      • session_id = session["session_id"]: Retrieves the session_id from the session.
      • history = get_history_from_db(session_id): Retrieves the conversation history from the database using the session ID.
      • if not history: checks if the history is empty and initializes it.
      • if request.method == "POST":: Handles POST requests (when the user submits a message).
        • user_input = request.form["user_input"]: Gets the user's input from the form.
        • store_message(session_id, "user", user_input): Stores the user's message in the database, associated with the session ID.
        • The code then calls the OpenAI API to get a response, stores the response in the database, and updates the session.
        • Renders the "chat.html" template, passing the conversation history.
      • return render_template("chat.html", history=history[1:]): Handles GET requests (when the user loads the page). Renders the "chat.html" template, passing the conversation history.
    • @app.route("/clear", methods=["POST"]): Route for clearing the chat session.
    • def clear_chat():: clears the session_id and history from the session.
    • if __name__ == "__main__":: Starts the Flask development server if the script is executed directly.
  • Create a folder named templates in the same directory as app.py.
  • Create a file named chat.html inside the templates folder with the following code:
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>GPT-4o Chat Assistant</title>
        <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
        <style>
            /* --- General Styles --- */
            body {
                font-family: 'Inter', sans-serif;
                background-color: #f3f4f6;
                padding: 20px;
                display: flex;
                justify-content: center;
                align-items: center;
                min-height: 100vh;
                margin: 0;
                color: #1f2937;
            }
            .container {
                max-width: 800px;
                width: 100%;
                background-color: #fff;
                padding: 30px;
                border-radius: 0.75rem;
                box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
            }
            h2 {
                font-size: 1.875rem;
                font-weight: 600;
                margin-bottom: 2rem;
                color: #1e293b;
                text-align: center;
            }
            /* --- Message Styles --- */
            .message {
                margin-bottom: 1.5rem;
                display: flex;
                flex-direction: column;
            }
            .message p {
                padding: 1rem;
                border-radius: 1rem;
                max-width: 80%;
            }
            .user p {
                background-color: #e0f2fe;
                color: #1d4ed8;
                margin-left: auto;
            }
            .assistant p {
                background-color: #f0fdf4;
                color: #15803d;
            }
            .message strong {
                font-size: 0.875rem;
                font-weight: 600;
                margin-bottom: 0.25rem;
            }
            .user strong {
                color: #1e40af;
            }
            .assistant strong {
                color: #16a34a;
            }
            /* --- Form Styles --- */
            form {
                margin-top: 2rem;
                display: flex;
                flex-direction: column;
                gap: 0.5rem;
            }
            textarea {
                width: 100%;
                padding: 0.75rem;
                border-radius: 0.5rem;
                border: 1px solid #d1d5db;
                resize: none;
                font-size: 1rem;
                line-height: 1.5rem;
                margin-bottom: 0.25rem;
                box-shadow: inset 0 2px 4px rgba(0,0,0,0.06);
            }
            textarea:focus {
                outline: none;
                border-color: #3b82f6;
                box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15);
            }
            input[type="submit"] {
                padding: 0.75rem 1.5rem;
                border-radius: 0.5rem;
                background-color: #3b82f6;
                color: #fff;
                font-size: 1rem;
                font-weight: 600;
                cursor: pointer;
                transition: background-color 0.3s ease;
                border: none;
                box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1);
            }
            input[type="submit"]:hover {
                background-color: #2563eb;
            }
            input[type="submit"]:focus {
                outline: none;
                box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.3);
            }
            /* --- Responsive Adjustments --- */
            @media (max-width: 768px) {
                .container {
                    padding: 20px;
                }
                textarea {
                    height: 100px;
                }
            }
            .clear-history-button{
                margin-top: 2rem;
                padding: 0.75rem 1.5rem;
                border-radius: 0.5rem;
                background-color: #3b82f6;
                color: #fff;
                font-size: 1rem;
                font-weight: 600;
                cursor: pointer;
                transition: background-color 0.3s ease;
                border: none;
                box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1);
            }
        </style>
    </head>
    <body>
        <div class="container">
            <h2>🧠 GPT-4o Chat Assistant</h2>
            {% for msg in history %}
                <div class="message {{ msg.role }}">
                    <p><strong>{{ msg.role.capitalize() }}:</strong> {{ msg.content }}</p>
                </div>
            {% endfor %}
            <form method="post">
                <textarea name="user_input" placeholder="Type your message..."></textarea><br>
                <input type="submit" value="Send Message">
            </form>
            <form method="post" action="/clear">
                <input type="submit" value="Clear Chat" class = "clear-history-button">
            </form>
        </div>
    </body>
    </html>
  • This HTML template provides the structure and styling for the chat interface. It includes a form for user input and displays the conversation history.

3.  Streamlit Implementation

Create a file named streamlit_app.py with the following code:

import streamlit as st
import openai
import os
from dotenv import load_dotenv
import psycopg2  # Import psycopg2
import datetime
from typing import List, Dict

load_dotenv()
openai.api_key = os.getenv("OPENAI_API_KEY")
DATABASE_URL = os.getenv("DATABASE_URL")

st.set_page_config(page_title="GPT-4o Chat with Memory", page_icon="🧠")
st.title("🧠 GPT-4o Chatbot with Persistent Memory")

# --- Database Connection (PostgreSQL) ---
def get_db_connection():
    """Gets a PostgreSQL connection."""
    try:
        conn = psycopg2.connect(DATABASE_URL)
        return conn
    except psycopg2.Error as e:
        st.error(f"Database connection error: {e}")
        return None

def create_table():
    """Creates the messages table if it doesn't exist."""
    conn = get_db_connection()
    if conn is not None:
        try:
            cursor = conn.cursor()
            cursor.execute("""
                CREATE TABLE IF NOT EXISTS messages (
                    id SERIAL PRIMARY KEY,
                    session_id TEXT NOT NULL,
                    role TEXT NOT NULL,
                    content TEXT NOT NULL,
                    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
                )
            """)
            conn.commit()
        except psycopg2.Error as e:
            st.error(f"Error creating table: {e}")
        finally:
            conn.close()

def store_message(session_id: str, role: str, content: str):
    """Stores a message in the database."""
    conn = get_db_connection()
    if conn is not None:
        try:
            cursor = conn.cursor()
            cursor.execute(
                "INSERT INTO messages (session_id, role, content) VALUES (%s, %s, %s)",
                (session_id, role, content),
            )
            conn.commit()
        except psycopg2.Error as e:
            st.error(f"Error storing message: {e}")
        finally:
            conn.close()

def get_history_from_db(session_id: str) -> List[Dict[str, str]]:
    """Retrieves the conversation history for a given session ID from the database."""
    conn = get_db_connection()
    if conn is not None:
        try:
            cursor = conn.cursor()
            cursor.execute(
                "SELECT role, content FROM messages WHERE session_id = %s ORDER BY created_at",
                (session_id,),
            )
            history = [{"role": row[0], "content": row[1]} for row in cursor.fetchall()]
            return history
        except psycopg2.Error as e:
            st.error(f"Error retrieving history from database: {e}")
            return []
        finally:
            conn.close()
    return []

create_table()  # Ensure the table exists

# --- Session Management ---
if "session_id" not in st.session_state:
    st.session_state.session_id = os.urandom(16).hex()  # Generate unique session ID

session_id = st.session_state.session_id

# --- Initialize chat history ---
if "messages" not in st.session_state:
    st.session_state.messages = get_history_from_db(session_id)
    if not st.session_state.messages:
        st.session_state.messages = [{"role": "system", "content": "You are a helpful assistant."}]

# --- Display chat history ---
for msg in st.session_state.messages[1:]:  # Skip system message
    speaker = "🧑‍💻 You" if msg["role"] == "user" else "🤖 Assistant"
    with st.chat_message(msg["role"]):
        st.markdown(f"**{speaker}:** {msg['content']}")

# --- User input ---
user_input = st.chat_input("Ask something...", key="user_input")  # Use key for chat_input

if user_input:
    # Store user message
    store_message(session_id, "user", user_input)
    st.session_state.messages.append({"role": "user", "content": user_input})

    # Get assistant reply
    with st.chat_message("assistant"):
        placeholder = st.empty()  # Reserve a placeholder for the response
        with st.spinner("Thinking..."):
            try:
                response = openai.ChatCompletion.create(
                    model="gpt-4o",
                    messages=st.session_state.messages,
                    temperature=0.6,
                )
                reply = response["choices"][0]["message"]["content"]
                store_message(session_id, "assistant", reply)  # Store assistant reply
                st.session_state.messages.append({"role": "assistant", "content": reply})
                placeholder.markdown(f"**🤖 Assistant:** {reply}")  # Replace placeholder with full response
            except openai.error.OpenAIError as e:
                error_message = f"OpenAI API Error: {e}"
                store_message(session_id, "assistant", error_message)
                st.session_state.messages.append({"role": "assistant", "content": error_message})
                st.error(error_message)
                placeholder.markdown(f"**🤖 Assistant:** {error_message}")
            except Exception as e:
                error_message = f"An unexpected error occurred: {e}"
                store_message(session_id, "assistant", error_message)
                st.session_state.messages.append({"role": "assistant", "content": error_message})
                st.error(error_message)
                placeholder.markdown(f"**🤖 Assistant:** {error_message}")
    # Clear the input field after sending the message
    st.session_state.user_input = ""
    st.rerun()

Code brakdown:

  • import streamlit as st: Imports the Streamlit library.
  • import openai: Imports the OpenAI library.
  • import os: Imports the os module for interacting with the operating system, such as accessing environment variables.
  • from dotenv import load_dotenv: Imports the load_dotenv function to load environment variables from a .env file.
  • import psycopg2: Imports the psycopg2 library for interacting with the PostgreSQL database.
  • import datetime: Imports the datetime module.
  • from typing import List, Dict: Imports typing
  • load_dotenv(): Loads the environment variables from a .env file.
  • openai.api_key = os.getenv("OPENAI_API_KEY"): Retrieves the OpenAI API key from the environment variable and sets it for the OpenAI library.
  • DATABASE_URL = os.getenv("DATABASE_URL"): Retrieves the PostgreSQL database URL.
  • st.set_page_config(...): Configures the page title and icon.
  • st.title(...): Sets the title of the Streamlit application.
  • get_db_connection():
    • Establishes a connection to the PostgreSQL database using the URL from the environment variable.
    • Returns the connection object.
    • Handles potential connection errors using a try...except block, displaying an error message using st.error().
  • create_table():
    • Creates the "messages" table in the PostgreSQL database if it doesn't exist. The table stores the conversation history, including a unique session_id for each conversation.
    • Handles potential errors during table creation using a try...except block.
  • store_message(session_id, role, content):
    • Stores a message (user or assistant) in the "messages" table, along with the session_id.
    • Handles potential errors during message storage.
  • get_history_from_db(session_id):
    • Retrieves the conversation history for a specific session_id from the database.
  • The main part of the code:
    • if "session_id" not in st.session_state:: Checks if a session ID exists in Streamlit's session state. If not, it generates a unique ID using os.urandom(16).hex().
    • session_id = st.session_state.session_id: Retrieves the session ID.
    • if "messages" not in st.session_state::
      • Retrieves the conversation history from the database.
      • If no history exists in the database for the current session, it initializes the chat history with the system message.
    • The code then displays the chat history.
    • user_input = st.chat_input(...): Creates a text input field for the user to type their message.
    • When the user submits a message:
      • The message is stored in the database using the store_message() function.
      • The message is added to the chat history in st.session_state.
      • The application calls the OpenAI API to get a response.
      • The assistant's response is stored in the database and added to the chat history.
      • The assistant's response is displayed in the chat interface.

4. Run the Applications

  • Flask:
    • Open a terminal, navigate to the project directory, and make sure your virtual environment is activated.
    • Run the Flask application:
      python app.py
    • Open a web browser and go to http://localhost:5000 to interact with the chatbot.
  • Streamlit:
    • Open a terminal, navigate to the project directory, and make sure your virtual environment is activated.
    • Run the Streamlit application:
      streamlit run streamlit_app.py
    • A new browser tab will open with the Streamlit chatbot.

5.  Enhancements (Optional)

  • Implement user authentication:
    • Add secure login/registration system using JWT or session tokens
    • Create user profiles to store conversation preferences
    • Enable private conversations tied to specific user accounts
  • Add a "Clear Chat" button feature:
    • Implement both complete and selective chat history clearing
    • Add confirmation dialogs to prevent accidental deletions
    • Include option to export chat history before clearing
  • Enhance the front-end interface:
    • Create responsive layouts using modern CSS frameworks
    • Add real-time message updates using WebSocket
    • Implement markdown support and code highlighting
  • Deploy to production:
    • Set up CI/CD pipelines for automated deployment
    • Configure SSL certificates for secure communication
    • Implement monitoring and logging systems
  • Add vector database capabilities:
    • Implement embedding generation using models like BERT or USE
    • Set up vector similarity search using Pinecone or Milvus
    • Create semantic search functionality for chat history

This project serves as an extensive foundation for developing a chatbot with memory capabilities. Here's what you'll gain from completing it:

First, you'll get practical experience with modern web frameworks - both Flask for traditional web development and Streamlit for rapid prototyping of data applications. You'll learn how these frameworks differ and when to use each one.

Second, you'll master working with the OpenAI API, understanding how to structure prompts, handle responses, and manage API interactions effectively. This knowledge is crucial for building any AI-powered application.

Third, you'll learn database integration principles, including how to design schemas, manage connections, and handle data persistence. This ensures your chatbot can maintain conversations across sessions and scale effectively.

Finally, this project will prepare you for building more sophisticated chatbot applications, such as those incorporating advanced features like sentiment analysis, multiple AI models, or real-time processing. The skills you develop here form the building blocks for more complex AI applications.

Project Steps

1.  Project Setup

  • Create a new directory for your project:
    mkdir chatbot_project
    cd chatbot_project
  • Create a virtual environment:
    python -m venv venv
  • Activate the virtual environment:
  • On Windows:
    venv\Scripts\activate
  • On macOS and Linux:
    source venv/bin/activate
  • Install the required libraries:
    pip install flask openai flask-session python-dotenv streamlit psycopg2
  • Create a .env file in your project directory and add your OpenAI API key and database url:
    OPENAI_API_KEY=YOUR_OPENAI_API_KEY
    DATABASE_URL=YOUR_DATABASE_URL # e.g., "postgresql://user:password@host:port/database_name"
  • For the postgres database, make sure you have a postgres database running and the url is correctly configured.

2.  Flask Implementation

  • Create a file named app.py with the following code:
    from flask import Flask, request, render_template, session, redirect, url_for
    import openai
    import os
    from dotenv import load_dotenv
    from flask_session import Session
    import psycopg2 #for postgres

    load_dotenv()
    openai.api_key = os.getenv("OPENAI_API_KEY")
    DATABASE_URL = os.getenv("DATABASE_URL")

    app = Flask(__name__)
    app.secret_key = os.urandom(24)
    app.config["SESSION_TYPE"] = "filesystem"  # Or "postgresql", "redis", etc.  For production, use a proper database.
    #app.config["SESSION_TYPE"] = "postgresql"
    #app.config["SESSION_PERMANENT"] = True  # Optional: Make sessions permanent
    #app.config["SESSION_SQLALCHEMY"] = {'url': DATABASE_URL}  #needs  Flask-SQLAlchemy
    Session(app)

    # --- Database Connection (PostgreSQL) ---
    def get_db_connection():
        """Gets a PostgreSQL connection."""
        try:
            conn = psycopg2.connect(DATABASE_URL)
            return conn
        except psycopg2.Error as e:
            print(f"Database connection error: {e}")
            return None

    def create_table():
        """Creates the messages table if it doesn't exist."""
        conn = get_db_connection()
        if conn is not None:
            try:
                cursor = conn.cursor()
                cursor.execute("""
                    CREATE TABLE IF NOT EXISTS messages (
                        id SERIAL PRIMARY KEY,
                        session_id TEXT NOT NULL,
                        role TEXT NOT NULL,
                        content TEXT NOT NULL,
                        created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
                    )
                """)
                conn.commit()
            except psycopg2.Error as e:
                print(f"Error creating table: {e}")
            finally:
                conn.close()

    def store_message(session_id: str, role: str, content: str):
        """Stores a message in the database."""
        conn = get_db_connection()
        if conn is not None:
            try:
                cursor = conn.cursor()
                cursor.execute(
                    "INSERT INTO messages (session_id, role, content) VALUES (%s, %s, %s)",
                    (session_id, role, content),
                )
                conn.commit()
            except psycopg2.Error as e:
                print(f"Error storing message: {e}")
            finally:
                conn.close()

    def get_history_from_db(session_id: str) -> List[Dict[str, str]]:
        """Retrieves the conversation history for a given session ID from the database."""
        conn = get_db_connection()
        if conn is not None:
            try:
                cursor = conn.cursor()
                cursor.execute(
                    "SELECT role, content FROM messages WHERE session_id = %s ORDER BY created_at",
                    (session_id,),
                )
                history = [{"role": row[0], "content": row[1]} for row in cursor.fetchall()]
                return history
            except psycopg2.Error as e:
                print(f"Error retrieving history from database: {e}")
                return []
            finally:
                conn.close()
        return []


    create_table()  # Ensure the table exists

    # --- Chat route ---
    @app.route("/", methods=["GET", "POST"])
    def chat():
        """Handles the main chat functionality."""
        if "session_id" not in session:
            session["session_id"] = os.urandom(16).hex()  # Generate unique session ID

        session_id = session["session_id"]
        history = get_history_from_db(session_id)  # Get history from the database

        if not history:
            history = [{"role": "system", "content": "You are a helpful assistant."}]
            session["history"] = history # Initialize session

        if request.method == "POST":
            user_input = request.form["user_input"]
            store_message(session_id, "user", user_input)  # Store to database
            history.append({"role": "user", "content": user_input})

            try:
                response = openai.ChatCompletion.create(
                    model="gpt-4o",
                    messages=history,
                    temperature=0.6,
                )
                assistant_reply = response.choices[0].message.content
                store_message(session_id, "assistant", assistant_reply)  # Store to database
                history.append({"role": "assistant", "content": assistant_reply})
                session["history"] = history
                session.modified = True # IMPORTANT
                return render_template("chat.html", history=history[1:])  # Pass history to template
            except openai.error.OpenAIError as e:
                error_message = f"OpenAI API Error: {e}"
                print(error_message)
                store_message(session_id, "assistant", error_message)
                history.append({"role": "assistant", "content": error_message})
                session["history"] = history
                session.modified = True
                return render_template("chat.html", history=history[1:])
            except Exception as e:
                error_message = f"An unexpected error occurred: {e}"
                print(error_message)
                store_message(session_id, "assistant", error_message)
                history.append({"role": "assistant", "content": error_message})
                session["history"] = history
                session.modified = True
                return render_template("chat.html", history=history[1:])

        return render_template("chat.html", history=history[1:])
    @app.route("/clear", methods=["POST"])
    def clear_chat():
        session.pop("session_id", None)
        session.pop("history", None)
        return redirect(url_for("chat"))

    if __name__ == "__main__":
        app.run(debug=True)

    Code breakdown:

    • from flask import ...: Imports the necessary Flask modules.
    • import openai: Imports the OpenAI library.
    • import os: Imports the os module for interacting with the operating system, such as accessing environment variables.
    • from dotenv import load_dotenv: Imports the load_dotenv function to load environment variables from a .env file.
    • from flask_session import Session: Imports the Session class from flask_session.
    • import psycopg2: Imports the psycopg2 library for interacting with PostgreSQL.
    • load_dotenv(): Loads the environment variables from a .env file.
    • openai.api_key = os.getenv("OPENAI_API_KEY"): Retrieves the OpenAI API key from the environment variable and sets it for the OpenAI library.
    • DATABASE_URL = os.getenv("DATABASE_URL"): Retrieves the PostgreSQL database URL from the environment variable.
    • app = Flask(__name__): Creates a Flask application instance.
    • app.secret_key = os.urandom(24): Sets a secret key for the Flask application. This is essential for using Flask sessions.
    • app.config["SESSION_TYPE"] = "filesystem|postgresql|redis|mongodb": Configures the session storage type. For this project, we are using a database.
    • Session(app): Initializes the Flask-Session extension, binding it to the Flask app.
    • get_db_connection():
      • Establishes a connection to the PostgreSQL database using the URL from the environment variable.
      • Returns the connection object.
      • Handles potential connection errors with a try...except block.
    • create_table():
      • Creates the "messages" table in the PostgreSQL database if it doesn't exist. The table stores the conversation history, including a unique session_id for each conversation.
    • store_message(session_id, role, content):
      • Stores a message (user or assistant) in the "messages" table, including the session_id.
    • get_history_from_db(session_id):
      • Retrieves the conversation history for a given session_id from the database.
    • @app.route("/", methods=["GET", "POST"]): Defines the route for the application's main page ("/"). The chat() function handles both GET and POST requests to this URL.
    • def chat():
      • if "session_id" not in session:: Checks if a session_id exists in the user's session. If not, it generates a unique ID using os.urandom(16).hex() and stores it in the session.
      • session_id = session["session_id"]: Retrieves the session_id from the session.
      • history = get_history_from_db(session_id): Retrieves the conversation history from the database using the session ID.
      • if not history: checks if the history is empty and initializes it.
      • if request.method == "POST":: Handles POST requests (when the user submits a message).
        • user_input = request.form["user_input"]: Gets the user's input from the form.
        • store_message(session_id, "user", user_input): Stores the user's message in the database, associated with the session ID.
        • The code then calls the OpenAI API to get a response, stores the response in the database, and updates the session.
        • Renders the "chat.html" template, passing the conversation history.
      • return render_template("chat.html", history=history[1:]): Handles GET requests (when the user loads the page). Renders the "chat.html" template, passing the conversation history.
    • @app.route("/clear", methods=["POST"]): Route for clearing the chat session.
    • def clear_chat():: clears the session_id and history from the session.
    • if __name__ == "__main__":: Starts the Flask development server if the script is executed directly.
  • Create a folder named templates in the same directory as app.py.
  • Create a file named chat.html inside the templates folder with the following code:
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>GPT-4o Chat Assistant</title>
        <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
        <style>
            /* --- General Styles --- */
            body {
                font-family: 'Inter', sans-serif;
                background-color: #f3f4f6;
                padding: 20px;
                display: flex;
                justify-content: center;
                align-items: center;
                min-height: 100vh;
                margin: 0;
                color: #1f2937;
            }
            .container {
                max-width: 800px;
                width: 100%;
                background-color: #fff;
                padding: 30px;
                border-radius: 0.75rem;
                box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
            }
            h2 {
                font-size: 1.875rem;
                font-weight: 600;
                margin-bottom: 2rem;
                color: #1e293b;
                text-align: center;
            }
            /* --- Message Styles --- */
            .message {
                margin-bottom: 1.5rem;
                display: flex;
                flex-direction: column;
            }
            .message p {
                padding: 1rem;
                border-radius: 1rem;
                max-width: 80%;
            }
            .user p {
                background-color: #e0f2fe;
                color: #1d4ed8;
                margin-left: auto;
            }
            .assistant p {
                background-color: #f0fdf4;
                color: #15803d;
            }
            .message strong {
                font-size: 0.875rem;
                font-weight: 600;
                margin-bottom: 0.25rem;
            }
            .user strong {
                color: #1e40af;
            }
            .assistant strong {
                color: #16a34a;
            }
            /* --- Form Styles --- */
            form {
                margin-top: 2rem;
                display: flex;
                flex-direction: column;
                gap: 0.5rem;
            }
            textarea {
                width: 100%;
                padding: 0.75rem;
                border-radius: 0.5rem;
                border: 1px solid #d1d5db;
                resize: none;
                font-size: 1rem;
                line-height: 1.5rem;
                margin-bottom: 0.25rem;
                box-shadow: inset 0 2px 4px rgba(0,0,0,0.06);
            }
            textarea:focus {
                outline: none;
                border-color: #3b82f6;
                box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15);
            }
            input[type="submit"] {
                padding: 0.75rem 1.5rem;
                border-radius: 0.5rem;
                background-color: #3b82f6;
                color: #fff;
                font-size: 1rem;
                font-weight: 600;
                cursor: pointer;
                transition: background-color 0.3s ease;
                border: none;
                box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1);
            }
            input[type="submit"]:hover {
                background-color: #2563eb;
            }
            input[type="submit"]:focus {
                outline: none;
                box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.3);
            }
            /* --- Responsive Adjustments --- */
            @media (max-width: 768px) {
                .container {
                    padding: 20px;
                }
                textarea {
                    height: 100px;
                }
            }
            .clear-history-button{
                margin-top: 2rem;
                padding: 0.75rem 1.5rem;
                border-radius: 0.5rem;
                background-color: #3b82f6;
                color: #fff;
                font-size: 1rem;
                font-weight: 600;
                cursor: pointer;
                transition: background-color 0.3s ease;
                border: none;
                box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1);
            }
        </style>
    </head>
    <body>
        <div class="container">
            <h2>🧠 GPT-4o Chat Assistant</h2>
            {% for msg in history %}
                <div class="message {{ msg.role }}">
                    <p><strong>{{ msg.role.capitalize() }}:</strong> {{ msg.content }}</p>
                </div>
            {% endfor %}
            <form method="post">
                <textarea name="user_input" placeholder="Type your message..."></textarea><br>
                <input type="submit" value="Send Message">
            </form>
            <form method="post" action="/clear">
                <input type="submit" value="Clear Chat" class = "clear-history-button">
            </form>
        </div>
    </body>
    </html>
  • This HTML template provides the structure and styling for the chat interface. It includes a form for user input and displays the conversation history.

3.  Streamlit Implementation

Create a file named streamlit_app.py with the following code:

import streamlit as st
import openai
import os
from dotenv import load_dotenv
import psycopg2  # Import psycopg2
import datetime
from typing import List, Dict

load_dotenv()
openai.api_key = os.getenv("OPENAI_API_KEY")
DATABASE_URL = os.getenv("DATABASE_URL")

st.set_page_config(page_title="GPT-4o Chat with Memory", page_icon="🧠")
st.title("🧠 GPT-4o Chatbot with Persistent Memory")

# --- Database Connection (PostgreSQL) ---
def get_db_connection():
    """Gets a PostgreSQL connection."""
    try:
        conn = psycopg2.connect(DATABASE_URL)
        return conn
    except psycopg2.Error as e:
        st.error(f"Database connection error: {e}")
        return None

def create_table():
    """Creates the messages table if it doesn't exist."""
    conn = get_db_connection()
    if conn is not None:
        try:
            cursor = conn.cursor()
            cursor.execute("""
                CREATE TABLE IF NOT EXISTS messages (
                    id SERIAL PRIMARY KEY,
                    session_id TEXT NOT NULL,
                    role TEXT NOT NULL,
                    content TEXT NOT NULL,
                    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
                )
            """)
            conn.commit()
        except psycopg2.Error as e:
            st.error(f"Error creating table: {e}")
        finally:
            conn.close()

def store_message(session_id: str, role: str, content: str):
    """Stores a message in the database."""
    conn = get_db_connection()
    if conn is not None:
        try:
            cursor = conn.cursor()
            cursor.execute(
                "INSERT INTO messages (session_id, role, content) VALUES (%s, %s, %s)",
                (session_id, role, content),
            )
            conn.commit()
        except psycopg2.Error as e:
            st.error(f"Error storing message: {e}")
        finally:
            conn.close()

def get_history_from_db(session_id: str) -> List[Dict[str, str]]:
    """Retrieves the conversation history for a given session ID from the database."""
    conn = get_db_connection()
    if conn is not None:
        try:
            cursor = conn.cursor()
            cursor.execute(
                "SELECT role, content FROM messages WHERE session_id = %s ORDER BY created_at",
                (session_id,),
            )
            history = [{"role": row[0], "content": row[1]} for row in cursor.fetchall()]
            return history
        except psycopg2.Error as e:
            st.error(f"Error retrieving history from database: {e}")
            return []
        finally:
            conn.close()
    return []

create_table()  # Ensure the table exists

# --- Session Management ---
if "session_id" not in st.session_state:
    st.session_state.session_id = os.urandom(16).hex()  # Generate unique session ID

session_id = st.session_state.session_id

# --- Initialize chat history ---
if "messages" not in st.session_state:
    st.session_state.messages = get_history_from_db(session_id)
    if not st.session_state.messages:
        st.session_state.messages = [{"role": "system", "content": "You are a helpful assistant."}]

# --- Display chat history ---
for msg in st.session_state.messages[1:]:  # Skip system message
    speaker = "🧑‍💻 You" if msg["role"] == "user" else "🤖 Assistant"
    with st.chat_message(msg["role"]):
        st.markdown(f"**{speaker}:** {msg['content']}")

# --- User input ---
user_input = st.chat_input("Ask something...", key="user_input")  # Use key for chat_input

if user_input:
    # Store user message
    store_message(session_id, "user", user_input)
    st.session_state.messages.append({"role": "user", "content": user_input})

    # Get assistant reply
    with st.chat_message("assistant"):
        placeholder = st.empty()  # Reserve a placeholder for the response
        with st.spinner("Thinking..."):
            try:
                response = openai.ChatCompletion.create(
                    model="gpt-4o",
                    messages=st.session_state.messages,
                    temperature=0.6,
                )
                reply = response["choices"][0]["message"]["content"]
                store_message(session_id, "assistant", reply)  # Store assistant reply
                st.session_state.messages.append({"role": "assistant", "content": reply})
                placeholder.markdown(f"**🤖 Assistant:** {reply}")  # Replace placeholder with full response
            except openai.error.OpenAIError as e:
                error_message = f"OpenAI API Error: {e}"
                store_message(session_id, "assistant", error_message)
                st.session_state.messages.append({"role": "assistant", "content": error_message})
                st.error(error_message)
                placeholder.markdown(f"**🤖 Assistant:** {error_message}")
            except Exception as e:
                error_message = f"An unexpected error occurred: {e}"
                store_message(session_id, "assistant", error_message)
                st.session_state.messages.append({"role": "assistant", "content": error_message})
                st.error(error_message)
                placeholder.markdown(f"**🤖 Assistant:** {error_message}")
    # Clear the input field after sending the message
    st.session_state.user_input = ""
    st.rerun()

Code brakdown:

  • import streamlit as st: Imports the Streamlit library.
  • import openai: Imports the OpenAI library.
  • import os: Imports the os module for interacting with the operating system, such as accessing environment variables.
  • from dotenv import load_dotenv: Imports the load_dotenv function to load environment variables from a .env file.
  • import psycopg2: Imports the psycopg2 library for interacting with the PostgreSQL database.
  • import datetime: Imports the datetime module.
  • from typing import List, Dict: Imports typing
  • load_dotenv(): Loads the environment variables from a .env file.
  • openai.api_key = os.getenv("OPENAI_API_KEY"): Retrieves the OpenAI API key from the environment variable and sets it for the OpenAI library.
  • DATABASE_URL = os.getenv("DATABASE_URL"): Retrieves the PostgreSQL database URL.
  • st.set_page_config(...): Configures the page title and icon.
  • st.title(...): Sets the title of the Streamlit application.
  • get_db_connection():
    • Establishes a connection to the PostgreSQL database using the URL from the environment variable.
    • Returns the connection object.
    • Handles potential connection errors using a try...except block, displaying an error message using st.error().
  • create_table():
    • Creates the "messages" table in the PostgreSQL database if it doesn't exist. The table stores the conversation history, including a unique session_id for each conversation.
    • Handles potential errors during table creation using a try...except block.
  • store_message(session_id, role, content):
    • Stores a message (user or assistant) in the "messages" table, along with the session_id.
    • Handles potential errors during message storage.
  • get_history_from_db(session_id):
    • Retrieves the conversation history for a specific session_id from the database.
  • The main part of the code:
    • if "session_id" not in st.session_state:: Checks if a session ID exists in Streamlit's session state. If not, it generates a unique ID using os.urandom(16).hex().
    • session_id = st.session_state.session_id: Retrieves the session ID.
    • if "messages" not in st.session_state::
      • Retrieves the conversation history from the database.
      • If no history exists in the database for the current session, it initializes the chat history with the system message.
    • The code then displays the chat history.
    • user_input = st.chat_input(...): Creates a text input field for the user to type their message.
    • When the user submits a message:
      • The message is stored in the database using the store_message() function.
      • The message is added to the chat history in st.session_state.
      • The application calls the OpenAI API to get a response.
      • The assistant's response is stored in the database and added to the chat history.
      • The assistant's response is displayed in the chat interface.

4. Run the Applications

  • Flask:
    • Open a terminal, navigate to the project directory, and make sure your virtual environment is activated.
    • Run the Flask application:
      python app.py
    • Open a web browser and go to http://localhost:5000 to interact with the chatbot.
  • Streamlit:
    • Open a terminal, navigate to the project directory, and make sure your virtual environment is activated.
    • Run the Streamlit application:
      streamlit run streamlit_app.py
    • A new browser tab will open with the Streamlit chatbot.

5.  Enhancements (Optional)

  • Implement user authentication:
    • Add secure login/registration system using JWT or session tokens
    • Create user profiles to store conversation preferences
    • Enable private conversations tied to specific user accounts
  • Add a "Clear Chat" button feature:
    • Implement both complete and selective chat history clearing
    • Add confirmation dialogs to prevent accidental deletions
    • Include option to export chat history before clearing
  • Enhance the front-end interface:
    • Create responsive layouts using modern CSS frameworks
    • Add real-time message updates using WebSocket
    • Implement markdown support and code highlighting
  • Deploy to production:
    • Set up CI/CD pipelines for automated deployment
    • Configure SSL certificates for secure communication
    • Implement monitoring and logging systems
  • Add vector database capabilities:
    • Implement embedding generation using models like BERT or USE
    • Set up vector similarity search using Pinecone or Milvus
    • Create semantic search functionality for chat history

This project serves as an extensive foundation for developing a chatbot with memory capabilities. Here's what you'll gain from completing it:

First, you'll get practical experience with modern web frameworks - both Flask for traditional web development and Streamlit for rapid prototyping of data applications. You'll learn how these frameworks differ and when to use each one.

Second, you'll master working with the OpenAI API, understanding how to structure prompts, handle responses, and manage API interactions effectively. This knowledge is crucial for building any AI-powered application.

Third, you'll learn database integration principles, including how to design schemas, manage connections, and handle data persistence. This ensures your chatbot can maintain conversations across sessions and scale effectively.

Finally, this project will prepare you for building more sophisticated chatbot applications, such as those incorporating advanced features like sentiment analysis, multiple AI models, or real-time processing. The skills you develop here form the building blocks for more complex AI applications.

Project Steps

1.  Project Setup

  • Create a new directory for your project:
    mkdir chatbot_project
    cd chatbot_project
  • Create a virtual environment:
    python -m venv venv
  • Activate the virtual environment:
  • On Windows:
    venv\Scripts\activate
  • On macOS and Linux:
    source venv/bin/activate
  • Install the required libraries:
    pip install flask openai flask-session python-dotenv streamlit psycopg2
  • Create a .env file in your project directory and add your OpenAI API key and database url:
    OPENAI_API_KEY=YOUR_OPENAI_API_KEY
    DATABASE_URL=YOUR_DATABASE_URL # e.g., "postgresql://user:password@host:port/database_name"
  • For the postgres database, make sure you have a postgres database running and the url is correctly configured.

2.  Flask Implementation

  • Create a file named app.py with the following code:
    from flask import Flask, request, render_template, session, redirect, url_for
    import openai
    import os
    from dotenv import load_dotenv
    from flask_session import Session
    import psycopg2 #for postgres

    load_dotenv()
    openai.api_key = os.getenv("OPENAI_API_KEY")
    DATABASE_URL = os.getenv("DATABASE_URL")

    app = Flask(__name__)
    app.secret_key = os.urandom(24)
    app.config["SESSION_TYPE"] = "filesystem"  # Or "postgresql", "redis", etc.  For production, use a proper database.
    #app.config["SESSION_TYPE"] = "postgresql"
    #app.config["SESSION_PERMANENT"] = True  # Optional: Make sessions permanent
    #app.config["SESSION_SQLALCHEMY"] = {'url': DATABASE_URL}  #needs  Flask-SQLAlchemy
    Session(app)

    # --- Database Connection (PostgreSQL) ---
    def get_db_connection():
        """Gets a PostgreSQL connection."""
        try:
            conn = psycopg2.connect(DATABASE_URL)
            return conn
        except psycopg2.Error as e:
            print(f"Database connection error: {e}")
            return None

    def create_table():
        """Creates the messages table if it doesn't exist."""
        conn = get_db_connection()
        if conn is not None:
            try:
                cursor = conn.cursor()
                cursor.execute("""
                    CREATE TABLE IF NOT EXISTS messages (
                        id SERIAL PRIMARY KEY,
                        session_id TEXT NOT NULL,
                        role TEXT NOT NULL,
                        content TEXT NOT NULL,
                        created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
                    )
                """)
                conn.commit()
            except psycopg2.Error as e:
                print(f"Error creating table: {e}")
            finally:
                conn.close()

    def store_message(session_id: str, role: str, content: str):
        """Stores a message in the database."""
        conn = get_db_connection()
        if conn is not None:
            try:
                cursor = conn.cursor()
                cursor.execute(
                    "INSERT INTO messages (session_id, role, content) VALUES (%s, %s, %s)",
                    (session_id, role, content),
                )
                conn.commit()
            except psycopg2.Error as e:
                print(f"Error storing message: {e}")
            finally:
                conn.close()

    def get_history_from_db(session_id: str) -> List[Dict[str, str]]:
        """Retrieves the conversation history for a given session ID from the database."""
        conn = get_db_connection()
        if conn is not None:
            try:
                cursor = conn.cursor()
                cursor.execute(
                    "SELECT role, content FROM messages WHERE session_id = %s ORDER BY created_at",
                    (session_id,),
                )
                history = [{"role": row[0], "content": row[1]} for row in cursor.fetchall()]
                return history
            except psycopg2.Error as e:
                print(f"Error retrieving history from database: {e}")
                return []
            finally:
                conn.close()
        return []


    create_table()  # Ensure the table exists

    # --- Chat route ---
    @app.route("/", methods=["GET", "POST"])
    def chat():
        """Handles the main chat functionality."""
        if "session_id" not in session:
            session["session_id"] = os.urandom(16).hex()  # Generate unique session ID

        session_id = session["session_id"]
        history = get_history_from_db(session_id)  # Get history from the database

        if not history:
            history = [{"role": "system", "content": "You are a helpful assistant."}]
            session["history"] = history # Initialize session

        if request.method == "POST":
            user_input = request.form["user_input"]
            store_message(session_id, "user", user_input)  # Store to database
            history.append({"role": "user", "content": user_input})

            try:
                response = openai.ChatCompletion.create(
                    model="gpt-4o",
                    messages=history,
                    temperature=0.6,
                )
                assistant_reply = response.choices[0].message.content
                store_message(session_id, "assistant", assistant_reply)  # Store to database
                history.append({"role": "assistant", "content": assistant_reply})
                session["history"] = history
                session.modified = True # IMPORTANT
                return render_template("chat.html", history=history[1:])  # Pass history to template
            except openai.error.OpenAIError as e:
                error_message = f"OpenAI API Error: {e}"
                print(error_message)
                store_message(session_id, "assistant", error_message)
                history.append({"role": "assistant", "content": error_message})
                session["history"] = history
                session.modified = True
                return render_template("chat.html", history=history[1:])
            except Exception as e:
                error_message = f"An unexpected error occurred: {e}"
                print(error_message)
                store_message(session_id, "assistant", error_message)
                history.append({"role": "assistant", "content": error_message})
                session["history"] = history
                session.modified = True
                return render_template("chat.html", history=history[1:])

        return render_template("chat.html", history=history[1:])
    @app.route("/clear", methods=["POST"])
    def clear_chat():
        session.pop("session_id", None)
        session.pop("history", None)
        return redirect(url_for("chat"))

    if __name__ == "__main__":
        app.run(debug=True)

    Code breakdown:

    • from flask import ...: Imports the necessary Flask modules.
    • import openai: Imports the OpenAI library.
    • import os: Imports the os module for interacting with the operating system, such as accessing environment variables.
    • from dotenv import load_dotenv: Imports the load_dotenv function to load environment variables from a .env file.
    • from flask_session import Session: Imports the Session class from flask_session.
    • import psycopg2: Imports the psycopg2 library for interacting with PostgreSQL.
    • load_dotenv(): Loads the environment variables from a .env file.
    • openai.api_key = os.getenv("OPENAI_API_KEY"): Retrieves the OpenAI API key from the environment variable and sets it for the OpenAI library.
    • DATABASE_URL = os.getenv("DATABASE_URL"): Retrieves the PostgreSQL database URL from the environment variable.
    • app = Flask(__name__): Creates a Flask application instance.
    • app.secret_key = os.urandom(24): Sets a secret key for the Flask application. This is essential for using Flask sessions.
    • app.config["SESSION_TYPE"] = "filesystem|postgresql|redis|mongodb": Configures the session storage type. For this project, we are using a database.
    • Session(app): Initializes the Flask-Session extension, binding it to the Flask app.
    • get_db_connection():
      • Establishes a connection to the PostgreSQL database using the URL from the environment variable.
      • Returns the connection object.
      • Handles potential connection errors with a try...except block.
    • create_table():
      • Creates the "messages" table in the PostgreSQL database if it doesn't exist. The table stores the conversation history, including a unique session_id for each conversation.
    • store_message(session_id, role, content):
      • Stores a message (user or assistant) in the "messages" table, including the session_id.
    • get_history_from_db(session_id):
      • Retrieves the conversation history for a given session_id from the database.
    • @app.route("/", methods=["GET", "POST"]): Defines the route for the application's main page ("/"). The chat() function handles both GET and POST requests to this URL.
    • def chat():
      • if "session_id" not in session:: Checks if a session_id exists in the user's session. If not, it generates a unique ID using os.urandom(16).hex() and stores it in the session.
      • session_id = session["session_id"]: Retrieves the session_id from the session.
      • history = get_history_from_db(session_id): Retrieves the conversation history from the database using the session ID.
      • if not history: checks if the history is empty and initializes it.
      • if request.method == "POST":: Handles POST requests (when the user submits a message).
        • user_input = request.form["user_input"]: Gets the user's input from the form.
        • store_message(session_id, "user", user_input): Stores the user's message in the database, associated with the session ID.
        • The code then calls the OpenAI API to get a response, stores the response in the database, and updates the session.
        • Renders the "chat.html" template, passing the conversation history.
      • return render_template("chat.html", history=history[1:]): Handles GET requests (when the user loads the page). Renders the "chat.html" template, passing the conversation history.
    • @app.route("/clear", methods=["POST"]): Route for clearing the chat session.
    • def clear_chat():: clears the session_id and history from the session.
    • if __name__ == "__main__":: Starts the Flask development server if the script is executed directly.
  • Create a folder named templates in the same directory as app.py.
  • Create a file named chat.html inside the templates folder with the following code:
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>GPT-4o Chat Assistant</title>
        <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
        <style>
            /* --- General Styles --- */
            body {
                font-family: 'Inter', sans-serif;
                background-color: #f3f4f6;
                padding: 20px;
                display: flex;
                justify-content: center;
                align-items: center;
                min-height: 100vh;
                margin: 0;
                color: #1f2937;
            }
            .container {
                max-width: 800px;
                width: 100%;
                background-color: #fff;
                padding: 30px;
                border-radius: 0.75rem;
                box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
            }
            h2 {
                font-size: 1.875rem;
                font-weight: 600;
                margin-bottom: 2rem;
                color: #1e293b;
                text-align: center;
            }
            /* --- Message Styles --- */
            .message {
                margin-bottom: 1.5rem;
                display: flex;
                flex-direction: column;
            }
            .message p {
                padding: 1rem;
                border-radius: 1rem;
                max-width: 80%;
            }
            .user p {
                background-color: #e0f2fe;
                color: #1d4ed8;
                margin-left: auto;
            }
            .assistant p {
                background-color: #f0fdf4;
                color: #15803d;
            }
            .message strong {
                font-size: 0.875rem;
                font-weight: 600;
                margin-bottom: 0.25rem;
            }
            .user strong {
                color: #1e40af;
            }
            .assistant strong {
                color: #16a34a;
            }
            /* --- Form Styles --- */
            form {
                margin-top: 2rem;
                display: flex;
                flex-direction: column;
                gap: 0.5rem;
            }
            textarea {
                width: 100%;
                padding: 0.75rem;
                border-radius: 0.5rem;
                border: 1px solid #d1d5db;
                resize: none;
                font-size: 1rem;
                line-height: 1.5rem;
                margin-bottom: 0.25rem;
                box-shadow: inset 0 2px 4px rgba(0,0,0,0.06);
            }
            textarea:focus {
                outline: none;
                border-color: #3b82f6;
                box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15);
            }
            input[type="submit"] {
                padding: 0.75rem 1.5rem;
                border-radius: 0.5rem;
                background-color: #3b82f6;
                color: #fff;
                font-size: 1rem;
                font-weight: 600;
                cursor: pointer;
                transition: background-color 0.3s ease;
                border: none;
                box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1);
            }
            input[type="submit"]:hover {
                background-color: #2563eb;
            }
            input[type="submit"]:focus {
                outline: none;
                box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.3);
            }
            /* --- Responsive Adjustments --- */
            @media (max-width: 768px) {
                .container {
                    padding: 20px;
                }
                textarea {
                    height: 100px;
                }
            }
            .clear-history-button{
                margin-top: 2rem;
                padding: 0.75rem 1.5rem;
                border-radius: 0.5rem;
                background-color: #3b82f6;
                color: #fff;
                font-size: 1rem;
                font-weight: 600;
                cursor: pointer;
                transition: background-color 0.3s ease;
                border: none;
                box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1);
            }
        </style>
    </head>
    <body>
        <div class="container">
            <h2>🧠 GPT-4o Chat Assistant</h2>
            {% for msg in history %}
                <div class="message {{ msg.role }}">
                    <p><strong>{{ msg.role.capitalize() }}:</strong> {{ msg.content }}</p>
                </div>
            {% endfor %}
            <form method="post">
                <textarea name="user_input" placeholder="Type your message..."></textarea><br>
                <input type="submit" value="Send Message">
            </form>
            <form method="post" action="/clear">
                <input type="submit" value="Clear Chat" class = "clear-history-button">
            </form>
        </div>
    </body>
    </html>
  • This HTML template provides the structure and styling for the chat interface. It includes a form for user input and displays the conversation history.

3.  Streamlit Implementation

Create a file named streamlit_app.py with the following code:

import streamlit as st
import openai
import os
from dotenv import load_dotenv
import psycopg2  # Import psycopg2
import datetime
from typing import List, Dict

load_dotenv()
openai.api_key = os.getenv("OPENAI_API_KEY")
DATABASE_URL = os.getenv("DATABASE_URL")

st.set_page_config(page_title="GPT-4o Chat with Memory", page_icon="🧠")
st.title("🧠 GPT-4o Chatbot with Persistent Memory")

# --- Database Connection (PostgreSQL) ---
def get_db_connection():
    """Gets a PostgreSQL connection."""
    try:
        conn = psycopg2.connect(DATABASE_URL)
        return conn
    except psycopg2.Error as e:
        st.error(f"Database connection error: {e}")
        return None

def create_table():
    """Creates the messages table if it doesn't exist."""
    conn = get_db_connection()
    if conn is not None:
        try:
            cursor = conn.cursor()
            cursor.execute("""
                CREATE TABLE IF NOT EXISTS messages (
                    id SERIAL PRIMARY KEY,
                    session_id TEXT NOT NULL,
                    role TEXT NOT NULL,
                    content TEXT NOT NULL,
                    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
                )
            """)
            conn.commit()
        except psycopg2.Error as e:
            st.error(f"Error creating table: {e}")
        finally:
            conn.close()

def store_message(session_id: str, role: str, content: str):
    """Stores a message in the database."""
    conn = get_db_connection()
    if conn is not None:
        try:
            cursor = conn.cursor()
            cursor.execute(
                "INSERT INTO messages (session_id, role, content) VALUES (%s, %s, %s)",
                (session_id, role, content),
            )
            conn.commit()
        except psycopg2.Error as e:
            st.error(f"Error storing message: {e}")
        finally:
            conn.close()

def get_history_from_db(session_id: str) -> List[Dict[str, str]]:
    """Retrieves the conversation history for a given session ID from the database."""
    conn = get_db_connection()
    if conn is not None:
        try:
            cursor = conn.cursor()
            cursor.execute(
                "SELECT role, content FROM messages WHERE session_id = %s ORDER BY created_at",
                (session_id,),
            )
            history = [{"role": row[0], "content": row[1]} for row in cursor.fetchall()]
            return history
        except psycopg2.Error as e:
            st.error(f"Error retrieving history from database: {e}")
            return []
        finally:
            conn.close()
    return []

create_table()  # Ensure the table exists

# --- Session Management ---
if "session_id" not in st.session_state:
    st.session_state.session_id = os.urandom(16).hex()  # Generate unique session ID

session_id = st.session_state.session_id

# --- Initialize chat history ---
if "messages" not in st.session_state:
    st.session_state.messages = get_history_from_db(session_id)
    if not st.session_state.messages:
        st.session_state.messages = [{"role": "system", "content": "You are a helpful assistant."}]

# --- Display chat history ---
for msg in st.session_state.messages[1:]:  # Skip system message
    speaker = "🧑‍💻 You" if msg["role"] == "user" else "🤖 Assistant"
    with st.chat_message(msg["role"]):
        st.markdown(f"**{speaker}:** {msg['content']}")

# --- User input ---
user_input = st.chat_input("Ask something...", key="user_input")  # Use key for chat_input

if user_input:
    # Store user message
    store_message(session_id, "user", user_input)
    st.session_state.messages.append({"role": "user", "content": user_input})

    # Get assistant reply
    with st.chat_message("assistant"):
        placeholder = st.empty()  # Reserve a placeholder for the response
        with st.spinner("Thinking..."):
            try:
                response = openai.ChatCompletion.create(
                    model="gpt-4o",
                    messages=st.session_state.messages,
                    temperature=0.6,
                )
                reply = response["choices"][0]["message"]["content"]
                store_message(session_id, "assistant", reply)  # Store assistant reply
                st.session_state.messages.append({"role": "assistant", "content": reply})
                placeholder.markdown(f"**🤖 Assistant:** {reply}")  # Replace placeholder with full response
            except openai.error.OpenAIError as e:
                error_message = f"OpenAI API Error: {e}"
                store_message(session_id, "assistant", error_message)
                st.session_state.messages.append({"role": "assistant", "content": error_message})
                st.error(error_message)
                placeholder.markdown(f"**🤖 Assistant:** {error_message}")
            except Exception as e:
                error_message = f"An unexpected error occurred: {e}"
                store_message(session_id, "assistant", error_message)
                st.session_state.messages.append({"role": "assistant", "content": error_message})
                st.error(error_message)
                placeholder.markdown(f"**🤖 Assistant:** {error_message}")
    # Clear the input field after sending the message
    st.session_state.user_input = ""
    st.rerun()

Code brakdown:

  • import streamlit as st: Imports the Streamlit library.
  • import openai: Imports the OpenAI library.
  • import os: Imports the os module for interacting with the operating system, such as accessing environment variables.
  • from dotenv import load_dotenv: Imports the load_dotenv function to load environment variables from a .env file.
  • import psycopg2: Imports the psycopg2 library for interacting with the PostgreSQL database.
  • import datetime: Imports the datetime module.
  • from typing import List, Dict: Imports typing
  • load_dotenv(): Loads the environment variables from a .env file.
  • openai.api_key = os.getenv("OPENAI_API_KEY"): Retrieves the OpenAI API key from the environment variable and sets it for the OpenAI library.
  • DATABASE_URL = os.getenv("DATABASE_URL"): Retrieves the PostgreSQL database URL.
  • st.set_page_config(...): Configures the page title and icon.
  • st.title(...): Sets the title of the Streamlit application.
  • get_db_connection():
    • Establishes a connection to the PostgreSQL database using the URL from the environment variable.
    • Returns the connection object.
    • Handles potential connection errors using a try...except block, displaying an error message using st.error().
  • create_table():
    • Creates the "messages" table in the PostgreSQL database if it doesn't exist. The table stores the conversation history, including a unique session_id for each conversation.
    • Handles potential errors during table creation using a try...except block.
  • store_message(session_id, role, content):
    • Stores a message (user or assistant) in the "messages" table, along with the session_id.
    • Handles potential errors during message storage.
  • get_history_from_db(session_id):
    • Retrieves the conversation history for a specific session_id from the database.
  • The main part of the code:
    • if "session_id" not in st.session_state:: Checks if a session ID exists in Streamlit's session state. If not, it generates a unique ID using os.urandom(16).hex().
    • session_id = st.session_state.session_id: Retrieves the session ID.
    • if "messages" not in st.session_state::
      • Retrieves the conversation history from the database.
      • If no history exists in the database for the current session, it initializes the chat history with the system message.
    • The code then displays the chat history.
    • user_input = st.chat_input(...): Creates a text input field for the user to type their message.
    • When the user submits a message:
      • The message is stored in the database using the store_message() function.
      • The message is added to the chat history in st.session_state.
      • The application calls the OpenAI API to get a response.
      • The assistant's response is stored in the database and added to the chat history.
      • The assistant's response is displayed in the chat interface.

4. Run the Applications

  • Flask:
    • Open a terminal, navigate to the project directory, and make sure your virtual environment is activated.
    • Run the Flask application:
      python app.py
    • Open a web browser and go to http://localhost:5000 to interact with the chatbot.
  • Streamlit:
    • Open a terminal, navigate to the project directory, and make sure your virtual environment is activated.
    • Run the Streamlit application:
      streamlit run streamlit_app.py
    • A new browser tab will open with the Streamlit chatbot.

5.  Enhancements (Optional)

  • Implement user authentication:
    • Add secure login/registration system using JWT or session tokens
    • Create user profiles to store conversation preferences
    • Enable private conversations tied to specific user accounts
  • Add a "Clear Chat" button feature:
    • Implement both complete and selective chat history clearing
    • Add confirmation dialogs to prevent accidental deletions
    • Include option to export chat history before clearing
  • Enhance the front-end interface:
    • Create responsive layouts using modern CSS frameworks
    • Add real-time message updates using WebSocket
    • Implement markdown support and code highlighting
  • Deploy to production:
    • Set up CI/CD pipelines for automated deployment
    • Configure SSL certificates for secure communication
    • Implement monitoring and logging systems
  • Add vector database capabilities:
    • Implement embedding generation using models like BERT or USE
    • Set up vector similarity search using Pinecone or Milvus
    • Create semantic search functionality for chat history

This project serves as an extensive foundation for developing a chatbot with memory capabilities. Here's what you'll gain from completing it:

First, you'll get practical experience with modern web frameworks - both Flask for traditional web development and Streamlit for rapid prototyping of data applications. You'll learn how these frameworks differ and when to use each one.

Second, you'll master working with the OpenAI API, understanding how to structure prompts, handle responses, and manage API interactions effectively. This knowledge is crucial for building any AI-powered application.

Third, you'll learn database integration principles, including how to design schemas, manage connections, and handle data persistence. This ensures your chatbot can maintain conversations across sessions and scale effectively.

Finally, this project will prepare you for building more sophisticated chatbot applications, such as those incorporating advanced features like sentiment analysis, multiple AI models, or real-time processing. The skills you develop here form the building blocks for more complex AI applications.