> ## Documentation Index
> Fetch the complete documentation index at: https://genai.codewithsiva.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# 7. Custom ChatGPT Application

> Build a persistent custom ChatGPT clone using Streamlit, SQLite database, and user logins

In this section, you will build a complete, stateful **Custom ChatGPT Clone** web application. This extends the Streamlit chatbot by adding a secure user registration and login system, local SQLite persistence, and chat log retrieval.

## Objectives

1. Implement a secure user authentication system (Registration, Login, and Logout) using SQLite and password hashing.
2. Link the active authenticated user's session to SQL-backed session storage (`SQLChatMessageHistory`).
3. Render user-specific history dynamically on login.
4. Provide a dashboard with chat capabilities, logout options, and sidebar controls (like clearing chat logs).

## Plan

1. **Database & Hashing Setup**: Establish an SQLite database (`chat_history.db`) with a `users` table and implement SHA-256 password hashing.
2. **User Authentication Flow**:
   * Use `st.session_state` to track authentication status and the logged-in username.
   * Build a landing portal where users can toggle between "Login" and "Register" forms.
3. **Dashboard & Session Initialization**: Once logged in, initialize `SQLChatMessageHistory` using the username as the session identifier.
4. **History Rendering & Chat Loop**: Retrieve and display past session messages, accept user chat input, save to SQLite, get the model's response, and save it back.
5. **Session Control & Logs**: Implement a "Log Out" button to clear session variables, and a "Clear Chat History" button to wipe chat logs.

## Step-by-Step Implementation

Let's build the Custom ChatGPT Clone incrementally:

#### Step 1: File and Folder Setup

**Plan:**

1. Create the target directory `langchain/1_chat_models` if it does not already exist.
2. Create a new Python file named `7_chat_model_custom_chatgpt.py` inside this folder.

**Command or Action:**
Create and navigate to the directory in your workspace:

```bash theme={null}
mkdir -p langchain/1_chat_models
touch langchain/1_chat_models/7_chat_model_custom_chatgpt.py
```

#### Step 2: Imports and Page Config

**Plan:**

1. Import `streamlit`, `sqlite3`, `hashlib`, `SQLChatMessageHistory`, and message schemas.
2. Configure the page settings and load environment variables.

**Code Implementation:**

```python theme={null}
import streamlit as st
import sqlite3
import hashlib
from langchain_community.chat_message_histories import SQLChatMessageHistory
from langchain.chat_models import init_chat_model
from langchain_core.messages import AIMessage, HumanMessage, SystemMessage
from dotenv import load_dotenv

load_dotenv()

st.set_page_config(page_title="Custom ChatGPT App", page_icon="🤖")

DB_FILE = "chat_history.db"
```

#### Step 3: Database Setup & Password Hashing

**Plan:**

1. Create a helper `init_db()` to create the `users` table if it doesn't exist.
2. Create `hash_password(password)` using `hashlib.sha256` to avoid storing plain-text passwords.

**Code Implementation:**

```python theme={null}
# Database Helper Functions
def init_db():
    conn = sqlite3.connect(DB_FILE)
    cursor = conn.cursor()
    cursor.execute("""
        CREATE TABLE IF NOT EXISTS users (
            username TEXT PRIMARY KEY,
            password TEXT NOT NULL
        )
    """)
    conn.commit()
    conn.close()

def hash_password(password):
    return hashlib.sha256(password.encode()).hexdigest()
```

#### Step 4: Registration and Login Functions

**Plan:**

1. Implement `register_user(username, password)` to insert credentials into the `users` table (handling potential duplicate username errors).
2. Implement `login_user(username, password)` to fetch the password for a username and verify it matches the hashed password input.

**Code Implementation:**

