@@ -25,6 +25,7 @@ import {
2525 IconTool ,
2626 IconInfoCircle ,
2727 IconSparkles ,
28+ IconCheck ,
2829 IconClockHour4 ,
2930 IconMessageChatbot ,
3031 IconMapPin ,
@@ -56,6 +57,7 @@ import { usePostHog } from "posthog-js/react"
5657import SiriSpheres from "@components/voice-visualization/SiriSpheres"
5758import { WebRTCClient } from "@lib/webrtc-client"
5859import useClickOutside from "@hooks/useClickOutside"
60+ import { usePlan } from "@hooks/usePlan"
5961
6062const toolIcons = {
6163 gmail : IconGoogleMail ,
@@ -77,6 +79,104 @@ const toolIcons = {
7779 default : IconTool
7880}
7981
82+ const proPlanFeatures = [
83+ { name : "Text Chat" , limit : "100 messages per day" } ,
84+ { name : "Voice Chat" , limit : "10 minutes per day" } ,
85+ { name : "One-Time Tasks" , limit : "20 async tasks per day" } ,
86+ { name : "Recurring Tasks" , limit : "10 active recurring workflows" } ,
87+ { name : "Triggered Tasks" , limit : "10 triggered workflows" } ,
88+ {
89+ name : "Parallel Agents" ,
90+ limit : "5 complex tasks per day with 50 sub agents"
91+ } ,
92+ { name : "File Uploads" , limit : "20 files per day" } ,
93+ { name : "Memories" , limit : "Unlimited memories" } ,
94+ {
95+ name : "Other Integrations" ,
96+ limit : "Notion, GitHub, Slack, Discord, Trello"
97+ }
98+ ]
99+
100+ const UpgradeToProModal = ( { isOpen, onClose } ) => {
101+ if ( ! isOpen ) return null
102+
103+ const handleUpgrade = ( ) => {
104+ const dashboardUrl = process . env . NEXT_PUBLIC_LANDING_PAGE_URL
105+ if ( dashboardUrl ) {
106+ window . open ( `${ dashboardUrl } /dashboard` , "_blank" )
107+ }
108+ onClose ( )
109+ }
110+
111+ return (
112+ < AnimatePresence >
113+ { isOpen && (
114+ < motion . div
115+ initial = { { opacity : 0 } }
116+ animate = { { opacity : 1 } }
117+ exit = { { opacity : 0 } }
118+ className = "fixed inset-0 bg-black/70 backdrop-blur-md z-[100] flex items-center justify-center p-4"
119+ onClick = { onClose }
120+ >
121+ < motion . div
122+ initial = { { scale : 0.95 , y : 20 } }
123+ animate = { { scale : 1 , y : 0 } }
124+ exit = { { scale : 0.95 , y : - 20 } }
125+ transition = { { duration : 0.2 , ease : "easeInOut" } }
126+ onClick = { ( e ) => e . stopPropagation ( ) }
127+ className = "relative bg-neutral-900/90 backdrop-blur-xl p-6 rounded-2xl shadow-2xl w-full max-w-lg border border-neutral-700 flex flex-col"
128+ >
129+ < header className = "text-center mb-4" >
130+ < h2 className = "text-2xl font-bold text-white flex items-center justify-center gap-2" >
131+ < IconSparkles className = "text-brand-orange" />
132+ Upgrade to Pro
133+ </ h2 >
134+ < p className = "text-neutral-400 mt-2" >
135+ Unlock Voice Mode and other powerful features.
136+ </ p >
137+ </ header >
138+ < main className = "grid grid-cols-1 sm:grid-cols-2 gap-x-6 gap-y-4 my-4" >
139+ { proPlanFeatures . map ( ( feature ) => (
140+ < div
141+ key = { feature . name }
142+ className = "flex items-start gap-2.5"
143+ >
144+ < IconCheck
145+ size = { 18 }
146+ className = "text-green-400 flex-shrink-0 mt-0.5"
147+ />
148+ < div >
149+ < p className = "text-white text-sm font-medium" >
150+ { feature . name }
151+ </ p >
152+ < p className = "text-neutral-400 text-xs" >
153+ { feature . limit }
154+ </ p >
155+ </ div >
156+ </ div >
157+ ) ) }
158+ </ main >
159+ < footer className = "mt-4 flex flex-col gap-2" >
160+ < button
161+ onClick = { handleUpgrade }
162+ className = "w-full py-2.5 px-5 rounded-lg bg-brand-orange hover:bg-brand-orange/90 text-brand-black font-semibold transition-colors"
163+ >
164+ Upgrade to Pro - $9/month
165+ </ button >
166+ < button
167+ onClick = { onClose }
168+ className = "w-full py-2 px-5 rounded-lg hover:bg-neutral-800 text-sm font-medium text-neutral-400"
169+ >
170+ Not now
171+ </ button >
172+ </ footer >
173+ </ motion . div >
174+ </ motion . div >
175+ ) }
176+ </ AnimatePresence >
177+ )
178+ }
179+
80180const StorylaneDemoModal = ( { onClose } ) => {
81181 // The script adds a global Storylane object. The button's onClick will use it.
82182 const embedHtml = `
@@ -159,12 +259,15 @@ export default function ChatPage() {
159259
160260 const searchParams = useSearchParams ( )
161261 const router = useRouter ( )
262+ const { isPro } = usePlan ( )
162263
163264 // --- File Upload State ---
164265 const [ selectedFile , setSelectedFile ] = useState ( null )
165266 const [ isUploading , setIsUploading ] = useState ( false )
166267 const [ uploadedFilename , setUploadedFilename ] = useState ( null )
167268
269+ // --- Pro Feature Modal ---
270+ const [ isUpgradeModalOpen , setUpgradeModalOpen ] = useState ( false )
168271 // --- Voice Mode State ---
169272 const [ isMuted , setIsMuted ] = useState ( false )
170273 const [ isVoiceMode , setIsVoiceMode ] = useState ( false )
@@ -663,10 +766,11 @@ export default function ChatPage() {
663766 }
664767
665768 useEffect ( ( ) => {
666- if ( chatEndRef . current ) {
667- chatEndRef . current . scrollIntoView ( { behavior : "smooth" } )
769+ if ( chatEndRef . current && ! isVoiceMode ) {
770+ // Use 'auto' for an instant scroll, which feels better when switching modes.
771+ chatEndRef . current . scrollIntoView ( { behavior : "auto" } )
668772 }
669- } , [ displayedMessages , thinking ] )
773+ } , [ displayedMessages , thinking , isVoiceMode ] )
670774
671775 const getGreeting = ( ) => {
672776 const hour = new Date ( ) . getHours ( )
@@ -969,6 +1073,11 @@ export default function ChatPage() {
9691073 }
9701074
9711075 const toggleVoiceMode = async ( ) => {
1076+ if ( ! isPro ) {
1077+ setUpgradeModalOpen ( true )
1078+ return
1079+ }
1080+
9721081 if ( isVoiceMode ) {
9731082 handleStopVoice ( )
9741083 setIsVoiceMode ( false )
@@ -986,11 +1095,10 @@ export default function ChatPage() {
9861095 }
9871096
9881097 useEffect ( ( ) => {
989- // This cleanup now only runs when the ChatPage component unmounts
1098+ // This cleanup now only runs when the ChatPage component unmounts.
1099+ // The handleStopVoice function is now the primary way to disconnect.
9901100 return ( ) => {
991- if ( webrtcClientRef . current ) {
992- webrtcClientRef . current . disconnect ( )
993- }
1101+ webrtcClientRef . current ?. disconnect ( )
9941102 }
9951103 } , [ ] )
9961104
@@ -1138,7 +1246,11 @@ export default function ChatPage() {
11381246 onClick = { toggleVoiceMode }
11391247 className = "p-2.5 rounded-full text-white bg-neutral-700 hover:bg-neutral-600 transition-colors"
11401248 data-tooltip-id = "home-tooltip"
1141- data-tooltip-content = "Switch to Voice Mode"
1249+ data-tooltip-content = {
1250+ isPro
1251+ ? "Switch to Voice Mode"
1252+ : "Voice Mode (Pro Feature)"
1253+ }
11421254 >
11431255 < IconWaveSine size = { 18 } />
11441256 </ button >
@@ -1398,6 +1510,10 @@ export default function ChatPage() {
13981510 src = "/audio/connected.mp3"
13991511 preload = "auto"
14001512 > </ audio >
1513+ < UpgradeToProModal
1514+ isOpen = { isUpgradeModalOpen }
1515+ onClose = { ( ) => setUpgradeModalOpen ( false ) }
1516+ > </ UpgradeToProModal >
14011517 < AnimatePresence >
14021518 { isDemoModalOpen && (
14031519 < StorylaneDemoModal onClose = { handleCloseDemo } />
0 commit comments