From c81a2930e6c68e92d7036a6e6c4b56247b3684d8 Mon Sep 17 00:00:00 2001 From: Arik Chakma Date: Fri, 23 May 2025 01:21:43 +0600 Subject: [PATCH] feat: ai chat limit --- .../RoadmapAIChat/RoadmapAIChat.tsx | 115 ++++++++++++++--- .../RoadmapAIChat/RoadmapAIChatHeader.tsx | 117 ++++++++++++++++++ src/components/TopicDetail/TopicDetailAI.tsx | 1 + 3 files changed, 218 insertions(+), 15 deletions(-) create mode 100644 src/components/RoadmapAIChat/RoadmapAIChatHeader.tsx diff --git a/src/components/RoadmapAIChat/RoadmapAIChat.tsx b/src/components/RoadmapAIChat/RoadmapAIChat.tsx index 08b0449a7..ecdf4df96 100644 --- a/src/components/RoadmapAIChat/RoadmapAIChat.tsx +++ b/src/components/RoadmapAIChat/RoadmapAIChat.tsx @@ -15,8 +15,10 @@ import { BotIcon, Frown, Loader2Icon, + LockIcon, PauseCircleIcon, SendIcon, + Trash2Icon, } from 'lucide-react'; import { ChatEditor } from '../ChatEditor/ChatEditor'; import { roadmapTreeMappingOptions } from '../../queries/roadmap-tree'; @@ -39,6 +41,10 @@ import { UserProgressActionList } from './UserProgressActionList'; import { RoadmapTopicList } from './RoadmapTopicList'; import { ShareResourceLink } from './ShareResourceLink'; import { RoadmapRecommendations } from './RoadmapRecommendations'; +import { RoadmapAIChatHeader } from './RoadmapAIChatHeader'; +import { showLoginPopup } from '../../lib/popup'; +import { UpgradeAccountModal } from '../Billing/UpgradeAccountModal'; +import { billingDetailsOptions } from '../../queries/billing'; export type RoamdapAIChatHistoryType = { role: AllowedAIChatRole; @@ -67,6 +73,7 @@ export function RoadmapAIChat(props: RoadmapAIChatProps) { const scrollareaRef = useRef(null); const [isLoading, setIsLoading] = useState(true); + const [showUpgradeModal, setShowUpgradeModal] = useState(false); const [aiChatHistory, setAiChatHistory] = useState< RoamdapAIChatHistoryType[] @@ -79,16 +86,27 @@ export function RoadmapAIChat(props: RoadmapAIChatProps) { roadmapJSONOptions(roadmapId), queryClient, ); - const { data: roadmapTreeData } = useQuery( + const { data: roadmapTreeData, isLoading: roadmapTreeLoading } = useQuery( roadmapTreeMappingOptions(roadmapId), queryClient, ); - const { data: userResourceProgressData } = useQuery( - userResourceProgressOptions('roadmap', roadmapId), + const { + data: userResourceProgressData, + isLoading: userResourceProgressLoading, + } = useQuery(userResourceProgressOptions('roadmap', roadmapId), queryClient); + + const { data: tokenUsage, isLoading: isTokenUsageLoading } = useQuery( + getAiCourseLimitOptions(), queryClient, ); + const { data: userBillingDetails, isLoading: isBillingDetailsLoading } = + useQuery(billingDetailsOptions(), queryClient); + + const isLimitExceeded = (tokenUsage?.used || 0) >= (tokenUsage?.limit || 0); + const isPaidUser = userBillingDetails?.status === 'active'; + const roadmapContainerRef = useRef(null); useEffect(() => { @@ -296,6 +314,14 @@ export function RoadmapAIChat(props: RoadmapAIChatProps) { ); } + const isDataLoading = + isLoading || + roadmapTreeLoading || + userResourceProgressLoading || + isTokenUsageLoading || + isBillingDetailsLoading; + const hasChatHistory = aiChatHistory.length > 0; + return (
@@ -304,6 +330,7 @@ export function RoadmapAIChat(props: RoadmapAIChatProps) {
)} + {roadmapDetail?.json && !isLoading && (
@@ -318,12 +345,21 @@ export function RoadmapAIChat(props: RoadmapAIChatProps) {
-
- - - AI Chat - -
+ {showUpgradeModal && ( + setShowUpgradeModal(false)} /> + )} + + { + showLoginPopup(); + }} + onUpgrade={() => { + setShowUpgradeModal(true); + }} + />
{isLoading && ( @@ -365,12 +401,13 @@ export function RoadmapAIChat(props: RoadmapAIChatProps) { editorRef={editorRef} roadmapId={roadmapId} onSubmit={(content) => { - if (isStreamingMessage || abortControllerRef.current) { - return; - } - - if (isEmptyContent(content)) { - toast.error('Please enter a message'); + if ( + isStreamingMessage || + abortControllerRef.current || + !isLoggedIn() || + isDataLoading || + isEmptyContent(content) + ) { return; } @@ -378,6 +415,54 @@ export function RoadmapAIChat(props: RoadmapAIChatProps) { }} /> + {isLimitExceeded && isLoggedIn() && ( +
+ +

+ Limit reached for today + {isPaidUser ? '. Please wait until tomorrow.' : ''} +

+ {!isPaidUser && ( + + )} +
+ )} + + {!isLoggedIn() && ( +
+ +

Please login to continue

+ +
+ )} + + {isDataLoading && ( +
+ +

Loading...

+
+ )} + + )} + + {!isPaidUser && ( + <> + + + + )} +
+ )} +
+ + ); +} diff --git a/src/components/TopicDetail/TopicDetailAI.tsx b/src/components/TopicDetail/TopicDetailAI.tsx index 30f70e220..1c8ebd5aa 100644 --- a/src/components/TopicDetail/TopicDetailAI.tsx +++ b/src/components/TopicDetail/TopicDetailAI.tsx @@ -471,6 +471,7 @@ export function TopicDetailAI(props: TopicDetailAIProps) { )}
)} + {!isLoggedIn() && (