Project Info
Tavus Interview Demo
100% client-side Tavus integration for hackathon demos. No server code, no API routes, fully modular for embedding in Creao.
⚠️ Security Warning
This demo uses NEXT_PUBLIC_* environment variables, which means your Tavus API key is exposed in the browser. This is acceptable for hackathon demos but must not be used in production. In production, always proxy Tavus API calls through your server to protect credentials.
Quick Start
1. Install dependencies
npm install
# or
pnpm install
2. Set up environment variables
Copy the example file and add your Tavus API key:
cp .env.local.example .env.local
Edit .env.local:
# Required: Get your API key from https://platform.tavus.io
NEXT_PUBLIC_TAVUS_API_KEY=your_actual_api_key_here
# Optional: Reuse an existing persona instead of creating a new one each run
# NEXT_PUBLIC_TAVUS_PERSONA_ID=p1234567890
# Optional: Specify a replica ID
# NEXT_PUBLIC_TAVUS_REPLICA_ID=r1234567890
3. Run the dev server
npm run dev
# or
pnpm dev
Open http://localhost:3000 in your browser.
How It Works
Persona-First Flow
- Edit Persona & Context: Use the left panel to customize the interviewer persona and interview context as JSON
- Create or Reuse Persona:
- If
NEXT_PUBLIC_TAVUS_PERSONA_IDis set → reuse that persona - Otherwise → create a new persona from your JSON inputs
- If
- Create Conversation: The app calls Tavus to create a conversation with the persona
- Embed Interview: The conversation URL is embedded in an iframe with camera/mic permissions
- Event Bridge: Messages from the iframe are forwarded to the metrics panel
Architecture
┌─────────────────┐
│ Browser Only │ ← No server code
├─────────────────┤
│ TavusInterview │ ← React component
│ (client-side) │
├─────────────────┤
│ fetch() calls │ ← Direct to Tavus API
│ ↓ │
│ Tavus API │
│ ↓ │
│ Iframe embed │ ← conversation_url
└─────────────────┘
Components
<TavusInterview> React Component
Main interview component with props:
import { TavusInterview } from "@/components/TavusInterview";
<TavusInterview
persona={{
name: "Technical Recruiter",
systemPrompt: "You are a friendly technical recruiter...",
topics: ["experience", "skills"],
tone: "friendly",
followUpStyle: "balanced",
questionStyle: "hybrid",
maxQuestions: 5,
maxFollowUpsPerQuestion: 2,
attachContextFromInterview: true,
}}
context={{
company: "Acme Corp",
role: "Senior Engineer",
seniority: "Senior",
jdHighlights: ["5+ years backend", "API design"],
extraContext: "Fast-paced startup",
}}
autoplay={true}
onEvent={(message) => console.log(message)}
/>
<tavus-interview> Web Component
For embedding in non-React apps or Creao:
<script>
// Listen for events
window.addEventListener('tavus:event', (event) => {
console.log('Tavus event:', event.detail);
});
</script>
<tavus-interview
persona='{"name":"Recruiter","systemPrompt":"You are a friendly recruiter..."}'
context='{"company":"Acme","role":"Engineer"}'
autoplay="true"
></tavus-interview>
Note: The web component is automatically registered when you import useTavusWebComponent() in your app.
<MetricsPanel> Debug Component
Shows a scrolling log of all events:
import { MetricsPanel } from "@/components/MetricsPanel";
const [events, setEvents] = useState<UIMessage[]>([]);
<MetricsPanel events={events} />
Debug hook in browser console:
window.__pushMetric({
type: "note",
timestamp: Date.now(),
text: "Test event"
});
Data Contracts
PersonaInput
Defines the interviewer's behavior:
interface PersonaInput {
name: string;
systemPrompt: string;
topics?: string[];
tone?: "neutral" | "friendly" | "direct" | "challenging";
followUpStyle?: "balanced" | "deep-dive" | "rapid-fire" | "supportive";
questionStyle?: "behavioral" | "technical" | "hybrid";
maxQuestions?: number;
maxFollowUpsPerQuestion?: number;
attachContextFromInterview?: boolean;
}
InterviewContext
Describes the role and company:
interface InterviewContext {
company: string;
role: string;
seniority?: string;
jdHighlights?: string[];
extraContext?: string;
}
UIMessage
Event structure for metrics:
interface UIMessage {
type: "ready" | "connected" | "disconnected" | "error" | "note";
timestamp: number;
text?: string;
payload?: unknown; // For opaque iframe messages
}
Embedding in Creao
Option 1: React Component
import { TavusInterview } from "@/components/TavusInterview";
function MyCreaoPage() {
return (
<TavusInterview
persona={myPersona}
context={myContext}
autoplay={true}
onEvent={handleMetrics}
/>
);
}
Option 2: Web Component
import { useTavusWebComponent } from "@/components/TavusInterviewWebComponent";
function MyCreaoPage() {
useTavusWebComponent(); // Register once in your app
return (
<div>
<tavus-interview
persona={JSON.stringify(myPersona)}
context={JSON.stringify(myContext)}
/>
</div>
);
}
Tavus API Calls
All calls are made directly from the browser using fetch():
Base URL
https://tavusapi.com/v2
Headers
x-api-key: NEXT_PUBLIC_TAVUS_API_KEY
content-type: application/json
Create Persona
POST /personas
Payload built from PersonaInput + InterviewContext. Skipped if NEXT_PUBLIC_TAVUS_PERSONA_ID is set.
Create Conversation
POST /conversations
Payload: { persona_id, replica_id? }
Returns: { conversation_id, conversation_url }
Get Conversation (optional)
GET /conversations/{id}
For polling if needed.
CORS & Network Issues
If the venue network blocks direct calls to Tavus:
- Create a minimal proxy endpoint on your server
- Update
TAVUS_BASE_URLin lib/tavus-client.ts to point to your proxy - Move
NEXT_PUBLIC_TAVUS_API_KEYto a server-side env var
File Structure
tavus/
├── app/
│ ├── layout.tsx # Root layout
│ ├── page.tsx # Main demo page with editors
│ └── globals.css # All styling (no Tailwind)
├── components/
│ ├── TavusInterview.tsx # Main interview component
│ ├── MetricsPanel.tsx # Event log display
│ └── TavusInterviewWebComponent.tsx # Web component wrapper
├── lib/
│ └── tavus-client.ts # Tavus API client (fetch only)
├── types/
│ └── index.ts # TypeScript contracts
├── .env.local.example # Template for env vars
└── README.md # This file
Development Checklist
- Runs with
npm run devafter settingNEXT_PUBLIC_TAVUS_API_KEY - Toggle
NEXT_PUBLIC_TAVUS_PERSONA_IDto skip persona creation - Creating conversation returns
conversation_urlthat loads in iframe - Event log shows
ready,connected, and forwards iframe messages asnote - No server files, no API routes, no Tailwind, no extra libs
- Component props match data contracts exactly
- Web component
<tavus-interview>dispatchestavus:eventwithUIMessage
License
MIT (Hackathon demo - use at your own risk)
Analysis
View
Metric
- 1
Figures cover GitHub contributors during the hackathon window. A co-authored commit counts in full for each author, so per-member totals add up to more than the whole-team figures.
Technology
- CSSIn code
- HTMLIn code
- JavaScriptIn code
- Next.jsIn code
- OpenAIIn code
- ReactIn code
- Tailwind CSSIn code
- TypeScriptIn code
- Hugging FaceClaimed
8 of 9 appear in the indexed code. 1 claimed on Devpost could not be matched to code, which may simply mean the tool leaves no trace in the repository.
AI coding agents
- Claude CodeConfig
Detected from committed agent config files and commit authorship. Absence of a signal is not proof an agent was unused.
Codebase size
Source size
216 KB
Source files
40
Counts recognized source files only; vendored directories, binaries and lockfiles are excluded, so this is smaller than the repository on disk.
Repository
8wali8/Calhacks2
48 files · 353 KB · @ 943b351
Structure
Interface
10 files · 21%Screens, components and styles rendered to the user.
API & routing
1 file · 2%Request entry points: routes, handlers and controllers.
Application logic
12 files · 25%Domain rules, services and shared utilities.
Background jobs
1 file · 2%Work run outside a request: tasks, workers and schedules.
Supporting
Layers are inferred from where files sit in the tree, not from reading the code. A project that names its directories unconventionally will read oddly here — open the file browser to check anything the diagram implies.
Languages
- TypeScript50%
- Markdown44%
- CSS3%
- HTML2%
- JavaScript1%
- Shell1%
Share of indexed source by file size. Binary and vendored files are excluded.
Dependencies
package.json
npm · 16- @tavily/core
- @tensorflow-models/face-landmarks-detection
- @tensorflow/tfjs
- @xenova/transformers
- next
- openai
- react
- react-dom
- zod
- +7 more
Declared in the repository’s manifests at the indexed commit. A declared package is not proof it is used, and runtime dependencies are listed first.
This project’s features have not been analysed yet.
Export this project's context (description, README, evidence, key source files) to chat with an AI agent elsewhere.