-
Notifications
You must be signed in to change notification settings - Fork 95
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
872c5b5
commit a7aae9e
Showing
19 changed files
with
1,034 additions
and
227 deletions.
There are no files selected for viewing
21 changes: 21 additions & 0 deletions
21
examples/conversational-ai/talk-to-santa/app/(main)/(santa)/[cards]/[id]/layout.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
"use server"; | ||
import { Loader2 } from "lucide-react"; | ||
import { Suspense } from "react"; | ||
|
||
export default async function Layout({ | ||
children, | ||
}: { | ||
children: React.ReactNode; | ||
params: { id: string }; | ||
}) { | ||
return ( | ||
<Suspense fallback={ | ||
<div className="flex flex-col items-center justify-center mt-8"> | ||
<Loader2 className="text-white p-4 rounded-full h-20 w-20" /> | ||
<span className="mt-2 text-white font-bold">Loading card...</span> | ||
</div> | ||
}> | ||
{children} | ||
</Suspense> | ||
); | ||
} |
271 changes: 271 additions & 0 deletions
271
examples/conversational-ai/talk-to-santa/app/(main)/(santa)/[cards]/[id]/page.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,271 @@ | ||
"use client"; | ||
|
||
import { | ||
getAgentConversation, | ||
getAgentConversationAudio, | ||
getConversationData, | ||
} from "@/app/(main)/(santa)/actions/actions"; | ||
import { christmasFont } from "@/components/custom-fonts"; | ||
import { | ||
Card, | ||
CardContent, | ||
CardDescription, | ||
CardHeader, | ||
CardTitle, | ||
} from "@/components/ui/card"; | ||
import { cn } from "@/lib/utils"; | ||
import { motion } from "framer-motion"; | ||
import { Loader2 } from "lucide-react"; | ||
import { useParams } from "next/navigation"; | ||
import { useEffect, useState, useRef } from "react"; | ||
import { toast } from "sonner"; | ||
|
||
export default function Page() { | ||
const params = useParams(); | ||
const id = params.id as string; | ||
|
||
const [conversationMetadata, setConversationMetadata] = useState<any>(null); | ||
const [videoUrl, setVideoUrl] = useState<string | null>(null); | ||
|
||
const [isLoading, setIsLoading] = useState(true); | ||
const [audio, setAudio] = useState<string | null>(null); | ||
|
||
const video = `https://iifpdwenjojkwnidrlxl.supabase.co/storage/v1/object/public/media/media/${id}.mp4`; | ||
|
||
const videoRef = useRef<HTMLVideoElement>(null); | ||
const audioRef = useRef<HTMLAudioElement>(null); | ||
|
||
useEffect(() => { | ||
const checkVideoExists = async () => { | ||
try { | ||
const response = await fetch(video); | ||
if (response.ok) { | ||
setVideoUrl(video); | ||
} else { | ||
setVideoUrl(null); | ||
} | ||
} catch (err) { | ||
console.error(err); | ||
setVideoUrl(null); | ||
} | ||
}; | ||
checkVideoExists(); | ||
}, [video]); | ||
|
||
useEffect(() => { | ||
const fetchData = async () => { | ||
const response = await getConversationData({ conversationId: id }); | ||
setConversationMetadata(response?.data); | ||
}; | ||
fetchData(); | ||
}, [id]); | ||
|
||
useEffect(() => { | ||
let timeoutId: NodeJS.Timeout; | ||
|
||
const fetchConversation = async () => { | ||
try { | ||
const result = await getAgentConversation({ conversationId: id }); | ||
if (result?.data?.conversation) { | ||
const isProcessing = result.data.conversation.status === "processing"; | ||
if (!isProcessing) { | ||
setIsLoading(false); | ||
const audio = await getAgentConversationAudio({ | ||
conversationId: id, | ||
}); | ||
if (audio?.data?.audio) { | ||
setAudio(audio.data.audio); | ||
} | ||
return true; | ||
} | ||
return false; | ||
} | ||
return false; | ||
} catch (err) { | ||
console.log(err); | ||
toast.error("Unable to fetch conversation. Please try again later."); | ||
return false; | ||
} | ||
}; | ||
|
||
const pollConversation = async () => { | ||
const shouldStop = await fetchConversation(); | ||
if (!shouldStop) { | ||
timeoutId = setTimeout(pollConversation, 5000); | ||
} | ||
}; | ||
|
||
pollConversation(); | ||
|
||
return () => { | ||
clearTimeout(timeoutId); | ||
}; | ||
}, [id]); | ||
|
||
|
||
const handleVideoPlay = () => { | ||
if (audioRef.current) { | ||
audioRef.current.play(); | ||
} | ||
}; | ||
|
||
const handleVideoPause = () => { | ||
if (audioRef.current) { | ||
audioRef.current.pause(); | ||
} | ||
}; | ||
|
||
const handleVideoTimeUpdate = () => { | ||
if (videoRef.current && audioRef.current) { | ||
const timeDiff = Math.abs( | ||
videoRef.current.currentTime - audioRef.current.currentTime | ||
); | ||
if (timeDiff > 0.1) { | ||
audioRef.current.currentTime = videoRef.current.currentTime; | ||
} | ||
} | ||
}; | ||
|
||
if (isLoading) { | ||
return ( | ||
<div className="flex flex-col items-center justify-center mt-8"> | ||
<Loader2 className="p-4 rounded-full text-white h-20 w-20 animate-spin" /> | ||
<span className="mt-2 text-white font-bold">Loading card...</span> | ||
</div> | ||
); | ||
} | ||
|
||
return ( | ||
<div | ||
className={cn( | ||
"mx-auto max-w-4xl", | ||
"relative rounded-lg", | ||
"p-[10px]", | ||
"bg-[repeating-linear-gradient(45deg,#ff0000_0px,#ff0000_10px,#ffffff_10px,#ffffff_20px)]", | ||
christmasFont.className | ||
)} | ||
> | ||
<Card className="bg-white backdrop-blur-sm rounded-lg"> | ||
<CardHeader> | ||
<CardTitle className="text-4xl font-bold text-red-600 text-center"> | ||
My Letter to Santa {isLoading ? "..." : ""} | ||
</CardTitle> | ||
<CardDescription className="text-xl text-gray-600 mb-2 text-center"> | ||
From my heart to the North Pole | ||
</CardDescription> | ||
<hr className="border-t-2 border-gray-300 my-4" /> | ||
</CardHeader> | ||
|
||
<CardContent className="max-w-2xl mx-auto px-8 py-5"> | ||
<div className="space-y-1"> | ||
<div className="text-4xl font-medium text-gray-800 mb-6"> | ||
Dear Santa, | ||
{conversationMetadata?.name && | ||
conversationMetadata?.name.length > 0 && ( | ||
<motion.span | ||
initial={{ opacity: 0 }} | ||
animate={{ opacity: 1 }} | ||
transition={{ | ||
duration: 1, | ||
ease: "easeOut", | ||
delay: 0.3, | ||
}} | ||
> | ||
{" "} | ||
my name is{" "} | ||
<span className="text-red-600 font-semibold"> | ||
{conversationMetadata?.name} | ||
</span> | ||
</motion.span> | ||
)} | ||
</div> | ||
<div className="space-y-2"> | ||
<p className="text-xl text-gray-700"> | ||
These are the presents I'm wishing for: | ||
</p> | ||
<ol className="space-y-4 ml-8"> | ||
{conversationMetadata?.wishlist.map( | ||
({ | ||
name: presentName, | ||
key, | ||
index, | ||
}: { | ||
name: string; | ||
key: string; | ||
index: number; | ||
}) => ( | ||
<motion.li | ||
key={key} | ||
initial={{ opacity: 0 }} | ||
animate={{ opacity: 1 }} | ||
transition={{ | ||
duration: 1, | ||
ease: "easeOut", | ||
delay: 0.3 + index * 0.2, | ||
}} | ||
className="flex items-center gap-4 text-xl text-gray-800" | ||
> | ||
<span className="text-1xl">🎄</span> | ||
<span className="text-red-600 font-bold"> | ||
{presentName} | ||
</span> | ||
</motion.li> | ||
) | ||
)} | ||
</ol> | ||
<p className="text-xl text-gray-800 pt-4">Thank you Santa!</p> | ||
</div> | ||
</div> | ||
</CardContent> | ||
|
||
<div className="flex flex-col items-center border-t pt-6 p-4"> | ||
{audio && videoUrl ? ( | ||
<audio | ||
ref={audioRef} | ||
className="hidden" | ||
src={`data:audio/mpeg;base64,${audio}`} | ||
/> | ||
) : ( | ||
audio && ( | ||
<div className="w-full max-w-md mb-4"> | ||
<audio | ||
controls | ||
className="w-full" | ||
src={`data:audio/mpeg;base64,${audio}`} | ||
/> | ||
</div> | ||
) | ||
)} | ||
{videoUrl && ( | ||
<video | ||
ref={videoRef} | ||
className="w-full max-w-xs sm:max-w-sm md:max-w-md lg:max-w-sm h-auto rounded-lg" | ||
controls | ||
playsInline | ||
preload="metadata" | ||
onPlay={handleVideoPlay} | ||
onPause={handleVideoPause} | ||
onTimeUpdate={handleVideoTimeUpdate} | ||
> | ||
<source src={videoUrl} type="video/mp4" /> | ||
Your browser does not support the video tag. | ||
</video> | ||
)} | ||
|
||
<span className="text-gray-500 pt-4"> | ||
Made with{" "} | ||
<span role="img" aria-label="heart"> | ||
❤️ | ||
</span>{" "} | ||
in the North Pole by{" "} | ||
<strong> | ||
<a href="https://elevenlabs.io" target="_blank"> | ||
ElevenLabs | ||
</a> | ||
</strong> | ||
</span> | ||
</div> | ||
</Card> | ||
</div> | ||
); | ||
} |
Oops, something went wrong.