← Back to blog

How I Built an AI Portfolio Assistant That Actually Knows My Work

Most portfolio chatbots are static FAQ widgets. I built one that reads live data from my CMS — projects, blog posts, services — and answers with real context. Here is the architecture, trade-offs, and why I skipped a vector database.

1/6/2026

The problem with most portfolio chatbots

When visitors land on a developer portfolio, they usually have a few questions:

  • What services do you offer?
  • Have you built something like my project?
  • How do I hire you?

A static contact form does not answer those in the moment. A generic ChatGPT embed does not know your projects, rates, or case studies.

I wanted something in between: an AI assistant that feels conversational, but is grounded in my real portfolio data — not hallucinated experience.

Try it now: open the chat widget in the bottom-right corner of this site while you read.


What I built

A lightweight AI assistant embedded in my Next.js portfolio that:

  1. Pulls live context from Supabase (site settings, projects, blog posts)
  2. Merges that with admin-configurable FAQs and custom knowledge
  3. Sends a structured system prompt to OpenAI on every request
  4. Returns concise, plain-text answers with guardrails against inventing facts

No vector database. No nightly embedding jobs. No separate AI platform.


Architecture overview

The flow is straightforward:

Visitor → Chat Widget → /api/chat → Context Builder → OpenAI → Response

Here is how the pieces map to my stack:

LayerTechnologyRole
FrontendNext.js + ReactFloating chat UI, message history
APINext.js Route HandlerAuth-free public endpoint, input sanitization
CMSSupabase (PostgreSQL)Projects, blog posts, site settings (JSONB)
AIOpenAI API (gpt-4o-mini)Chat completions with dynamic system prompt

(Optional: add a simple ASCII or image diagram here — Browser → Next.js API → Supabase + OpenAI)


Step 1: The chat widget (frontend)

The widget is a client component that:

  • Opens as a floating panel (bottom-right)
  • Keeps conversation history in React state
  • POSTs messages to /api/chat
  • Excludes the greeting message from the API payload (only real conversation goes upstream)

Key design choices:

  • No streaming (for now) — simpler error handling for a portfolio use case
  • Plain text responses — the assistant is instructed not to use markdown, which keeps the UI clean without a markdown renderer
  • Configurable greeting — loaded from site settings so I can change tone without redeploying

The widget loads server-side via a small loader component that reads the greeting from CMS settings and personalizes it with my name.


Step 2: The API route — security before intelligence

Before calling OpenAI, the API route validates and sanitizes input:

  • Accepts only user and assistant roles
  • Caps history to the last 12 messages (prevents runaway token usage)
  • Truncates each message to 1,000 characters
  • Requires the last message to be from the user
  • Returns 503 if OPENAI_API_KEY is missing (fail gracefully, not silently)

Model settings I chose:

  • Model: gpt-4o-mini (configurable via env)
  • Temperature: 0.4 — factual but not robotic
  • Max tokens: 400 — keeps answers short; portfolio visitors want clarity, not essays

This is production-minded defaults for a public-facing endpoint with no login.


Step 3: Dynamic context — the part most tutorials skip

The core idea lives in a function that builds the system prompt on every request by reading fresh data from Supabase.

What gets injected automatically

SourceWhat the AI sees
Site settingsName, title, about, education, specialties
ServicesEach service title + description
Tech stackGrouped by category (Frontend, Backend, etc.)
ProjectsTitle, summary, technologies, status, case study URL, truncated MDX content
Blog postsTitle, excerpt, slug URL, truncated content
ContactEmail, LinkedIn, GitHub, Upwork, Freelancer

Project and blog content is stripped from MDX/markdown to plain text and truncated (roughly 400–500 chars) so the prompt stays within reasonable size.

What I configure manually in admin

Through an Assistant settings page in my admin panel:

  • Custom knowledge — rates, availability, niches, background not in CMS
  • FAQ pairs — precise Q&A the model should prefer over guessing
  • Custom instructions — tone, hiring CTAs, discovery call mentions
  • Greeting — first message visitors see
  • Context preview — read-only view of the full system prompt before it hits OpenAI

This hybrid model is the main architectural decision: structured CMS data + human-curated FAQs, not embeddings.


Step 4: Guardrails that matter for a portfolio

I added explicit rules in the system prompt:

  • Answer only about me, my services, projects, and how to hire
  • Do not invent projects, clients, metrics, or experience
  • Redirect off-topic questions politely
  • For hiring inquiries, mention relevant services and contact options
  • Use plain text only — no markdown syntax in replies

For a portfolio assistant, accuracy beats creativity. A hallucinated client reference is worse than saying "I don't have details on that — contact me directly."


Why I did not use RAG (and when you should)

Retrieval-Augmented Generation (vector search over documents) is the default recommendation for AI chatbots. I deliberately skipped it because:

My situationWhy structured context works
Small, structured dataset~5–20 projects, handful of blog posts
Data already in PostgreSQLSlugs, summaries, tech tags are queryable
Content updates frequentlyNew blog post → instantly in prompt on next request
Low document volumeTotal context fits comfortably in a system prompt

Consider RAG when:

  • You have hundreds of PDFs, docs, or support tickets
  • Answers require semantic search across unstructured text
  • Context exceeds model window even after summarization
  • You need citation of specific document sections

For a portfolio or small SaaS marketing site, CMS-driven context injection is often enough — faster to build, easier to debug, no embedding pipeline to maintain.


How blog posts make the assistant smarter

Every published blog post is automatically included in the assistant's context.

When I publish this article, the assistant will know:

  • The title and excerpt
  • A truncated plain-text summary of the content
  • The URL path (/blog/ai-portfolio-assistant-architecture)

That creates a flywheel: write expertise → assistant can discuss it → visitors get deeper answers → more reason to hire.

No manual sync step. Publish in admin, refresh the context preview, done.


Trade-offs and limitations

Being honest about what this does not do:

  1. No conversation persistence — refresh the page, history is gone (acceptable for portfolio; would need Redis/DB for support use cases)
  2. No user authentication — public endpoint needs rate limiting in production (add middleware or edge rate limiter)
  3. No streaming — slight delay before full response appears
  4. Token ceiling — very long project write-ups are truncated; full case studies live on /projects/[slug]
  5. Single provider — OpenAI only today; multi-provider fallback is a future improvement

These are conscious MVP choices, not oversights.


What I would add next

If I were productizing this for clients:

  1. Rate limiting per IP/session on /api/chat
  2. Streaming responses for better UX
  3. Analytics — log top questions to improve FAQ pairs
  4. Tool calling — let the assistant fetch a specific project or blog post on demand instead of stuffing everything in the prompt
  5. Optional RAG layer for clients with large knowledge bases

Key takeaways

  1. Start with structured context before reaching for vector databases
  2. Ground the model in CMS data so content updates propagate automatically
  3. Guardrails are non-negotiable for public-facing AI tied to your reputation
  4. Admin-configurable FAQs fill gaps structured data cannot cover (pricing, availability)
  5. Blog + assistant compound — each post makes the bot more useful

Try it yourself

Open the chat widget on this site and ask:

  • "What AI projects have you built?"
  • "How can I hire you?"
  • "What's your tech stack?"
  • "Tell me about your customer support platform"

If you are building something similar — an AI-powered portfolio, SaaS onboarding bot, or internal knowledge assistant — get in touch or find me on Upwork.