Behind the build

Project Assistant
Chatbot - Docs.

A complete breakdown of how the floating chat widget on the Projects page was built - from the bubble button to the AI response, the serverless function, and everything in between. The concepts apply to any hosting platform.

01
What the Chatbot Does

The Project Assistant is a floating chat widget that appears exclusively on the Projects page (Page 3) of the portfolio. Visitors can ask it anything about the projects on display and it responds with intelligent, contextual answers - powered by Claude AI.

How visitors use it
  • A floating amber bubble appears in the bottom-right corner of Page 3
  • Clicking the bubble opens a compact chat panel
  • Quick-question chips let visitors tap a question without typing
  • Visitors can also type their own free-form questions
  • Claude reads a system prompt about all the projects and responds accordingly
  • The conversation history is maintained during the session
What it looks like
Project Assistant - Ask me anything
Hi! I'm familiar with all of Trea's projects. What would you like to know?
What's the Email Agent?
The Email Agent is an AI-powered tool that parses raw business inquiry emails and returns structured data - sender info, urgency level, request type, and a suggested next action. It's built with Claude AI and deployed on Netlify.
What's Purrfect Health? Tell me about the Email Agent Which project is best for me?
02
The Tech Stack

Like the Email Parser, this chatbot is built with nothing but web-native tools - no frameworks, no dependencies on the frontend.

Frontend
  • Plain HTML, CSS, and vanilla JavaScript - no React, no Vue
  • The chat widget lives directly inside index.html as a floating overlay
  • CSS animations handle the open/close transitions and the pulsing dot
AI / Backend
  • Claude AI via the Anthropic API - the same model powering the Email Parser
  • A serverless function (chat.mjs) handles the API call securely - works on Netlify, Vercel, Cloudflare Workers, or any serverless platform
  • The system prompt - describing all your projects - lives server-side only
Hosting
  • Any static hosting platform that supports serverless functions - same site as your portfolio, no separate deployment needed
  • The function URL varies by platform - e.g. /.netlify/functions/chat on Netlify, /api/chat on Vercel
HTML CSS JavaScript Claude AI Serverless Functions Node.js ESM
03
How It Works - The Full Flow

Here is exactly what happens from the moment a visitor clicks the bubble to when they see a reply:

Step 01
Visitor types

User types a question or taps a chip. JS captures the input.

Step 02
POST request

JS sends the full conversation history to your serverless function endpoint.

Step 03
Claude replies

The function adds the system prompt and calls the Anthropic API.

Step 04
UI updates

The reply is added to the chat panel. History is stored in memory.

The fetch call - what the frontend sends
const res = await fetch('/your-function-endpoint/chat', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ messages: history })
  // e.g. /.netlify/functions/chat or /api/chat depending on your platform
});

The full conversation history is sent every time - this is how Claude remembers what was said earlier in the session. Claude has no memory between sessions, so history only lasts as long as the page is open.

04
The Serverless Function (chat.mjs)

This is the most important file. It's a small Node.js module that sits between your visitors and the Anthropic API. Nothing secret ever touches the browser. The code is the same regardless of platform - only the file location and function URL change.

File location by platform
netlify/functions/chat.mjs  // Netlify
api/chat.js  // Vercel
functions/chat.js  // Cloudflare Workers
What it does
  • Receives the conversation history from the frontend POST request
  • Prepends the system prompt - which describes all your projects in detail
  • Calls the Anthropic API with your secret API key from environment variables
  • Returns Claude's response as JSON back to the browser
Basic structure of chat.mjs
import Anthropic from '@anthropic-ai/sdk';

export default async (req) => {
  const { messages } = await req.json();
  const client = new Anthropic();

  const response = await client.messages.create({
    model: 'claude-sonnet-4-20250514',
    max_tokens: 1024,
    system: YOUR_SYSTEM_PROMPT, // describes your projects
    messages: messages
  });

  return Response.json(response);
};
Environment variable required
ANTHROPIC_API_KEY=your-key-here
// Set in your platform's dashboard under Environment Variables
// Netlify: Site Settings → Environment Variables
// Vercel: Project Settings → Environment Variables
// Cloudflare: Workers → Settings → Variables
05
The System Prompt - Teaching Claude Your Projects

The system prompt is the secret sauce. It's a block of text that tells Claude who it is and what it knows before any visitor sends a message. It lives only in chat.mjs - never in the browser.

What a good system prompt includes
  • Role definition - "You are a helpful assistant for Trea Sule's portfolio..."
  • Tone instructions - "Keep answers concise, friendly, and 2–3 sentences max"
  • Project descriptions - a paragraph for each project: what it does, how it was built, what makes it interesting
  • Boundaries - "Only answer questions about the projects. For anything off-topic, politely redirect."
  • Contact info - so Claude can tell visitors how to reach you if they're interested
