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:
- Pulls live context from Supabase (site settings, projects, blog posts)
- Merges that with admin-configurable FAQs and custom knowledge
- Sends a structured system prompt to OpenAI on every request
- 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:
| Layer | Technology | Role |
|---|---|---|
| Frontend | Next.js + React | Floating chat UI, message history |
| API | Next.js Route Handler | Auth-free public endpoint, input sanitization |
| CMS | Supabase (PostgreSQL) | Projects, blog posts, site settings (JSONB) |
| AI | OpenAI 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
userandassistantroles - 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
503ifOPENAI_API_KEYis 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
| Source | What the AI sees |
|---|---|
| Site settings | Name, title, about, education, specialties |
| Services | Each service title + description |
| Tech stack | Grouped by category (Frontend, Backend, etc.) |
| Projects | Title, summary, technologies, status, case study URL, truncated MDX content |
| Blog posts | Title, excerpt, slug URL, truncated content |
| Contact | Email, 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 situation | Why structured context works |
|---|---|
| Small, structured dataset | ~5–20 projects, handful of blog posts |
| Data already in PostgreSQL | Slugs, summaries, tech tags are queryable |
| Content updates frequently | New blog post → instantly in prompt on next request |
| Low document volume | Total 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:
- No conversation persistence — refresh the page, history is gone (acceptable for portfolio; would need Redis/DB for support use cases)
- No user authentication — public endpoint needs rate limiting in production (add middleware or edge rate limiter)
- No streaming — slight delay before full response appears
- Token ceiling — very long project write-ups are truncated; full case studies live on
/projects/[slug] - 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:
- Rate limiting per IP/session on
/api/chat - Streaming responses for better UX
- Analytics — log top questions to improve FAQ pairs
- Tool calling — let the assistant fetch a specific project or blog post on demand instead of stuffing everything in the prompt
- Optional RAG layer for clients with large knowledge bases
Key takeaways
- Start with structured context before reaching for vector databases
- Ground the model in CMS data so content updates propagate automatically
- Guardrails are non-negotiable for public-facing AI tied to your reputation
- Admin-configurable FAQs fill gaps structured data cannot cover (pricing, availability)
- 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.