07 / 15 PROJECT

Every project

Interactive AI Portfolio

The second version of this site, kept at v2.jacksmith.me now that this one has the apex. Its hero is a question box, answered by a model handed a system prompt assembled from my own structured data.

Open itRead the sourceWrite-up

The hero before anything is asked: name, two lines, and the question box part way through one of its animated placeholder suggestions.
DeepSeek
the only provider
14
messages of history sent
3
features per project in prompt
8
plates, each its own world

A site you can ask

The hero is a question box, sitting under the name and above the usual about, projects, skills and contact sections. Ask something and the answer streams back a token at a time, written in the first person as Jack. The widget keeps the conversation, renders markdown as it arrives, and watches for one marker, [CV_CARD], which it swaps for a card offering the two page CV and the one page version.

The prompt is assembled

Nothing about the prompt is hand written. loadContext reads public/context.json on the server, then overwrites its projects array with the canonical entries from lib/projects-data.ts, so the model cannot answer from a stale copy of a project. createSystemPrompt folds bio, skills, every project with its tech, links and dates, the experience, the awards and the contact details into one string.

The guardrails come in near the end, marked as overriding everything above them. They are the sharp edges: I have graduated, so never say final year; Recensorium is deployed but pre-launch, so never imply users or growth; MotionGen is not on the Asset Store; if you do not know a number, say so rather than estimating it.

Each project contributes only its first three features. The full set pushed the prompt past the 8k context window of the legacy fallback models, and that failure was silent: the moment the primary model was rate limited, the whole fallback chain turned into a 502. The candidate list is now one model with a 1M token window, so the cap is a leftover from an older constraint.

The browser only ever talks to /api/ask on the same origin.

The key stays server side

The browser only ever talks to /api/ask on the same origin. That route reads the API key from the environment, calls DeepSeek with streaming on, and re-frames the server sent events as its own before writing them out, so the key is never in anything the client can read. History is filtered for shape, each message is clipped to 6000 characters, and only the last fourteen messages are sent.

Failure is handled rather than absorbed. A 429 gets one wait of 1.5 seconds and one retry, then it is surfaced to the client as a 429 with a readable message instead of hanging. The model that answered comes back in an X-Model-Used header. Output passes through an entity decoder on the way out, because models sometimes emit an escaped ampersand where a plain one was wanted.

Rendering while it streams

The markdown renderer is a small parser inside the widget. Each line is escaped first, then inline code, bold, italics, markdown links and bare URLs are rewritten into placeholder tags, the string is split on those tags, and every token is mapped to a real React element. Nothing is ever handed to dangerouslySetInnerHTML.

Streaming makes ordinary React patterns expensive. The CV card lives at module scope rather than in the render body, because a component declared in the body is a new type on every token and would remount the card on every chunk of the answer. The placeholder animator has the same shape of problem: typing in the box bumps a run id, which invalidates every pending wait already queued inside the typing loop.

Eight plates and a hero

The rebuild that replaced it is the page you are reading: a scroll spine of a hero and eight plates. Each plate has its own palette and its own world running behind the type, and no world is used twice. The interactive pieces are the thing itself rather than a picture of one: a trained 784-64-10 network you can draw a digit into, and the real coordinates of the hitchhiking route. A pixel sparrow called Pip perches on marked up furniture and rides the page as it scrolls.

What it does

  1. Real-time streaming AI chat on DeepSeek V4 Flash, with conversation memory and rate-limit retry
  2. RunId-based deterministic placeholder suggestion animator (no overlap)
  3. Lightweight custom markdown renderer with links, code & lists
  4. Dynamic system prompt built from structured JSON context & project injection
  5. Responsive glass UI with particle & hero entrance animations (GSAP)
  6. Server-side streaming proxy that keeps the model key off the client entirely
  7. Type-safe project metadata with extended feature lists
  8. Optimized minimal message rendering and scroll management

Built with

  • Next.js
  • React
  • TypeScript
  • Tailwind CSS
  • GSAP
  • LLM Streaming
  • DeepSeek
  • Markdown
  • Node.js
  • Edge Patterns
  • AI
  • LLM
  • API