~/TechPurAI
~/tutorials/llm-and-advanced-ai/build-a-website-ai-chatbot
intermediate·part 13 of 22·3 min read

Build a Website AI Chatbot

Updated Aug 16, 2026AI

Every RAG project in this series so far has run as backend Python code. This part puts it on Bright Leaf Coffee's actual, real website — a genuine, embeddable chat widget, using the exact HTML and CSS techniques this site's own HTML From Scratch and CSS From Scratch series already taught.

The real backend: exposing part 6's RAG system as an API

python
# widget_api.py
from fastapi import FastAPI
from pydantic import BaseModel
from rag_app import answer_question  # part 6's real function

app = FastAPI()

class ChatRequest(BaseModel):
    question: str

@app.post("/api/chat")
def chat(request: ChatRequest):
    result = answer_question(request.question)
    return {"answer": result["answer"]}

This directly reuses the AI Projects series' own FastAPI pattern — a real, minimal API exposing part 6's actual RAG function to whatever real frontend calls it, here specifically a real website widget.

The real, semantic HTML structure

html
<div id="bl-chat-widget">
  <button id="bl-chat-toggle" aria-label="Open chat" aria-expanded="false">
    Ask us
  </button>
  <div id="bl-chat-panel" hidden>
    <div id="bl-chat-messages" role="log" aria-live="polite"></div>
    <form id="bl-chat-form">
      <label for="bl-chat-input">Your question</label>
      <input type="text" id="bl-chat-input" required />
      <button type="submit">Send</button>
    </form>
  </div>
</div>

This directly follows the HTML series' own forms and accessibility guidance — a real, associated <label>, and role="log" with aria-live="polite" on the messages container, so a screen reader announces new real messages as they arrive, exactly the same accessibility discipline this site's own HTML tutorials established throughout.

The real, styled floating widget

css
#bl-chat-widget {
  position: fixed;
  bottom: 1.5rem;
  right: 1.5rem;
  z-index: 50;
}

#bl-chat-panel {
  position: absolute;
  bottom: 4rem;
  right: 0;
  width: 320px;
  background: var(--color-panel);
  border: 1px solid var(--color-line);
  border-radius: var(--radius);
  box-shadow: 0 4px 24px rgba(0, 0, 0, 0.15);
}

#bl-chat-toggle {
  background: var(--color-accent);
  border-radius: 50%;
  width: 56px;
  height: 56px;
}

This is directly the CSS series' own positioning and design-token guidance — position: fixed keeps the widget pinned to the viewport regardless of scroll, exactly the real pattern that part covered, and every color and spacing value references Bright Leaf Coffee's real, established design tokens rather than new, arbitrary values.

The real, working JavaScript

html
<script>
  const form = document.getElementById("bl-chat-form");
  const input = document.getElementById("bl-chat-input");
  const messages = document.getElementById("bl-chat-messages");
  const toggle = document.getElementById("bl-chat-toggle");
  const panel = document.getElementById("bl-chat-panel");

  toggle.addEventListener("click", () => {
    const isOpen = !panel.hidden;
    panel.hidden = isOpen;
    toggle.setAttribute("aria-expanded", String(!isOpen));
  });

  form.addEventListener("submit", async (e) => {
    e.preventDefault();
    const question = input.value.trim();
    if (!question) return;

    appendMessage("user", question);
    input.value = "";

    const response = await fetch("/api/chat", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ question }),
    });
    const data = await response.json();
    appendMessage("assistant", data.answer);
  });

  function appendMessage(role, text) {
    const el = document.createElement("p");
    el.textContent = `${role}: ${text}`;
    messages.appendChild(el);
  }
</script>
Why it matters

aria-expanded, updated on every real toggle click, is what lets a screen reader user know whether the chat panel is currently open or closed — directly extending the exact real accessibility pattern the HTML series established for any collapsible UI element, applied here to a chat widget specifically.

Embedding it on a real Bright Leaf Coffee page

html
<!-- at the end of <body> on every real page that should show the widget -->
<div id="bl-chat-widget">...</div>
<script src="/widget.js"></script>

A real, genuine embeddable widget like this is typically added once to a real site's shared template or footer — exactly the kind of shared, site-wide component the HTML series' own multi-page organization guidance covered, now carrying an actual AI feature rather than static content.

Next: streaming responses in a website chatbot widget — real-time, token-by-token display for a genuinely more responsive user experience.

VK

Vijay Kumar

Founder of TechPurAI — writing hands-on tutorials and honest tool breakdowns.

LinkedIn ↗
← previous12. Handling Tables and Scanned PDFsnext →14. Streaming Responses in a Website Chatbot Widget