A minimal, runnable Python chat UI that mimics the Claude quickstart demo using a local mock LLM
Welcome to another official UVF IT build guide! In this tutorial we’ll spin up a tiny chatbot inspired by Anthropic’s Claude‑quickstart projects, but we’ll keep everything on your laptop and avoid any paid APIs.
You’ll end up with a Flask web app that accepts a message, calls a mock LLM function, and returns a response – all in under 45 minutes. Feel free to swap the mock for a real Claude key later.
pip available.mock_claude() with an actual API call to go production.Create a clean folder for the clone and isolate its dependencies in a virtual environment. This keeps your system Python tidy and makes the project reproducible.
Install Flask, the only external library we need for the web server. After this step you’ll have a ready‑to‑run folder structure.
We’ll also add a tiny run.sh helper so you can start the app with a single command.
# 1️⃣ Create project directory and enter it
mkdir claude_chat_clone && cd claude_chat_clone
# 2️⃣ Set up a virtual environment (Python 3.10+ required)
python3 -m venv venv
source venv/bin/activate
# 3️⃣ Upgrade pip and install Flask
pip install --upgrade pip
pip install Flask
# 4️⃣ Optional helper to launch the server
cat > run.sh <<'EOF'
#!/usr/bin/env bash
source venv/bin/activate
python app.py
EOF
chmod +x run.sh
# 5️⃣ Verify installation
python -c "import flask, sys; print('Flask version:', flask.__version__)"Now we write the heart of the clone: a tiny Python function that pretends to be Claude. It simply returns the user’s prompt prefixed with a friendly message, giving you deterministic output for testing.
We also set up a minimal Flask route that receives a chat message, calls the mock function, and returns JSON. This keeps the UI logic separate from the model logic, mirroring the real quickstart structure.
app.py
from flask import Flask, request, jsonify, render_template_string
app = Flask(__name__)
# ---- Mock LLM ---------------------------------------------------
def mock_claude(prompt: str) -> str:
"""Return a deterministic reply that mimics Claude's style.
In a real implementation you would call Anthropic's API here.
"""
return f"Claude says: {prompt.strip()}"
# ---- Simple HTML UI --------------------------------------------
HTML_UI = """
<!doctype html>
<html lang='en'>
<head><title>Claude‑quickstart Clone</title></head>
<body>
<h2>Chat with Mock Claude</h2>
<form method='post' action='/chat'>
<input name='msg' placeholder='Your message' size='40'/>
<button type='submit'>Send</button>
</form>
{% if response %}<p><strong>Response:</strong> {{ response }}</p>{% endif %}
</body>
</html>
"""
# ---- Routes -----------------------------------------------------
@app.route('/')
def index():
return render_template_string(HTML_UI)
@app.route('/chat', methods=['POST'])
def chat():
user_msg = request.form.get('msg', '')
reply = mock_claude(user_msg)
return render_template_string(HTML_UI, response=reply)
if __name__ == '__main__':
# Run on localhost:5000 for quick testing
app.run(debug=True)In this step we wire a tiny Flask server that accepts chat requests, forwards them to our mock Claude function, and returns a JSON response. The same file also serves a static HTML page so you can type messages without leaving the browser. Keeping everything in one file makes the clone easy to run and modify for later extensions.
app.py
from flask import Flask, request, jsonify, render_template_string
import time
app = Flask(__name__)
# Mock Claude function – simply prefixes the input
def mock_claude(message: str) -> str:
time.sleep(0.2) # simulate latency
return f"Claude says: {message.capitalize()}"
# HTML UI – minimal but functional
HTML_PAGE = """
<!doctype html>
<html lang='en'>
<head><meta charset='utf-8'><title>Claude‑quickstart Clone</title></head>
<body>
<h2>Chat with Mock Claude</h2>
<div id='chat' style='margin-bottom:1em;'></div>
<input id='msg' type='text' placeholder='Type a message' style='width:70%'>
<button onclick='send()'>Send</button>
<script>
async function send(){
const txt=document.getElementById('msg').value;
const resp=await fetch('/chat',{
method:'POST',
headers:{'Content-Type':'application/json'},
body:JSON.stringify({message:txt})
});
const data=await resp.json();
const chat=document.getElementById('chat');
chat.innerHTML+=`<p><b>You:</b> ${txt}</p><p><b>Claude:</b> ${data.reply}</p>`;
document.getElementById('msg').value='';
}
</script>
</body>
</html>
"""
@app.route('/')
def index():
return render_template_string(HTML_PAGE)
@app.route('/chat', methods=['POST'])
def chat():
payload = request.get_json()
user_msg = payload.get('message', '')
reply = mock_claude(user_msg)
return jsonify({'reply': reply})
if __name__ == '__main__':
app.run(debug=True)Now that the server code is ready, start the development server with a single command. Flask will listen on localhost port 5000 and automatically reload when you edit the file, which is handy while you experiment.
python app.pyOpen your browser to http://127.0.0.1:5000, type a message, and watch the prefixed reply appear. Once the round‑trip works, you can replace the mock_claude function with a real Anthropic API call or add a SQLite log to persist chats. Below is a quick example of how to log each exchange without breaking the existing UI.
app.py
# Append to the previous app.py – add after imports
import sqlite3
# Initialize SQLite DB (run once)
conn = sqlite3.connect('chatlog.db')
cur = conn.cursor()
cur.execute('CREATE TABLE IF NOT EXISTS logs(id INTEGER PRIMARY KEY AUTOINCREMENT, user TEXT, bot TEXT, ts DATETIME DEFAULT CURRENT_TIMESTAMP)')
conn.commit()
conn.close()
# Modified mock_claude to also store the exchange
def mock_claude(message: str) -> str:
reply = f"Claude says: {message.capitalize()}"
# Persist to SQLite
conn = sqlite3.connect('chatlog.db')
cur = conn.cursor()
cur.execute('INSERT INTO logs(user, bot) VALUES(?,?)', (message, reply))
conn.commit()
conn.close()
return reply| Check | Command / Why it matters |
|---|---|
| Flask server starts without errors | python app.py → should show *Running on http://127.0.0.1:5000/* |
| Browser displays chat UI | Open http://127.0.0.1:5000 in a browser – you see a text box and Send button |
| Message round‑trip works | Enter "hello" → UI shows "Claude says: Hello" |
Swap mock_claude for a real call to Anthropic’s API (you’ll need an API key) or persist chats in SQLite/Postgres. The scaffold is ready for any upgrade!