Live. Open to the public with real users or real money. You are looking at it. Source: github.com/ayush108108/my-dev-portfolio-website.
Back to Case Studies
Benchmark
Full-Stack
RAG / AI
Three.js / WebGL
Next.js App Router

ayushv.dev — The Portfolio PlatformFull-Stack Architecture: AI Bot · WebGL Scene · Voice Avatar · Email CTA

A personal portfolio platform built without a template. Layers range from a procedural WebGL sky to a retrieval-based chat assistant.

20+
Pages / Routes
9
WebGL Layers
7
AI Endpoints
ElevenLabs → Groq
TTS Fallbacks
Lighthouse score not published
Performance

System Context

Six external systems integrate into the platform. Visitors interact via browser, email, and voice. Ayush manages the knowledge base via token-protected admin endpoints. No third-party CMS — all content is code.

Container Architecture

The frontend is a Next.js App Router SPA deployed on Vercel with static export for blazing CDN delivery. The backend is a standalone FastAPI service on Heroku — independently deployable, independently scalable.

Why separate backend?

FAISS indexing, Groq calls, and Simli streaming are CPU/IO-bound tasks unsuitable for Edge Runtime. A persistent Heroku dyno keeps the FAISS index warm in memory between requests.

Static export + streaming

Next.js `output: export` gives a pure static CDN site. Dynamic behaviour (chat, voice) proxies to the Heroku backend — best of both worlds: static page delivery with a separate AI backend.

Three.js / WebGL Hero Scene

The entire hero background is a 9-layer WebGL pipeline written in React Three Fiber. It runs at adaptive DPR (1–1.5) with no external assets — everything is procedurally generated in GLSL.

MilkyWay Shader

  • ›6-octave FBM star density
  • ›Domain-warped nebula clouds (4 colours)
  • ›Galactic core glow + dust lane absorption
  • ›8 hand-placed hero stars with 4-ray diffraction spikes
  • ›3 layers of fine star spray

Meteor System

  • ›Outer glow quad — Gaussian width profile
  • ›Inner core quad — power-curve sharp fade
  • ›Nucleus sphere — blooms through post-processing
  • ›Cross halo — 4-spike star at head
  • ›Dual-colour sparks: hot orange + cold blue

Post-Processing Pipeline

  • ›Bloom — luminance threshold 0.7, mipmap blur
  • ›Vignette — 0.3 offset, 0.5 darkness
  • ›Film Grain — 2% additive noise overlay
  • ›ACES Filmic Tone Mapping
  • ›Multisampling=0 for perf (MSAA replaced by bloom blur)

AI Chat Bot — RAG Flow

The chat widget is not a wrapper around a generic chatbot. It's a three-tier RAG system: FAISS vector retrieval → Groq LLaMA generation → navigation command injection. Every response is grounded in portfolio data.

Navigation Command Protocol

The LLM can emit [[NAVIGATE:/projects]] tokens in its response. The widget parses these, renders a navigation button, and performs client-side routing — no surprise redirects.

1// Navigation commands emitted by the LLM: [[NAVIGATE:/projects]]
2// Widget parses and pushes via Next.js router — no surprise redirects
3const navMatch = response.match(/\[\[NAVIGATE:(.*?)\]\]/);
4if (navMatch) {
5 const target = normalize_navigation_target(navMatch[1], userMessage);
6 router.push(target); // smooth SPA navigation, not a full reload
7}

JSON-LD Graph Injector

A tiny utility that keeps structured data composable across 20+ pages. Strips nested @context nodes and emits a single canonical @graph.

1// Single canonical @context — no duplicates across pages
2export function JsonLd({ data }: { data: Record<string,unknown> | Array<Record<string,unknown>> }) {
3 const payload = Array.isArray(data) ? data : [data];
4 const graphNodes = payload.map(({ "@context": _ctx, ...rest }) => rest);
5 const json =
6 graphNodes.length === 1
7 ? { "@context": "https://schema.org", ...graphNodes[0] }
8 : { "@context": "https://schema.org", "@graph": graphNodes };
9 return <script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(json) }} />;
10}

Voice Avatar — End-to-End

