RUNLOCALAIv38
->Will it run?Best GPUCompareTroubleshootStartLearnPulseModelsHardwareToolsBench
Run check
RUNLOCALAI

Independently operated catalog for local-AI hardware and software. Hand-written verdicts. Source-cited claims. Reproducible commands when we have them.

OP·Eruo Fredoline
DIR
  • Models
  • Hardware
  • Tools
  • Benchmarks
TOOLS
  • Will it run?
  • Compare hardware
  • Cost vs cloud
  • Choose my GPU
  • Prompting kits
  • Quick answers
REF
  • All buyer guides
  • Learn local AI
  • Methodology
  • Glossary
  • Errors KB
  • Trust
EDITOR
  • About
  • Author
  • How we make money
  • Editorial policy
  • Contact
LEGAL
  • Privacy
  • Terms
  • Sitemap
MAIL · MONTHLY DIGEST
Get monthly local AI changes
Monthly recap. No spam.
DISCLOSURE

Some links on this site are affiliate links (Amazon Associates and other first-class retailers). When you buy through them, we earn a small commission at no extra cost to you. Affiliate links do not influence our verdicts — there are cards we rate highly that we don't have affiliate relationships with, and cards that sell well that we refuse to recommend. Read more →

© 2026 runlocalai.coIndependently operated
RUNLOCALAI · v38
  1. >
  2. Home
  3. /Learn
  4. /Courses
  5. /First Local Chatbot
  6. /Ch. 5
First Local Chatbot

05. HTML Frontend Basics

Chapter 5 of 15 · 15 min
KEY INSIGHT

The frontend is just HTML served by FastAPI. No CDN, no JS bundler, no build pipeline—the browser loads it directly.

Create app/templates/index.html. This is a single-file frontend—no build step, no framework:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Local Chatbot</title>
  <style>
    body { font-family: system-ui; max-width: 720px; margin: 2rem auto; padding: 0 1rem; }
    #chat { border: 1px solid #ccc; height: 400px; overflow-y: auto; padding: 1rem; }
    .msg { margin: 0.5rem 0; }
    .user { color: #0066cc; }
    .assistant { color: #333; }
  </style>
</head>
<body>
  <h1>Chatbot</h1>
  <div id="chat"></div>
  <form id="form">
    <textarea id="input" rows="3" style="width:100%;"></textarea>
    <button type="submit">Send</button>
  </form>
  <script>
    // JS goes here
  </script>
</body>
</html>

Wire it into FastAPI. Update app/main.py:

from fastapi import FastAPI
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles

app = FastAPI()

@app.get("/", response_class=HTMLResponse)
async def root():
    with open("app/templates/index.html") as f:
        return f.read()

If the file path is wrong, FastAPI returns a 500 with FileNotFoundError. Use an absolute path or mount the templates directory. For now, use a relative path from the project root.

Local verification checkpoint

Run the smallest example from this chapter in a local workspace and record the package version, runtime, data path, and observed output. If the result depends on model size, vector count, CPU/GPU backend, or available memory, note that constraint beside the exercise so the lesson remains reproducible.

Local verification checkpoint

Run the smallest example from this chapter in a local workspace and record the package version, runtime, data path, and observed output. If the result depends on model size, vector count, CPU/GPU backend, or available memory, note that constraint beside the exercise so the lesson remains reproducible.

EXERCISE

Add a model selector <select id="model"> to the HTML and populate it via a fetch("/models") call on page load.

← Chapter 4
Streaming Responses
Chapter 6 →
JavaScript EventSource