Example system prompt structure
"You are a project assistant for Trea Sule's portfolio.
Keep answers friendly and under 3 sentences.

PROJECTS:
- Email Parser: An AI agent that extracts structured data from
  business emails. Built with Claude AI + Netlify functions.
- Purrfect Health: A [description here]...
- GoodTea: A [description here]...

If asked something unrelated, say you can only help with
questions about Trea's projects."

The more detail you put in the system prompt, the better Claude's answers will be. Think of it as briefing a new team member on your work before they start answering visitor questions.

06
The Frontend - Widget & Bubble

The chat widget is built from three HTML elements and a block of JavaScript - all inside index.html. No separate files needed.

The three HTML pieces
  • The bubble button - a fixed amber circle in the bottom-right corner. Clicking it opens or closes the panel.
  • The chat panel - a floating card that slides up when opened. Contains the message list, chip buttons, and text input.
  • The message area - a scrollable div where bot and user messages are added dynamically by JavaScript.
Page-specific visibility
// The bubble only shows on Page 3 (Projects)
function syncBubble() {
  const activePage = document.querySelector('.page:not(.hidden)');
  if (activePage?.id === 'page3') {
    bubble.style.display = 'block';
  } else {
    bubble.style.display = 'none';
  }
}
Quick-question chips

The chips (e.g. "What's Purrfect Health?") are defined as a JavaScript array at the top of the script. When a chip is clicked, its text is sent directly to sendMessage() just like a typed message - no special handling needed.

const CHIPS = [
  "What's Purrfect Health?",
  "Tell me about the Email Agent",
  "Which project is best for me?",
];

To add or change chip questions, just edit this array. The UI rebuilds itself automatically every time the panel opens.

07
Conversation Memory - How It Works

Claude has no built-in memory between API calls - every call starts fresh. To make the conversation feel continuous, the JavaScript maintains a history array and sends the full conversation every time.

How the history array works
// Starts empty. Grows with every exchange.
let history = [];

// When user sends a message:
history.push({ role: 'user', content: text });

// When Claude replies:
history.push({ role: 'assistant', content: reply });

This array is passed to your serverless function on every request. Claude reads the full history each time, which is why it can refer back to earlier parts of the conversation.

Important limitation

History only lasts as long as the page is open. If the visitor refreshes or comes back later, the conversation starts fresh. For most portfolio use cases this is perfectly fine - visitors aren't looking for long-term relationships with a chat widget!

08
How to Recreate This on Your Own Site

The core concept works on any platform that supports serverless functions. This guide uses Netlify and Anthropic as the example since that's what this site runs on - but the same steps apply to Vercel, Cloudflare Workers, AWS Lambda, or any similar platform.

Step 1 - Get your Anthropic API key

Sign up at console.anthropic.com, create a project, and generate an API key. Keep it secret - it never goes in your HTML file.

Step 2 - Choose and set up your hosting platform

You need a platform that supports serverless functions - Netlify, Vercel, and Cloudflare Workers all have generous free tiers. Deploy your portfolio to whichever you prefer. All three detect your function files automatically with no extra configuration.

Step 3 - Add your API key as an environment variable

Every platform has a dashboard where you can store secret keys safely. Add ANTHROPIC_API_KEY with your key as the value. The name of the setting and where to find it varies slightly but is always under something like "Environment Variables" or "Secrets" in your project settings.

Step 4 - The Anthropic SDK ✦ Your platform handles this for you

You won't need to run npm install yourself. Most platforms automatically install any dependencies listed in your project's package.json during deployment - on their servers, not your machine. Make sure @anthropic-ai/sdk is listed as a dependency and your platform takes care of the rest.

// In your package.json - your platform reads this automatically
"dependencies": {
  "@anthropic-ai/sdk": "^0.20.0"
}
Step 5 - Create the serverless function file

Create your function file in the correct folder for your platform. Use the code structure from Section 04 as your starting point - the logic is identical, only the file path changes.

netlify/functions/chat.mjs  // Netlify
api/chat.js  // Vercel
functions/chat.js  // Cloudflare Workers
Step 6 - Write your system prompt

This is the most important step and the one that makes your bot actually useful. Write a detailed description of every project - what it does, the tech used, what makes it interesting. The more detail, the better Claude's answers. This lives inside your function file, never in the browser.

Step 7 - Add the widget HTML to your page

Add the bubble button, chat panel, and message area HTML to your index.html. Add the JavaScript at the bottom. Update the fetch URL to match your platform's function endpoint and update the chip questions to match your projects.

Step 8 - Deploy and test ✦ Your platform builds everything automatically

Push your changes or drag-and-drop your folder to your platform's dashboard. It will automatically install dependencies, deploy your function, and make it live. You don't run any build commands yourself. Once deployed, open your portfolio, find the Projects page, click the bubble, and ask it a question. If it answers correctly - you're done!