Designing Conversational AI: How I Built DiegoAI for My Portfolio

Most AI integrations are a chat box dropped into a page. I wanted something different — an assistant that represents me, speaks in my voice, and feels like a natural part of the portfolio rather than a bolt-on feature. This is the full story: the design decisions, the technical architecture, and what I learned building a streaming AI chat from scratch.

Why build it at all?

A portfolio is a static thing. You read it, you form an impression, you leave. The problem is that the most interesting parts of my work are in the decisions — the tradeoffs, the hypotheses, the things that didn't make it into the final design. A case study can only hold so much.

DiegoAI is an attempt to make the portfolio interactive. Ask it why I made a specific decision. Ask it what I'm looking for in a role. Ask it what Pepe the dachshund has to do with product design. It knows the answers because I wrote the knowledge base.

It's also a demonstration. As a designer, I think a lot about conversational interfaces — onboarding flows, activation moments, trust signals. Building one from scratch meant I had to put those principles into practice.

The design brief

Before writing a line of code, I defined what this thing needed to be:

Ambient, not intrusive

The chat shouldn't compete with the portfolio content. It should be discoverable for those who want it, invisible for those who don't. A floating action button at the bottom right — large enough to notice, small enough not to obstruct.

About me, not about projects

I didn't want a per-case-study Q&A. I wanted one assistant that knows everything — my career, my thinking, my results, my personality. One source of truth.

Fast and real-time

Nothing kills conversational UX faster than waiting for a full response to load. Streaming was non-negotiable. The text should appear as it's generated, not arrive all at once after a delay.

Mobile-native

On mobile, a floating button at the bottom right is awkward. So on mobile, the chat disappears entirely and lives instead inside the pill navigation as an avatar button. Tap the avatar, get a full-screen bottom sheet. Different entry point, same experience.

The technical architecture

The site runs on Astro — a static site generator that also supports server-side rendering for individual routes. The chat API is a single serverless function at /api/chat with prerender = false, which tells Astro to handle it server-side. Everything else on the site is static HTML.

The function takes a messages array from the client, injects a system prompt, and calls OpenAI's GPT-4 with streaming enabled. It returns a ReadableStream with content-type text/plain. The client reads the stream chunk by chunk and updates the UI as each token arrives.

The infrastructure is Vercel. I started on GitHub Pages, but static hosting can't run serverless functions. The migration to Vercel was straightforward — swap the adapter in the Astro config, set the API key in Vercel's environment variables, done.

The system prompt as product design

The system prompt is where most of the design work lives. It's not just instructions — it's a knowledge base, a personality guide, and a set of constraints, all in one.

I wrote it to cover: who I am (Spanish, based in Berlin, not Chilean — this matters), my full career history, key results with specific numbers, how I think about design, what I'm looking for in a role, and personal details like the dog and the rugby.

The tone instruction was the hardest to get right. I wanted responses that matched my voice: direct, no buzzwords, human but professional. I also needed to suppress markdown — the model wanted to respond with asterisks and hashes, which would render as raw symbols in a chat UI that wasn't expecting markdown. The fix was explicit: "Use plain text only — no markdown symbols like ** or ##."

Even with that instruction, the model occasionally slipped into markdown. So I also added a client-side renderer that detects and converts markdown to HTML — belt and braces.

The UI: welcome state and chips

The chat panel opens with a welcome state: my avatar, a short question ("What would you like to know?"), and four preset chip buttons. The chips lower the barrier to first interaction — users don't have to think of a question, they can just tap one.

Chips I chose: "What's your background?", "What are you working on?", "What are you looking for?", "Tell me something unexpected." Each one is a question I'd want a recruiter or collaborator to ask. They guide the conversation toward the most relevant information.

Once the first message is sent, the welcome state disappears and the messages area takes over. Clean transition. No dead space.

Typing animation and streaming

While the response is loading, three animated dots appear — a standard typing indicator. Pure CSS: three spans, each with a bounce keyframe animation offset by 0.15s. No JavaScript needed for the animation itself.

When the stream starts, the typing dots are replaced by the message element. The client reads the stream with getReader(), decodes each chunk with a TextDecoder, appends it to an accumulated string, and sets innerHTML to the rendered result on every chunk. The message grows in real time.

The rendering step converts the accumulated text to safe HTML: escape first (to prevent XSS), then convert markdown patterns to HTML tags. Bold, italic, lists, newlines — all handled. The escaping always runs before the HTML conversion, so user-injected content can't break the DOM.

The FAB: attention without noise

The floating action button is 84×84px, positioned bottom-right. Large enough to read as intentional, not accidental. It shows my avatar — immediately personal, immediately clear who you're talking to.

Every six seconds, it pulses — a subtle scale animation that draws the eye without being annoying. On hover, a speech bubble tooltip appears: "I'm Diego AI. Ask me anything :)". This resolves the most common question a user would have: what is this?

The cursor changes when you hover the FAB: a white speech bubble SVG instead of the standard dot cursor. Small signal, but it reinforces the conversational affordance.

On mobile, the FAB is hidden entirely. The entry point moves to the pill navigation avatar. Tapping it dispatches a custom browser event (open-diego-ai), which the chat component listens for. The two components are decoupled — Nav doesn't know about Chat, Chat doesn't know about Nav. The event is the contract.

Mobile: the bottom sheet pattern

On mobile, the chat opens as a full-screen bottom sheet. It starts at translateY(100%) (off-screen below) and animates to translateY(0) on open. A backdrop overlay behind it can be tapped to close. This is the standard mobile sheet pattern — familiar, predictable, thumb-friendly.

The sheet takes the full viewport width and most of the height. On desktop, the panel is a fixed 360px wide box anchored to the bottom right. Same component, two completely different presentations via CSS alone.

What I learned

The system prompt is the product

The quality of an AI assistant is almost entirely determined by the system prompt. The model is capable — your job is to constrain it correctly. Tone, knowledge, format, scope. Everything that matters lives there.

Streaming UX is table stakes

Non-streaming AI chat feels broken in 2025. Users expect to see text appear in real time. If your AI chat makes people wait for a full response, they'll assume it's slow or broken. Streaming is not optional.

Mobile-first means different entry points

Resizing a desktop UI for mobile isn't mobile-first. The FAB that works on desktop is awkward on mobile. The right answer was a different entry point entirely — the pill nav avatar. Same destination, designed for the context.

Conversational design is product design

Choosing the preset chips, writing the system prompt, deciding when the welcome state disappears — these are onboarding design problems. The same principles apply: value before commitment, reduce friction at first interaction, trust signals at decision points.

DiegoAI is live on this portfolio. Open the chat and ask it something — it knows more than the case studies do.

Try DiegoAI → Back to insights