Skip to content

Commit ca6ee8d

Browse files
committed
Use VAPI to make function calls to realtime broadcast endpoint
1 parent 0d1a313 commit ca6ee8d

File tree

11 files changed

+128
-48
lines changed

11 files changed

+128
-48
lines changed
File renamed without changes.

app/api/realtime/broadcast/route.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { createClient } from "@/utils/supabase/server";
2+
3+
export async function POST(request: Request) {
4+
const supabase = createClient();
5+
const json = await request.json();
6+
const params = json.message.functionCall.parameters;
7+
console.log(params);
8+
9+
supabase.channel(params.channelID).send({
10+
type: "broadcast",
11+
event: "slide",
12+
payload: {
13+
slide: params.slide,
14+
},
15+
});
16+
17+
return new Response("OK", {
18+
status: 200,
19+
});
20+
}
21+
22+
export async function OPTIONS() {
23+
return new Response("OK", {
24+
status: 200,
25+
});
26+
}

app/login/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export default function Login({ searchParams }: { searchParams: { message: strin
3636
email,
3737
password,
3838
options: {
39-
emailRedirectTo: `${origin}/auth/callback`,
39+
emailRedirectTo: `${origin}/api/auth/callback`,
4040
},
4141
});
4242

app/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ export default async function Index() {
88
<div className="w-full max-w-4xl flex justify-between items-center p-3 text-sm"></div>
99
</nav>
1010

11-
<div className="animate-in flex-1 flex flex-col gap-20 opacity-0 max-w-4xl px-3">
12-
<main className="flex-1 flex flex-col gap-6">
11+
<div className="animate-in flex-1 flex flex-col gap-20 opacity-0 w-full max-w-4xl px-3">
12+
<main className="flex-1 flex flex-col gap-6 w-full h-full items-center">
1313
<h2 className="font-bold text-4xl mb-4 text-center">Demo</h2>
1414
<Demi />
1515
</main>

components/ActiveCallDetail.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export const ActiveCallDetail: React.FC<{
1515
<div className="flex flex-col items-center justify-center p-4 border border-solid border-[#ddd] rounded-3xl shadow-md w-96 h-48">
1616
<AssistantSpeechIndicator isSpeaking={assistantIsSpeaking} />
1717
</div>
18-
<div className="flex mt-8 gap-5 justify-center">
18+
<div className="flex mt-8 gap-12 justify-center">
1919
<Button className="rounded-full bg-gray-400 p-3" onClick={onPauseClick}>
2020
{paused ? <PlayIcon className="h-8" /> : <PauseIcon className="h-8" />}
2121
</Button>

components/Demi.tsx

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,17 @@
33
import { ActiveCallDetail } from "@/components/ActiveCallDetail";
44
import { Button } from "@/components/button/Button";
55
import { LoadingSpinner } from "@/components/loading/Loading";
6-
import { AssistantOptions } from "@/utils/vapi/config";
6+
import { SlideDisplay } from "@/components/slides/Slide";
77
import Vapi from "@vapi-ai/web";
88
import { useEffect, useState } from "react";
9+
import { v4 } from "uuid";
910

11+
const ASSISTANT_ID =
12+
process.env.NODE_ENV === "development"
13+
? "e61177f1-25d3-4273-8d5e-649555b9ccb7"
14+
: "08d6e6dc-b5ab-4bff-86b9-5126653f56ad";
1015
const vapi = new Vapi("969bf530-507e-4751-8aec-07b9f8f93020");
16+
const channelId = v4();
1117

1218
export const Demi: React.FC = () => {
1319
const [connecting, setConnecting] = useState(false);
@@ -16,6 +22,9 @@ export const Demi: React.FC = () => {
1622

1723
const [assistantIsSpeaking, setAssistantIsSpeaking] = useState(false);
1824

25+
// TODO: Fetch from server based on URL slug
26+
const assistantId = ASSISTANT_ID;
27+
1928
useEffect(() => {
2029
vapi.on("call-start", () => {
2130
setConnecting(false);
@@ -39,18 +48,21 @@ export const Demi: React.FC = () => {
3948
console.error(error);
4049
setConnecting(false);
4150
});
42-
43-
// we only want this to fire on mount
44-
// eslint-disable-next-line react-hooks/exhaustive-deps
4551
}, []);
4652

4753
const startCallInline = () => {
4854
setConnecting(true);
49-
vapi.start(AssistantOptions);
55+
vapi.start(assistantId, {
56+
variableValues: {
57+
channelId: channelId,
58+
},
59+
});
5060
};
61+
5162
const endCall = () => {
5263
vapi.stop();
5364
};
65+
5466
const pause = () => {
5567
const audioElements = document.querySelectorAll("audio");
5668
audioElements.forEach((audio) => {
@@ -60,7 +72,12 @@ export const Demi: React.FC = () => {
6072
setPaused(true);
6173
setAssistantIsSpeaking(false);
6274
};
75+
6376
const unpause = () => {
77+
const audioElements = document.querySelectorAll("audio");
78+
audioElements.forEach((audio) => {
79+
audio.play();
80+
});
6481
vapi.send({
6582
type: "add-message",
6683
message: {
@@ -78,7 +95,9 @@ export const Demi: React.FC = () => {
7895
connecting ? (
7996
<LoadingSpinner className="mx-auto animate-grow" />
8097
) : (
81-
<Button onClick={startCallInline}>Start Demo</Button>
98+
<Button className="w-40" onClick={startCallInline}>
99+
Start Demo
100+
</Button>
82101
)
83102
) : (
84103
<ActiveCallDetail
@@ -88,6 +107,7 @@ export const Demi: React.FC = () => {
88107
paused={paused}
89108
/>
90109
)}
110+
<SlideDisplay channelID={channelId} />
91111
</>
92112
);
93113
};

components/slides/Slide.tsx

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
"use client";
2+
import { createClient } from "@/utils/supabase/client";
3+
import Image from "next/image";
4+
import { useEffect, useState } from "react";
5+
6+
type Slide = {
7+
src: string;
8+
alt: string;
9+
};
10+
11+
export const SlideDisplay: React.FC<{ channelID: string }> = ({ channelID }) => {
12+
const [slide, setSlide] = useState<Slide>();
13+
14+
useEffect(() => {
15+
const supabase = createClient();
16+
supabase
17+
.channel(channelID)
18+
.on("broadcast", { event: "slide" }, (message) => {
19+
setSlide(message.payload.slide);
20+
})
21+
.subscribe((status) => {
22+
console.log(status);
23+
console.log(channelID);
24+
});
25+
}, [channelID]);
26+
27+
if (!slide) {
28+
return null;
29+
}
30+
31+
return (
32+
<div className="flex flex-1 relative max-w-screen-sm w-full max-h-[480px] h-full">
33+
<Image src={slide.src} alt={slide.alt} width={640} height={360} />
34+
</div>
35+
);
36+
};

next.config.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,15 @@
11
/** @type {import('next').NextConfig} */
2-
const nextConfig = {};
2+
const nextConfig = {
3+
images: {
4+
remotePatterns: [
5+
{
6+
protocol: "https",
7+
hostname: "via.placeholder.com",
8+
port: "",
9+
pathname: "/**",
10+
},
11+
],
12+
},
13+
};
314

415
module.exports = nextConfig;

package-lock.json

Lines changed: 21 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,14 @@
2121
"react-dom": "18.2.0",
2222
"tailwind-merge": "^2.3.0",
2323
"tailwindcss": "3.4.1",
24-
"typescript": "5.3.3"
24+
"typescript": "5.3.3",
25+
"uuid": "^9.0.1"
2526
},
2627
"devDependencies": {
2728
"@types/node": "20.11.5",
2829
"@types/react": "18.2.48",
2930
"@types/react-dom": "18.2.18",
31+
"@types/uuid": "^9.0.8",
3032
"encoding": "^0.1.13",
3133
"eslint": "8.57.0",
3234
"eslint-config-next": "14.2.3"

utils/vapi/config.ts

Lines changed: 0 additions & 35 deletions
This file was deleted.

0 commit comments

Comments
 (0)