Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 15 additions & 6 deletions components/Agent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { useRouter } from "next/navigation";

import { cn } from "@/lib/utils";
import { vapi } from "@/lib/vapi.sdk";
import { interviewer } from "@/constants";
import { generator, interviewer } from "@/constants";
import { createFeedback } from "@/lib/actions/general.action";

enum CallStatus {
Expand Down Expand Up @@ -118,12 +118,19 @@ const Agent = ({
setCallStatus(CallStatus.CONNECTING);

if (type === "generate") {
await vapi.start(process.env.NEXT_PUBLIC_VAPI_WORKFLOW_ID!, {
variableValues: {
username: userName,
userid: userId,
await vapi.start(
undefined,
{
variableValues: {
username: userName,
userid: userId,
},
clientMessages: ["transcript"],
serverMessages: [],
},
});
undefined,
generator
);
} else {
let formattedQuestions = "";
if (questions) {
Expand All @@ -136,6 +143,8 @@ const Agent = ({
variableValues: {
questions: formattedQuestions,
},
clientMessages: ["transcript"],
serverMessages: [],
});
}
};
Expand Down
196 changes: 195 additions & 1 deletion constants/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { CreateAssistantDTO } from "@vapi-ai/web/dist/api";
import { CreateAssistantDTO, CreateWorkflowDTO } from "@vapi-ai/web/dist/api";
import { z } from "zod";

export const mappings = {
Expand Down Expand Up @@ -97,6 +97,200 @@ export const mappings = {
"aws amplify": "amplify",
};

export const generator: CreateWorkflowDTO = {
name: "Generate Interview",
nodes: [
{
name: "start",
type: "conversation",
isStart: true,
metadata: {
position: {
x: 0,
y: 0,
},
},
prompt:
"Speak first. Greet the user and help them create a new AI Interviewer",
voice: {
model: "aura-2",
voiceId: "thalia",
provider: "deepgram",
},
variableExtractionPlan: {
output: [
{
title: "level",
description: "The job experience level.",
type: "string",
enum: ["entry", "mid", "senior"],
},
{
title: "amount",
description: "How many questions would you like to generate?",
type: "number",
enum: [],
},
{
title: "techstack",
description:
"A list of technologies to cover during the job interview. For example, React, Next.js, Express.js, Node and so on...",
type: "string",
enum: [],
},
{
title: "role",
description:
"What role should would you like to train for? For example Frontend, Backend, Fullstack, Design, UX?",
type: "string",
enum: [],
},
{
title: "type",
description: "What type of the interview should it be? ",
type: "string",
enum: ["behavioural", "technical", "mixed"],
},
],
},
},
{
name: "apiRequest_1747470739045",
type: "apiRequest",
metadata: {
position: {
x: -16.075937072883846,
y: 703.623428447121,
},
},
method: "POST",
url: `${process.env.NEXT_PUBLIC_BASE_URL}/api/vapi/generate`,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Ensure environment variable is properly defined.

The API URL uses ${process.env.NEXT_PUBLIC_BASE_URL} - make sure this environment variable is properly defined in all environments where this code will run.


🏁 Script executed:

#!/bin/bash
# Check for environment variable definition or usage
rg "NEXT_PUBLIC_BASE_URL" --type ts --type js

Length of output: 132


Define and Configure NEXT_PUBLIC_BASE_URL in All Environments

We’ve only located the usage at constants/index.ts:167 and no committed definitions for NEXT_PUBLIC_BASE_URL. To prevent runtime errors:

  • Add NEXT_PUBLIC_BASE_URL to your environment files (e.g.,
    .env.development
    .env.production
    .env.example
    NEXT_PUBLIC_BASE_URL=https://your-domain.com
  • Ensure it’s set in your hosting/CI environment (Vercel, Netlify, Docker, etc.).
  • Optionally, guard against undefined values in code:
    // constants/index.ts (line 167)
    const baseUrl = process.env.NEXT_PUBLIC_BASE_URL ?? "";
    export const API_URL = `${baseUrl}/api/vapi/generate`;

Please update your .env files and deployment settings accordingly.

🤖 Prompt for AI Agents
In constants/index.ts at line 167, the environment variable NEXT_PUBLIC_BASE_URL
is used but not defined in any environment files. To fix this, add
NEXT_PUBLIC_BASE_URL with the appropriate URL value to all your environment
files such as .env.development, .env.production, and .env.example. Also, ensure
this variable is set in your deployment environment settings like Vercel or
Netlify. Optionally, update the code to safely handle undefined values by
assigning a default empty string before constructing the API URL.

headers: {
type: "object",
properties: {},
},
body: {
type: "object",
properties: {
role: {
type: "string",
description: "",
value: "{{ role }}",
},
level: {
type: "string",
description: "",
value: "{{ level }}",
},
type: {
type: "string",
description: "",
value: "{{ type }}",
},
amount: {
type: "number",
description: "",
value: "{{ amount }}",
},
userid: {
type: "string",
description: "",
value: "{{ userid }}",
},
Comment on lines +195 to +199
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify user ID handling in the API request.

The workflow passes userid to the API, but make sure the backend API at /api/vapi/generate correctly handles and validates this user identifier to prevent potential security issues.


🏁 Script executed:

#!/bin/bash
# Check how the API validates user IDs
fd "api/vapi/generate" --type f | xargs rg -A 10 -B 5 "userid"

Length of output: 3671


Add proper validation and authentication for userid in the generate route

The handler in app/api/vapi/generate/route.ts currently does:

const { type, role, level, techstack, amount, userid } = await request.json();
// …  
const interview = {
  // …
  userId: userid,
  // …
};
await db.collection("interviews").add(interview);

There’s no check that userid is well-formed or that it matches an authenticated user. To prevent spoofing or injection:

  • Enforce authentication (e.g. via session or JWT) and derive userId from the auth context rather than client payload.
  • If you must accept it in the body, validate its format (UUID, Firebase UID, etc.) and verify the user exists.
  • Sanitize or whitelist allowed characters/lengths to guard against injection attacks.

Files to update:

  • app/api/vapi/generate/route.ts – around the JSON destructuring and before writing to Firestore.

Please implement these checks before persisting any interview data.

🤖 Prompt for AI Agents
In app/api/vapi/generate/route.ts around the lines where the request JSON is
destructured, the userid from the client payload is used directly without
validation or authentication. To fix this, enforce authentication by extracting
the userId from the authenticated session or JWT token instead of the request
body. If accepting userid from the body is unavoidable, validate its format
strictly (e.g., UUID or Firebase UID), check that the user exists in the
database, and sanitize the input to prevent injection. Implement these checks
before creating and saving the interview data to ensure userid integrity and
security.

techstack: {
type: "string",
description: "",
value: "{{ techstack }}",
},
},
},
output: {
type: "object",
properties: {},
},
mode: "blocking",
hooks: [],
},
{
name: "conversation_1747721261435",
type: "conversation",
metadata: {
position: {
x: -17.547788169718615,
y: 1003.3409337989506,
},
},
prompt:
"Thank the user for the conversation and inform them that the interview was generated successfully.",
voice: {
provider: "deepgram",
voiceId: "thalia",
model: "aura-2",
},
},
{
name: "conversation_1747744490967",
type: "conversation",
metadata: {
position: {
x: -11.165436030430953,
y: 484.94857971060617,
},
},
prompt: "Say that the Interview will be generated shortly.",
voice: {
provider: "deepgram",
voiceId: "thalia",
model: "aura-2",
},
},
{
name: "hangup_1747744730181",
type: "hangup",
metadata: {
position: {
x: 76.01267674000721,
y: 1272.0665127156606,
},
},
},
],
edges: [
{
from: "apiRequest_1747470739045",
to: "conversation_1747721261435",
condition: {
type: "ai",
prompt: "",
},
},
{
from: "start",
to: "conversation_1747744490967",
condition: {
type: "ai",
prompt: "If user provided all the required variables",
},
},
{
from: "conversation_1747744490967",
to: "apiRequest_1747470739045",
condition: {
type: "ai",
prompt: "",
},
},
{
from: "conversation_1747721261435",
to: "hangup_1747744730181",
condition: {
type: "ai",
prompt: "",
},
},
],
};

export const interviewer: CreateAssistantDTO = {
name: "Interviewer",
firstMessage:
Expand Down
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"@hookform/resolvers": "^4.1.3",
"@radix-ui/react-label": "^2.1.2",
"@radix-ui/react-slot": "^1.1.2",
"@vapi-ai/web": "^2.2.4",
"@vapi-ai/web": "^2.3.0",
"ai": "^4.1.61",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
Expand Down