02 · Multimodal assistant
Mercatus Assist & THINK
A C# / ASP.NET Core service and a React workspace that call Gemini from the server, keep the API key off the browser, and say so when the live path is down.
Live, with a labelled fallback
Challenge
A portfolio assistant is easy to fake with a browser key. The engineering problem was a real model call, a swap-able provider, a safe public surface, and an honest screen when the service is down.
Role and scope
Built the API, the proxy, and the workspace
- Shipped an ASP.NET Core 8 minimal API in C# that calls Gemini through an OpenAI-compatible chat completions client, so the provider can change in config.
- Ran that API as a systemd service on an Ubuntu VPS, behind nginx, with the static site behind Cloudflare.
- Put a same-origin proxy in front so the API key stays on the server.
- Built THINK as a React 19, Vite, and TypeScript workspace: chat, image, live voice over WebSocket, plus sites, code, and a learning path.
- Labelled the fallback. If the live robot is unhealthy, the UI says it is demo coaching instead of pretending.
Architecture
01
Experience
Mercatus Assist for guided chat. THINK for multimodal work: chat, image, live voice, sites, code, and learning.
02
Gateway
Same-origin proxy to the VPS. The browser never receives the provider key.
03
Robot
ASP.NET Core 8 minimal API. OpenAI-compatible client. Exponential backoff on HTTP 429 and 5xx.
04
Fallback
Health failure switches the surface to a labelled demo path. Live and demo are not the same state.
60–90 second demo
- Play the 60-second walkthrough on this page, then open Mercatus Assist.
- Ask one question. Note whether the reply is marked live or demo.
- Open THINK and try chat only. Live voice and image are capped per IP — that cap is the control, not a bug.
- If the robot is down, the honest outcome is the labelled fallback, not a blank error.
Flows to look at

Mercatus Assist
Live path through the VPS proxy. The banner names Gemini and the retry behavior. Feed copy inside THINK is product content, not a résumé claim.

THINK
React workspace: pulse, chat, live voice, sites, and code. The model key stays on the server.
AI behavior
- AI-assisted: one bounded model response for chat, image, or voice.
- Agentic: routing across approved surfaces (chat, voice relay, tool-style paths) with session context.
- Human-controlled: the visitor chooses the path. The service does not take external actions on their behalf.
- Observability: live versus demo is a visible state. Rate limits are per IP on chat, image, video, and live voice.
Security
- API key isolation on the VPS.
- Per-IP rate limits on the public multimodal paths.
- Provider is config-swappable, so a key rotation does not require a UI rewrite.
- No private prompts are published here to prove the architecture.
Monitoring
- The fallback itself is a health signal: visitors see demo mode instead of a silent failure.
- Process supervision is systemd on Ubuntu, with nginx in front and Cloudflare on the static site.
Limits
- THINK is a portfolio workspace with caps, not an unlimited production assistant.
- A public GitHub profile is not on the résumé. The service code is available in a walkthrough.
- Unit-test counts are not published. The public checks are the fallback label, the proxy boundary, and the retry policy.