diff --git a/src/components/FrameRenderer/RoadmapFloatingChat.tsx b/src/components/FrameRenderer/RoadmapFloatingChat.tsx index 9220d07e0..14efc0648 100644 --- a/src/components/FrameRenderer/RoadmapFloatingChat.tsx +++ b/src/components/FrameRenderer/RoadmapFloatingChat.tsx @@ -28,6 +28,8 @@ import { queryClient } from '../../stores/query-client'; import { RoadmapAIChatCard } from '../RoadmapAIChat/RoadmapAIChatCard'; import { UpdatePersonaModal } from '../UserPersona/UpdatePersonaModal'; import { CLOSE_TOPIC_DETAIL_EVENT } from '../TopicDetail/TopicDetail'; +import { billingDetailsOptions } from '../../queries/billing'; +import { getAiCourseLimitOptions } from '../../queries/ai-course'; type ChatHeaderButtonProps = { onClick?: () => void; @@ -77,6 +79,72 @@ function ChatHeaderButton(props: ChatHeaderButtonProps) { ); } +type UpgradeMessageProps = { + onUpgradeClick?: () => void; +}; + +function UpgradeMessage(props: UpgradeMessageProps) { + const { onUpgradeClick } = props; + + return ( +
+
+ +
+

+ You've reached your AI usage limit +

+

+ Upgrade to Pro for relaxed limits and advanced features +

+
+ +
+
+ ); +} + +type UsageButtonProps = { + percentageUsed: number; + onUpgradeClick?: () => void; +}; + +function UsageButton(props: UsageButtonProps) { + const { percentageUsed, onUpgradeClick } = props; + + return ( + + ); +} + type RoadmapChatProps = { roadmapId: string; }; @@ -111,6 +179,19 @@ export function RoadmapFloatingChat(props: RoadmapChatProps) { queryClient, ); + const { data: tokenUsage, isLoading: isTokenUsageLoading } = useQuery( + getAiCourseLimitOptions(), + queryClient, + ); + const isLimitExceeded = (tokenUsage?.used || 0) >= (tokenUsage?.limit || 0); + const percentageUsed = Math.round( + ((tokenUsage?.used || 0) / (tokenUsage?.limit || 0)) * 100, + ); + + const { data: userBillingDetails, isLoading: isBillingDetailsLoading } = + useQuery(billingDetailsOptions(), queryClient); + const isPaidUser = userBillingDetails?.status === 'active'; + const totalTopicCount = useMemo(() => { const allowedTypes = ['topic', 'subtopic', 'todo']; return ( @@ -222,174 +303,205 @@ export function RoadmapFloatingChat(props: RoadmapChatProps) {
{isOpen && ( -
- {/* Messages area */} -
-
- } - > - AI Tutor - - { - setIsPersonalizeOpen(true); - }} - icon={} - className="rounded-md bg-gray-200 py-1 pr-2 pl-1.5 text-gray-500 hover:bg-gray-300" - > - Personalize - -
- -
- {hasMessages && ( + <> +
+ {/* Messages area */} +
+
{ - setInputValue(''); - clearChat(); - }} - icon={} - className="mr-2 text-gray-500" + icon={} + className="mr-2 hidden text-sm sm:flex" > - Clear + AI Tutor - )} + {!isPaidUser && ( + { + window.open('/premium', '_blank'); + }} + /> + )} +
- } - className="rounded-md hidden sm:flex bg-gray-200 py-1 pr-2 pl-1.5 text-gray-500 hover:bg-gray-300" - > - Open in new tab - +
+ } + className="hidden rounded-md bg-gray-200 py-1 pr-2 pl-1.5 text-gray-500 hover:bg-gray-300 sm:flex" + > + Open in new tab + - setIsOpen(false)} - icon={} - className="rounded-md bg-red-100 px-1 py-1 text-red-500 hover:bg-red-200" - /> + setIsOpen(false)} + icon={} + className="rounded-md bg-red-100 px-1 py-1 text-red-500 hover:bg-red-200" + /> +
-
-
-
- - Hey, I am your AI tutor. How can I help you today? 👋 - - } - isIntro - /> +
+
+ + Hey, I am your AI tutor. How can I help you today? 👋 + + } + isIntro + /> - {/* Show default questions only when there's no chat history */} - {aiChatHistory.length === 0 && defaultQuestions.length > 0 && ( -
-

- Some questions you might have about this roadmap: -

-
- {defaultQuestions.map((question, index) => ( - - ))} -
-
- )} + {/* Show default questions only when there's no chat history */} + {aiChatHistory.length === 0 && + defaultQuestions.length > 0 && ( +
+

+ Some questions you might have about this roadmap: +

+
+ {defaultQuestions.map((question, index) => ( + + ))} +
+
+ )} - {aiChatHistory.map( - (chat: RoadmapAIChatHistoryType, index: number) => ( - - - - ), - )} + {aiChatHistory.map( + (chat: RoadmapAIChatHistoryType, index: number) => ( + + + + ), + )} - {isStreamingMessage && !streamedMessage && ( - - )} + {isStreamingMessage && !streamedMessage && ( + + )} - {streamedMessage && ( - + {streamedMessage && ( + + )} +
+ + {/* Scroll to bottom button */} + {showScrollToBottom && ( + )}
- {/* Scroll to bottom button */} - {showScrollToBottom && ( - + /> + )} + {!isLimitExceeded && ( + <> +
+ { + setIsPersonalizeOpen(true); + }} + icon={} + className="rounded-md bg-gray-200 py-1 pr-2 pl-1.5 text-gray-500 hover:bg-gray-300" + > + Personalize + + {hasMessages && ( + { + setInputValue(''); + clearChat(); + }} + icon={} + className="rounded-md bg-gray-200 py-1 pr-2 pl-1.5 text-gray-500 hover:bg-gray-300" + > + Clear + + )} +
+
+ setInputValue(e.target.value)} + autoFocus + disabled={isLimitExceeded} + onKeyDown={(e) => { + if (e.key === 'Enter') { + e.preventDefault(); + if (isStreamingMessage) { + return; + } + submitInput(); + } + }} + placeholder={ + isLimitExceeded + ? 'You have reached the usage limit for today..' + : 'Ask me anything about this roadmap...' + } + className={cn( + 'w-full resize-none px-3 py-4 outline-none', + isLimitExceeded && 'bg-gray-100 text-gray-400', + )} + /> + + +
+ )}
- - {/* Input area */} -
- setInputValue(e.target.value)} - autoFocus - onKeyDown={(e) => { - if (e.key === 'Enter') { - e.preventDefault(); - if (isStreamingMessage) { - return; - } - submitInput(); - } - }} - placeholder="Ask me anything about this roadmap..." - className="w-full resize-none p-3 outline-none" - /> - - -
-
+ )} {!isOpen && (