import '../ChatMessages/AIChat.css'; import { useQuery } from '@tanstack/react-query'; import { BotIcon, ChevronRightIcon, Gift, Loader2Icon, LockIcon, SendIcon, Trash2, WandSparkles, } from 'lucide-react'; import { useEffect, useRef, useState } from 'react'; import TextareaAutosize from 'react-textarea-autosize'; import { useToast } from '../../hooks/use-toast'; import { cn } from '../../lib/classname'; import { isLoggedIn } from '../../lib/jwt'; import { getPercentage } from '../../lib/number'; import { showLoginPopup } from '../../lib/popup'; import type { ResourceType } from '../../lib/resource-progress'; import { aiLimitOptions } from '../../queries/ai-course'; import { billingDetailsOptions } from '../../queries/billing'; import { roadmapTreeMappingOptions } from '../../queries/roadmap-tree'; import { queryClient } from '../../stores/query-client'; import { AILimitsPopup } from '../GenerateCourse/AILimitsPopup'; import { PredefinedActions } from './PredefinedActions'; import type { ChatStatus, UIMessage } from 'ai'; import type { UseChatHelpers } from '@ai-sdk/react'; import { useAIChatScroll } from '../../hooks/use-ai-chat-scroll'; import { TopicChatMessages } from '../ChatMessages/TopicChatMessages'; type TopicDetailAIProps = { resourceId: string; resourceType: ResourceType; topicId: string; hasUpgradeButtons?: boolean; messages: UIMessage[]; sendMessage: UseChatHelpers['sendMessage']; setMessages: UseChatHelpers['setMessages']; status: ChatStatus; onUpgrade: () => void; onLogin: () => void; onShowSubjectSearchModal: () => void; }; export function TopicDetailAI(props: TopicDetailAIProps) { const { messages, sendMessage, setMessages, status, resourceId, resourceType, topicId, hasUpgradeButtons = true, onUpgrade, onLogin, onShowSubjectSearchModal, } = props; const textareaRef = useRef(null); const formRef = useRef(null); const sanitizedTopicId = topicId?.includes('@') ? topicId?.split('@')?.[1] : topicId; const toast = useToast(); const [message, setMessage] = useState(''); const [showAILimitsPopup, setShowAILimitsPopup] = useState(false); const { data: tokenUsage, isLoading } = useQuery( aiLimitOptions(), queryClient, ); const { data: userBillingDetails, isLoading: isBillingDetailsLoading } = useQuery(billingDetailsOptions(), queryClient); const { data: roadmapTreeMapping, isLoading: isRoadmapTreeMappingLoading } = useQuery( { ...roadmapTreeMappingOptions(resourceId), select: (data) => { const node = data.find( (mapping) => mapping.nodeId === sanitizedTopicId, ); return node; }, }, queryClient, ); const isLimitExceeded = (tokenUsage?.used || 0) >= (tokenUsage?.limit || 0); const isPaidUser = userBillingDetails?.status === 'active'; const handleChatSubmit = (overrideMessage?: string) => { const trimmedMessage = (overrideMessage ?? message).trim(); if ( !trimmedMessage || status !== 'ready' || !isLoggedIn() || isLimitExceeded || isLoading ) { return; } sendMessage( { text: trimmedMessage, }, { body: { resourceId, resourceType, topicId: sanitizedTopicId, }, }, ); setMessage(''); setTimeout(() => { scrollToBottom(); textareaRef.current?.focus(); }, 0); }; const { scrollToBottom, scrollableContainerRef, showScrollToBottomButton } = useAIChatScroll({ messages, }); useEffect(() => { scrollToBottom(); }, []); const isDataLoading = isLoading || isBillingDetailsLoading || isRoadmapTreeMappingLoading; const usagePercentage = getPercentage( tokenUsage?.used || 0, tokenUsage?.limit || 0, ); const hasChatHistory = messages.length > 0; const nodeTextParts = roadmapTreeMapping?.text?.split('>') || []; const hasSubjects = (roadmapTreeMapping?.subjects && roadmapTreeMapping?.subjects?.length > 0) || nodeTextParts.length > 1; return (
{isDataLoading && (
)} {showAILimitsPopup && ( setShowAILimitsPopup(false)} onUpgrade={() => { setShowAILimitsPopup(false); onUpgrade(); }} /> )} {hasSubjects && ( )}
{hasSubjects && ( Chat with AI AI Tutor )} {!hasSubjects && (

AI Tutor

)} {!isDataLoading && (
{hasChatHistory && ( )} {!isPaidUser && hasUpgradeButtons && ( <> )}
)}
{ if (!isLoggedIn()) { onLogin(); return; } if (isLimitExceeded) { onUpgrade(); return; } if (!action?.prompt) { toast.error('Something went wrong'); return; } setMessage(action.prompt); handleChatSubmit(action.prompt); }} />
{ e.preventDefault(); handleChatSubmit(); }} > {isLimitExceeded && isLoggedIn() && (

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

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

Please login to continue

)} {isDataLoading && (

Loading...

)} setMessage(e.target.value)} autoFocus={true} onKeyDown={(e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); handleChatSubmit(); } }} ref={textareaRef} />
); }