This repository is now a pnpm workspace monorepo.
apps/
ai/ WebGL portfolio chat assistant
portfolio/ Current jeremiah.dev Next.js app
packages/ Shared packages and shared data for workspace apps
The existing site lives in apps/portfolio, and the repository root now owns the workspace-level scripts.
Install dependencies:
pnpm devRun the current site:
pnpm devOpen http://localhost:3000 to view the app.
Useful workspace commands:
pnpm dev:ai
pnpm dev:portfolio
pnpm build:ai
pnpm build
pnpm lint:ai
pnpm build:portfolio
pnpm lint
pnpm sanity:seedThe portfolio assistant lives in apps/ai.
Local setup:
cp apps/ai/.env.example apps/ai/.env.local
pnpm dev:aiRequired environment variables for apps/ai:
NEXT_PUBLIC_TURNSTILE_SITE_KEY=
CLOUDFLARE_TURNSTILE_SECRET_KEY=
OPENAI_API_KEY=
OPENAI_MODEL=
ASSISTANT_SPEECH_SIGNING_SECRET=
ELEVENLABS_API_KEY=
ELEVENLABS_VOICE_ID=
ELEVENLABS_MODEL_ID=
ELEVENLABS_OUTPUT_FORMAT=Key assistant files:
- System prompt:
apps/ai/lib/prompt/system-prompt.ts - Approved Jeremiah profile context loader:
apps/ai/lib/context/profile.ts - Approved Jeremiah profile context data:
packages/profile-data - Approved Jeremiah context tool:
apps/ai/lib/context/context-tools.ts - Speech pipeline helpers:
apps/ai/lib/speech - Assistant response API route:
apps/ai/app/api/assistant/respond/route.ts - Conversation request processing:
apps/ai/lib/conversation - OpenAI service:
apps/ai/lib/openai/service.ts - Turnstile verification utility:
apps/ai/lib/turnstile/verify.ts - Speech normalization:
apps/ai/lib/speech/normalization.ts - Speech authorization:
apps/ai/lib/speech/authorization.ts - Text-to-speech service:
apps/ai/lib/assistant/text-to-speech.ts - TTS API route:
apps/ai/app/api/assistant/speech/route.ts
Request flow:
- The client submits the current message, recent validated history, and a Turnstile token to
app/api/assistant/respond/route.ts. - The route validates the body, applies rate limiting, and verifies the Turnstile token with Cloudflare.
- Only after Turnstile succeeds does the route call the isolated OpenAI service.
- The OpenAI service builds the assistant instructions from the dedicated system prompt and approved Jeremiah profile context, then calls the Responses API with
OPENAI_MODEL. - The assistant response route signs the exact assistant text with
ASSISTANT_SPEECH_SIGNING_SECRETand returns thatspeechTokenalongside the text response. - The client renders the assistant text immediately and requests speech only when the visitor presses the voice control.
- The protected
app/api/assistant/speech/route.tsverifies the signed token, normalizes the assistant text for speech, applies TTS-specific rate limiting, and then streams MP3 audio from ElevenLabs.
TTS notes:
- Arbitrary visitor text is not synthesized. The speech route accepts only assistant text paired with a valid signed token generated by the assistant response route.
ELEVENLABS_API_KEYis the standard variable name, but the current implementation also acceptsELEVEN_LABS_KEYas a local compatibility fallback.ELEVENLABS_VOICE_IDmust be set to your cloned voice ID from ElevenLabs.ELEVENLABS_MODEL_IDdefaults toeleven_multilingual_v2.ELEVENLABS_OUTPUT_FORMATdefaults tomp3_44100_128.- Voice defaults live in
apps/ai/lib/speech/config.ts. - To disable TTS locally, leave
ELEVENLABS_VOICE_IDor the API key unset. - The current tests mock provider requests, so
pnpm --filter @jeremiah-dev/ai testdoes not spend ElevenLabs credits.
You can start editing the portfolio site by modifying apps/portfolio/app/page.tsx, or the AI assistant by modifying apps/ai/app/page.tsx.
This project uses next/font to automatically optimize and load Inter, a custom Google Font.