```python theme={null}
def register_user(username, password):
    conn = sqlite3.connect(DB_FILE)
    cursor = conn.cursor()
    try:
        cursor.execute("INSERT INTO users (username, password) VALUES (?, ?)", (username, hash_password(password)))
        conn.commit()
        return True
    except sqlite3.IntegrityError:
        return False
    finally:
        conn.close()

def login_user(username, password):
    conn = sqlite3.connect(DB_FILE)
    cursor = conn.cursor()
    cursor.execute("SELECT password FROM users WHERE username = ?", (username,))
    row = cursor.fetchone()
    conn.close()
    if row and row[0] == hash_password(password):
        return True
    return False

# Initialize database
init_db()
```

#### Step 5: Streamlit Session State & Authentication UI

**Plan:**

1. Set up session state variables `authenticated` and `username`.
2. If the user is not authenticated, show a login/registration portal using `st.radio`.

**Code Implementation:**

```python theme={null}
# Session State Initialization
if "authenticated" not in st.session_state:
    st.session_state.authenticated = False
if "username" not in st.session_state:
    st.session_state.username = None

# Screen Navigation and Authentication Flow
if not st.session_state.authenticated:
    st.title("🔐 Custom ChatGPT Portal")
    
    # Toggle between Login and Register
    auth_mode = st.radio("Select Option:", ["Login", "Register"])
    
    username_input = st.text_input("Username:")
    password_input = st.text_input("Password:", type="password")
    
    if auth_mode == "Register":
        if st.button("Create Account"):
            if username_input and password_input:
                if register_user(username_input, password_input):
                    st.success("Registration successful! Please select 'Login' to continue.")
                else:
                    st.error("Username already exists. Please choose a different one.")
            else:
                st.warning("Please fill in all fields.")
                
    elif auth_mode == "Login":
        if st.button("Log In"):
            if username_input and password_input:
                if login_user(username_input, password_input):
                    st.session_state.authenticated = True
                    st.session_state.username = username_input
                    st.success("Login successful!")
                    st.rerun()
                else:
                    st.error("Invalid username or password.")
            else:
                st.warning("Please fill in all fields.")
```

#### Step 6: Authenticated Chat Dashboard & Logout

**Plan:**

1. If authenticated, render the dashboard.
2. Add a **Log Out** button in the sidebar to reset session variables.
3. Initialize the SQL-backed chat history using the active username and load the chat interface.

**Code Implementation:**

```python theme={null}
else:
    # Authenticated Screen (Dashboard & Chat)
    st.title("🤖 Custom ChatGPT Dashboard")
    st.sidebar.header(f"👤 Account: {st.session_state.username}")
    
    # Logout action
    if st.sidebar.button("🔓 Log Out"):
        st.session_state.authenticated = False
        st.session_state.username = None
        st.rerun()
        
    # Setup SQL Message History unique to logged-in user
    chat_history = SQLChatMessageHistory(
        session_id=st.session_state.username,
        connection_string=f"sqlite:///{DB_FILE}"
    )

    # Sidebar Actions
    if st.sidebar.button("🗑️ Clear Chat History"):
        chat_history.clear()
        st.sidebar.warning("History cleared!")
        st.rerun()

    # Initialize model using core abstractions (Groq Llama model)
    model = init_chat_model("llama-3.3-70b-versatile", model_provider="groq")

    # If the SQL history is empty, write a default SystemMessage
    if len(chat_history.messages) == 0:
        chat_history.add_message(SystemMessage(content="You are a helpful and custom ChatGPT assistant."))

    # Render persisted chat history from SQLite
    for message in chat_history.messages:
        if isinstance(message, HumanMessage):
            with st.chat_message("user"):
                st.markdown(message.content)
        elif isinstance(message, AIMessage):
            with st.chat_message("assistant"):
                st.markdown(message.content)

    # User chat input
    if prompt := st.chat_input("Ask ChatGPT anything..."):
        # Render user message
        with st.chat_message("user"):
            st.markdown(prompt)
        # Save to SQLite database
        chat_history.add_user_message(prompt)

        # Get AI response
        with st.chat_message("assistant"):
            message_placeholder = st.empty()
            with st.spinner("Responding..."):
                response = model.invoke(chat_history.messages)
                message_placeholder.markdown(response.content)
        
        # Save response to SQLite database
        chat_history.add_ai_message(response.content)
```

