Site

App: Web Chat — Flask Chat Interface

Tutorial A6.0  •  AI / Learn / Applications

A6.0 What This Teaches

This tutorial builds a minimal Flask web app: the browser sends a message, the server calls Claude, and the response appears in the page without a full reload. It covers:

A6.1 Application Layout

The project has two files: a Python server and one HTML template.
web_chat/
├── app.py          ← Flask server
└── templates/
    └── index.html  ← chat UI
Flask looks for templates in a templates/ folder by default. That folder must sit next to app.py.

A6.2 The Flask Server

The server has two routes: Conversation history is kept in a module-level list called messages. This is intentionally simple: it supports one user at a time and resets when the server restarts. That is fine for local development and learning.
# app.py (structure) - Flask server with in-memory chat history.
from flask import Flask, render_template, request, jsonify
import anthropic

app = Flask(__name__)
client = anthropic.Anthropic()
messages = []   # single-user, in-memory history; resets on restart

@app.route("/")
def index():
    return render_template("index.html")

@app.route("/chat", methods=["POST"])
def chat():
    user_text = request.json["message"]
    messages.append({"role": "user", "content": user_text})
    response = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=1024,
        messages=messages,
    )
    reply = response.content[0].text
    messages.append({"role": "assistant", "content": reply})
    return jsonify({"reply": reply})

A6.3 Complete app.py

# app.py - Flask chat server backed by Claude.
# Run: python app.py, then open http://127.0.0.1:5000

from flask import Flask, render_template, request, jsonify
import anthropic

app = Flask(__name__)
client = anthropic.Anthropic()

# In-memory history - single-user only, resets on server restart.
messages = []

@app.route("/")
def index():
    return render_template("index.html")

@app.route("/chat", methods=["POST"])
def chat():
    data = request.json
    if not data or not data.get("message"):
        return jsonify({"error": "empty message"}), 400

    user_text = data["message"]
    messages.append({"role": "user", "content": user_text})

    response = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=1024,
        messages=messages,
    )

    reply = response.content[0].text
    messages.append({"role": "assistant", "content": reply})
    return jsonify({"reply": reply})

if __name__ == "__main__":
    app.run(debug=True)
debug=True enables auto-reload on file changes. Turn it off in any non-development environment.

A6.4 The HTML Template

The chat UI needs three things: The JavaScript appends both the user message and the reply directly to the chat div. No page reload occurs. The input is cleared after each send.
// Sends a message and appends the reply to the chat log.
async function sendMessage() {
  const input = document.getElementById('user-input');
  const text  = input.value.trim();
  if (!text) return;
  input.value = '';

  appendMessage('You', text);

  const res  = await fetch('/chat', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ message: text }),
  });
  const data = await res.json();
  appendMessage('Claude', data.reply);
}

A6.5 Complete index.html

<!-- templates/index.html - Chat UI -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Web Chat</title>
  <style>
    body { font-family: sans-serif; max-width: 700px; margin: 2rem auto; }
    #chat-log { border: 1px solid #ccc; height: 400px; overflow-y: auto;
                padding: 1rem; margin-bottom: 1rem; }
    .msg { margin-bottom: 0.75rem; }
    .msg strong { display: block; }
    #controls { display: flex; gap: 0.5rem; }
    #user-input { flex: 1; padding: 0.4rem; }
  </style>
</head>
<body>
  <h1>Web Chat</h1>
  <div id="chat-log"></div>
  <div id="controls">
    <input id="user-input" type="text" placeholder="Type a message..."
           onkeydown="if(event.key==='Enter') sendMessage()">
    <button onclick="sendMessage()">Send</button>
  </div>

  <script>
    function appendMessage(who, text) {
      const log = document.getElementById('chat-log');
      const div = document.createElement('div');
      div.className = 'msg';
      div.innerHTML = '<strong>' + who + '</strong>' + text;
      log.appendChild(div);
      log.scrollTop = log.scrollHeight;
    }

    async function sendMessage() {
      const input = document.getElementById('user-input');
      const text  = input.value.trim();
      if (!text) return;
      input.value = '';

      appendMessage('You', text);

      const res  = await fetch('/chat', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ message: text }),
      });
      const data = await res.json();
      appendMessage('Claude', data.reply);
    }
  </script>
</body>
</html>

A6.6 Running the Application

Install Flask if you have not already, then start the server:
pip install flask anthropic
cd web_chat
python app.py
Open http://127.0.0.1:5000 in a browser. Type a message and press Enter or click Send. The reply appears below your message without a page reload.
 * Running on http://127.0.0.1:5000
 * Debug mode: on
 * Restarting with stat

A6.7 Exercise

Exercise Add a "Clear" button that resets the conversation on both the server and the browser.
  1. Add a POST route /clear in app.py that runs messages.clear() and returns jsonify({"ok": True}).
  2. Add a Clear button to index.html that calls fetch('/clear', {method: 'POST'}) and then clears the #chat-log div.
Test it: have a multi-turn conversation, click Clear, then send a new message. Confirm the model no longer remembers the earlier turns.

A6.8 Common Mistakes

Storing history in a module-level list for multiple users

A module-level messages list works correctly for one user at a time during development. With two simultaneous users, their messages get interleaved and both users see the wrong history. For production, use a session or database keyed per user.

Returning HTML from the /chat route instead of JSON

If the route returns an HTML string, the browser-side JavaScript has to parse HTML to extract the reply. Returning jsonify({"reply": text}) lets the JS read data.reply directly - much simpler and less error-prone.

Forgetting Content-Type: application/json in fetch()

Without headers: {'Content-Type': 'application/json'} in the fetch() call, Flask receives the body as raw bytes and request.json returns None. Always set the header when sending a JSON body.

Key Terms

TermMeaning
FlaskLightweight Python web framework; maps URLs to Python functions called routes
routeA URL pattern bound to a Python function with @app.route()
render_templateFlask helper that reads an HTML file from the templates/ folder and returns it
request.jsonThe parsed JSON body of an incoming POST request; None if Content-Type is wrong
jsonifyFlask helper that converts a Python dict to a JSON HTTP response with correct headers
fetch()Browser API for making HTTP requests from JavaScript without a page reload
POSTHTTP method for sending data to the server; used here to submit chat messages
in-memory sessionConversation history stored in a Python list; fast but lost on server restart