A single POST /converse round-trip handles: audio upload → Groq Whisper STT → RAG → LLaMA LLM → ElevenLabs TTS streaming → Simli lip-synced avatar. Transcripts arrive in response headers.

/converse endpoint

1# /converse — full voice-to-voice in one round trip
2@app.post("/converse")
3async def converse_endpoint(file: UploadFile = File(...)):
4 # 1. STT: Groq Whisper
5 user_text = await groq_voice.transcribe_audio(file)
6
7 # 2. RAG context retrieval
8 _, context = get_rag_context(user_text, config.RAG_TOP_K)
9
10 # 3. LLM generation (Groq LLaMA → OpenAI fallback)
11 ai_text = generate_answer(config.SYSTEM_PROMPT, context, user_text)
12
13 # 4. Stream TTS (ElevenLabs → Groq fallback) with transcripts in headers
14 return StreamingResponse(
15 synthesize_speech_stream(ai_text),
16 media_type="audio/mpeg",
17 headers={"X-Transcript": user_text[:2000], "X-Response": ai_text[:2000]},
18 )

TTS Fallback Chain

ElevenLabs is primary (neural quality). On failure, Groq TTS takes over. The `synthesize_speech_stream()` function handles provider switching transparently — the widget never knows which provider responded.

Simli Video Avatar

Simli receives the MP3 audio stream and returns a WebRTC video feed with frame-accurate lip-sync. Integrated via a dedicated WebSocket router in the FastAPI backend. Face ID configurable via env var.

Email CTA — Payload & UX

The contact form uses a mailto deep-link to the founder email alias. No backend involved — the visitor's own mail client sends the message, so no message content is stored on this site.

1// mailto deep-link — zero setup, opens the visitor's mail client
2const emailAddress = "ayush@correlatex.com"; // founder email alias
3const mailtoLink = `mailto:${emailAddress}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(message)}`;
4
5window.location.href = mailtoLink;

mailto Link Anatomy

mailto:ayush@correlatex.com
► subject and body are URL-encoded parameters
► Opens the visitor's own mail client
► No server round-trip — the site never sees the message

Why mailto

  • ›Zero infrastructure and zero tracking on the message path
  • ›One public address: the founder alias, not a personal mailbox
  • ›Works on desktop and mobile without an account

SEO Architecture

A composable SEO layer that prevents the most common structured-data bugs. One @context per page head, always.

Schema Types
Person, WebPage, FAQ, BreadcrumbList, WebSite, Organization
Sitemap
Dynamic TypeScript — enumerates all static + slug routes at build
Robots
Per-environment: crawl full site in prod, disallow all in dev

Admin & RAG Management

Token-protected admin endpoints allow hot-reindexing the FAISS knowledge base and querying structured chat logs — all without restarting the dyno.

POST /admin/reindex

Rebuild FAISS index from data/ sources. Reloads into memory in-process.

POST /admin/ingest

Partial ingestion with custom payload (subset of sources or URLs).

GET /admin/rag-status

Returns mode (remote-vector / local-vector / lexical-fallback), metadata count, index path.

GET /admin/logs

Paginated chat log query from SQLite. Includes retrieved chunk IDs and scores.

Key Design Decisions

01
Three-tier RAG degradation

remote-vector → local-vector (FAISS) → lexical keyword match → extractive fallback. The bot never goes down even if the embedder fails to load.

02
Static export + streaming API separation

Static pages and the streaming chat backend are deployed separately. No response-time benchmark or uptime guarantee is published.

03
Navigation protocol in LLM responses

Instead of parsing free-text intent on the client, the LLM is instructed to emit structured [[NAVIGATE:...]] tokens that the widget handles deterministically. No surprise page changes.

04
Dual-provider TTS with streaming

ElevenLabs has the best voice quality but can be unavailable. Groq TTS is the instant fallback. Both use streaming (StreamingResponse) so audio playback starts within 300ms of the LLM finishing.

05
FAISS over pgvector for portfolio scale

For a ~500 chunk knowledge base, in-process FAISS is faster than a Postgres round-trip and requires zero additional hosting. The index fits in memory on the smallest Heroku dyno.

06
Email via mailto (no API)

A contact form backend would store messages and add surface area. A mailto link opens the visitor's mail client with a pre-filled subject and body — instant, free, and the message never touches this site's servers.