## Combined Code

Combining all the steps above gives the final complete script:

```python theme={null}
import streamlit as st
import sqlite3
import hashlib
from langchain_community.chat_message_histories import SQLChatMessageHistory
from langchain.chat_models import init_chat_model
from langchain_core.messages import AIMessage, HumanMessage, SystemMessage
from dotenv import load_dotenv

load_dotenv()

st.set_page_config(page_title="Custom ChatGPT App", page_icon="🤖")

DB_FILE = "chat_history.db"

# Database Helper Functions
def init_db():
    conn = sqlite3.connect(DB_FILE)
    cursor = conn.cursor()
    # Create users table if not exists
    cursor.execute("""
        CREATE TABLE IF NOT EXISTS users (
            username TEXT PRIMARY KEY,
            password TEXT NOT NULL
        )
    """)
    conn.commit()
    conn.close()

def hash_password(password):
    return hashlib.sha256(password.encode()).hexdigest()

def register_user(username, password):
    conn = sqlite3.connect(DB_FILE)
    cursor = conn.cursor()
    try:
        cursor.execute("INSERT INTO users (username, password) VALUES (?, ?)", (username, hash_password(password)))
        conn.commit()
        return True
    except sqlite3.IntegrityError:
        return False
    finally:
        conn.close()

def login_user(username, password):
    conn = sqlite3.connect(DB_FILE)
    cursor = conn.cursor()
    cursor.execute("SELECT password FROM users WHERE username = ?", (username,))
    row = cursor.fetchone()
    conn.close()
    if row and row[0] == hash_password(password):
        return True
    return False

# Initialize database
init_db()

# Session State Initialization
if "authenticated" not in st.session_state:
    st.session_state.authenticated = False
if "username" not in st.session_state:
    st.session_state.username = None

# Screen Navigation and Authentication Flow
if not st.session_state.authenticated:
    st.title("🔐 Custom ChatGPT Portal")
    
    # Toggle between Login and Register
    auth_mode = st.radio("Select Option:", ["Login", "Register"])
    
    username_input = st.text_input("Username:")
    password_input = st.text_input("Password:", type="password")
    
    if auth_mode == "Register":
        if st.button("Create Account"):
            if username_input and password_input:
                if register_user(username_input, password_input):
                    st.success("Registration successful! Please select 'Login' to continue.")
                else:
                    st.error("Username already exists. Please choose a different one.")
            else:
                st.warning("Please fill in all fields.")
                
    elif auth_mode == "Login":
        if st.button("Log In"):
            if username_input and password_input:
                if login_user(username_input, password_input):
                    st.session_state.authenticated = True
                    st.session_state.username = username_input
                    st.success("Login successful!")
                    st.rerun()
                else:
                    st.error("Invalid username or password.")
            else:
                st.warning("Please fill in all fields.")
else:
    # Authenticated Screen (Dashboard & Chat)
    st.title("🤖 Custom ChatGPT Dashboard")
    st.sidebar.header(f"👤 Account: {st.session_state.username}")
    
    # Logout action
    if st.sidebar.button("🔓 Log Out"):
        st.session_state.authenticated = False
        st.session_state.username = None
        st.rerun()
        
    # Setup SQL Message History unique to logged-in user
    chat_history = SQLChatMessageHistory(
        session_id=st.session_state.username,
        connection_string=f"sqlite:///{DB_FILE}"
    )

    # Sidebar Actions
    if st.sidebar.button("🗑️ Clear Chat History"):
        chat_history.clear()
        st.sidebar.warning("History cleared!")
        st.rerun()

    # Initialize model using core abstractions (Groq Llama model)
    model = init_chat_model("llama-3.3-70b-versatile", model_provider="groq")

    # If the SQL history is empty, write a default SystemMessage
    if len(chat_history.messages) == 0:
        chat_history.add_message(SystemMessage(content="You are a helpful and custom ChatGPT assistant."))

    # Render persisted chat history from SQLite
    for message in chat_history.messages:
        if isinstance(message, HumanMessage):
            with st.chat_message("user"):
                st.markdown(message.content)
        elif isinstance(message, AIMessage):
            with st.chat_message("assistant"):
                st.markdown(message.content)

    # User chat input
    if prompt := st.chat_input("Ask ChatGPT anything..."):
        # Render user message
        with st.chat_message("user"):
            st.markdown(prompt)
        # Save to SQLite database
        chat_history.add_user_message(prompt)

        # Get AI response
        with st.chat_message("assistant"):
            message_placeholder = st.empty()
            with st.spinner("Responding..."):
                response = model.invoke(chat_history.messages)
                message_placeholder.markdown(response.content)
        
        # Save response to SQLite database
        chat_history.add_ai_message(response.content)
```

