A production-ready boilerplate for AI-powered voice intake workflows. Built with Next.js, TypeScript, Tailwind CSS, shadcn/ui, and the Vapi Web SDK.
The app implements a four-step voice intake flow: Setup → Voice Session → Review → Export, connected to Vapi for real-time voice conversations.
npm install
cp .env.local.example .env.local
# Add your Vapi keys to .env.local
npm run devOpen http://localhost:3000.
Create a .env.local file:
NEXT_PUBLIC_VAPI_PUBLIC_KEY=your_vapi_public_key
NEXT_PUBLIC_VAPI_ASSISTANT_ID=your_assistant_id # optional| Variable | Required | Description |
|---|---|---|
NEXT_PUBLIC_VAPI_PUBLIC_KEY |
Yes | Your Vapi public API key from the Vapi dashboard |
NEXT_PUBLIC_VAPI_ASSISTANT_ID |
No | Pre-configured Vapi assistant ID. If omitted, an inline assistant config is used. |
If the public key is not set, the setup screen shows a configuration guide and disables the start button.
src/
├── app/ # Next.js App Router
│ ├── layout.tsx
│ └── page.tsx # Main page with step routing
├── components/
│ ├── setup/ # Session config form + system status panel
│ ├── voice-session/ # Call controls, progress sidebar, live notes
│ ├── transcript/ # Real-time transcript panel with search
│ ├── summary/ # Structured review with inline editing
│ ├── export/ # Export with copy/download + JSON preview
│ ├── step-indicator.tsx # Wizard progress bar
│ └── event-log.tsx # Debug event log drawer
├── lib/
│ ├── schemas.ts # Zod schemas for all data shapes
│ ├── templates.ts # Intake template definitions (configurable)
│ ├── transcript-utils.ts # Formatting, search, plain-text export
│ ├── summary-mapper.ts # Export generation, completion calculation
│ ├── env.ts # Environment variable access
│ └── vapi/
│ ├── types.ts # VapiAdapter interface
│ ├── live-adapter.ts # Real Vapi Web SDK wrapper
│ └── index.ts # Barrel export
└── store/
├── session-store.ts # App step, call status, config
├── transcript-store.ts # Transcript entries
└── summary-store.ts # Section data and extractions
- Adapter pattern for Vapi: Voice integration goes through a
VapiAdapterinterface (lib/vapi/types.ts). The live implementation wraps@vapi-ai/web. Swap voice providers by implementing the same interface. - Template-driven sections: Intake templates are plain TypeScript objects in
lib/templates.ts. Add new templates by adding entries to the array — no code changes needed elsewhere. - Zustand stores: Three focused stores (session, transcript, summary) keep state simple and avoid prop drilling.
- Zod schemas: All data shapes are validated with Zod and used as the source of TypeScript types.
- The
VapiAdapterinterface defines:init,start,stop,setMuted,on(events), anddestroy. createLiveAdapter(publicKey)creates a Vapi Web SDK instance and maps its events:call-start/call-end→ call status updatesmessage(transcript type, final) → transcript entrieserror→ error handling
- If an
assistantIdis provided,start()uses that. Otherwise it creates an inline assistant with a default system prompt, voice, and first message. - The adapter is initialized in
VoiceSessionViewand wired to Zustand stores.
| Feature | Status |
|---|---|
| Email delivery | UI placeholder — wire your email API |
| Section extraction | Manual for now — plug in your NLP/LLM extraction |
| Section progress tracking | Updates from store — connect to extraction logic |
Edit src/lib/templates.ts and add an entry to the templates array. Each template needs id, name, description, and a sections array.
In VoiceSessionView, subscribe to transcript updates and call useSummaryStore.updateSection() to populate sections with extracted content as the conversation progresses.
Implement the VapiAdapter interface from lib/vapi/types.ts for your provider, then update the adapter creation in VoiceSessionView.
Replace the placeholder in ExportView with a call to your email API (e.g., Resend, SendGrid, or a Next.js API route).
- Next.js 16 (App Router, Turbopack)
- TypeScript
- Tailwind CSS 4
- shadcn/ui
- Zustand
- Zod
- Framer Motion
- Vapi Web SDK
- Lucide Icons