## Exercise: Dynamic Model Swapper 🔀

#### Goal

Extend the sidebar options to include a model selector selectbox (`st.sidebar.selectbox`) that allows the logged-in user to swap between Llama (`llama-3.3-70b-versatile` via Groq) and Gemini (`gemini-2.5-flash` via Google GenAI) models dynamically without resetting the conversation history or logging out.

#### Plan

1. Inside the authenticated screen block (`else:`), add a selectbox in the sidebar containing model choices: `"Llama 3.3 (Groq)"` and `"Gemini 2.5 (Google)"`.
2. Based on selection, determine the correct `model_name` and `model_provider`.
3. Pass these parameters to the `init_chat_model` instantiation dynamically.

<Accordion title="Solution">
  ```python theme={null}
  import streamlit as st
  import sqlite3
  import hashlib
  from langchain_community.chat_message_histories import SQLChatMessageHistory
  from langchain.chat_models import init_chat_model
  from langchain_core.messages import AIMessage, HumanMessage, SystemMessage
  from dotenv import load_dotenv

  load_dotenv()

  st.set_page_config(page_title="Custom ChatGPT App", page_icon="🤖")

  DB_FILE = "chat_history.db"

  # Database Helper Functions
  def init_db():
      conn = sqlite3.connect(DB_FILE)
      cursor = conn.cursor()
      cursor.execute("""
          CREATE TABLE IF NOT EXISTS users (
              username TEXT PRIMARY KEY,
              password TEXT NOT NULL
          )
      """)
      conn.commit()
      conn.close()

  def hash_password(password):
      return hashlib.sha256(password.encode()).hexdigest()

  def register_user(username, password):
      conn = sqlite3.connect(DB_FILE)
      cursor = conn.cursor()
      try:
          cursor.execute("INSERT INTO users (username, password) VALUES (?, ?)", (username, hash_password(password)))
          conn.commit()
          return True
      except sqlite3.IntegrityError:
          return False
      finally:
          conn.close()

  def login_user(username, password):
      conn = sqlite3.connect(DB_FILE)
      cursor = conn.cursor()
      cursor.execute("SELECT password FROM users WHERE username = ?", (username,))
      row = cursor.fetchone()
      conn.close()
      if row and row[0] == hash_password(password):
          return True
      return False

  # Initialize database
  init_db()

  # Session State Initialization
  if "authenticated" not in st.session_state:
      st.session_state.authenticated = False
  if "username" not in st.session_state:
      st.session_state.username = None

  # Screen Navigation and Authentication Flow
  if not st.session_state.authenticated:
      st.title("🔐 Custom ChatGPT Portal")
      
      # Toggle between Login and Register
      auth_mode = st.radio("Select Option:", ["Login", "Register"])
      
      username_input = st.text_input("Username:")
      password_input = st.text_input("Password:", type="password")
      
      if auth_mode == "Register":
          if st.button("Create Account"):
              if username_input and password_input:
                  if register_user(username_input, password_input):
                      st.success("Registration successful! Please select 'Login' to continue.")
                  else:
                      st.error("Username already exists. Please choose a different one.")
              else:
                  st.warning("Please fill in all fields.")
                  
      elif auth_mode == "Login":
          if st.button("Log In"):
              if username_input and password_input:
                  if login_user(username_input, password_input):
                      st.session_state.authenticated = True
                      st.session_state.username = username_input
                      st.success("Login successful!")
                      st.rerun()
                  else:
                      st.error("Invalid username or password.")
              else:
                  st.warning("Please fill in all fields.")
  else:
      # Authenticated Screen (Dashboard & Chat)
      st.title("🤖 Custom ChatGPT Dashboard")
      st.sidebar.header(f"👤 Account: {st.session_state.username}")
      
      # Logout action
      if st.sidebar.button("🔓 Log Out"):
          st.session_state.authenticated = False
          st.session_state.username = None
          st.rerun()
          
      # Model Selection
      selected_model = st.sidebar.selectbox(
          "Select Model Provider:",
          ["Llama 3.3 (Groq)", "Gemini 2.5 (Google)"]
      )
      
      if selected_model == "Llama 3.3 (Groq)":
          model_name, provider = "llama-3.3-70b-versatile", "groq"
      else:
          model_name, provider = "gemini-2.5-flash", "google_genai"

      # Setup SQL Message History unique to logged-in user
      chat_history = SQLChatMessageHistory(
          session_id=st.session_state.username,
          connection_string=f"sqlite:///{DB_FILE}"
      )

      # Sidebar Actions
      if st.sidebar.button("🗑️ Clear Chat History"):
          chat_history.clear()
          st.sidebar.warning("History cleared!")
          st.rerun()

      # Dynamic Model loading using core abstractions
      model = init_chat_model(model_name, model_provider=provider)

      # If the SQL history is empty, write a default SystemMessage
      if len(chat_history.messages) == 0:
          chat_history.add_message(SystemMessage(content="You are a helpful and custom ChatGPT assistant."))

      # Render persisted chat history from SQLite
      for message in chat_history.messages:
          if isinstance(message, HumanMessage):
              with st.chat_message("user"):
                  st.markdown(message.content)
          elif isinstance(message, AIMessage):
              with st.chat_message("assistant"):
                  st.markdown(message.content)

      # User chat input
      if prompt := st.chat_input("Ask ChatGPT anything..."):
          # Render user message
          with st.chat_message("user"):
              st.markdown(prompt)
          # Save to SQLite database
          chat_history.add_user_message(prompt)

          # Get AI response
          with st.chat_message("assistant"):
              message_placeholder = st.empty()
              with st.spinner("Responding..."):
                  response = model.invoke(chat_history.messages)
                  message_placeholder.markdown(response.content)
          
          # Save response to SQLite database
          chat_history.add_ai_message(response.content)
  ```
</Accordion>

## Practice & Exercises

To practice, open the interactive notebook:

<CardGroup cols={1}>
  <Card title="Practice & Exercises" icon="laptop-code">
    Practice setting up and running your custom ChatGPT Streamlit application.

    [💻 VS Code](vscode://file/Users/sivaprasad/Downloads/GenAI%20With%20Python/public/notebooks/langchain/7_chat_model_custom_chatgpt.ipynb) | [🚀 Colab](https://colab.research.google.com/github/prasad230776/genai-course/blob/master/public/notebooks/langchain/7_chat_model_custom_chatgpt.ipynb) | <a href="/public/notebooks/langchain/7_chat_model_custom_chatgpt.ipynb" download>📥 Download</a>
  </Card>
</CardGroup>

To execute the Custom ChatGPT app locally, run the script from your terminal:

```bash theme={null}
streamlit run langchain/1_chat_models/7_chat_model_custom_chatgpt.py
```
