diff --git a/src/components/GenerateRoadmap/GenerateRoadmap.tsx b/src/components/GenerateRoadmap/GenerateRoadmap.tsx index 681a373d0..05d03454f 100644 --- a/src/components/GenerateRoadmap/GenerateRoadmap.tsx +++ b/src/components/GenerateRoadmap/GenerateRoadmap.tsx @@ -1,10 +1,10 @@ import { type FormEvent, + type MouseEvent, + useCallback, useEffect, useRef, useState, - useCallback, - type MouseEvent, } from 'react'; import './GenerateRoadmap.css'; import { useToast } from '../../hooks/use-toast'; @@ -19,7 +19,6 @@ import { BadgeCheck, Ban, Download, - PencilRuler, PenSquare, Save, Telescope, @@ -38,6 +37,13 @@ import { showLoginPopup } from '../../lib/popup.ts'; import { cn } from '../../lib/classname.ts'; import { RoadmapTopicDetail } from './RoadmapTopicDetail.tsx'; +export type GetAIRoadmapLimitResponse = { + used: number; + limit: number; + topicUsed: number; + topicLimit: number; +}; + const ROADMAP_ID_REGEX = new RegExp('@ROADMAPID:(\\w+)@'); export type RoadmapNodeDetails = { @@ -93,6 +99,8 @@ export function GenerateRoadmap() { const [roadmapLimit, setRoadmapLimit] = useState(0); const [roadmapLimitUsed, setRoadmapLimitUsed] = useState(0); + const [roadmapTopicLimit, setRoadmapTopicLimit] = useState(0); + const [roadmapTopicLimitUsed, setRoadmapTopicLimitUsed] = useState(0); const renderRoadmap = async (roadmap: string) => { const { nodes, edges } = generateAIRoadmapFromText(roadmap); @@ -249,19 +257,20 @@ export function GenerateRoadmap() { }; const loadAIRoadmapLimit = async () => { - const { response, error } = await httpGet<{ - limit: number; - used: number; - }>(`${import.meta.env.PUBLIC_API_URL}/v1-get-ai-roadmap-limit`); + const { response, error } = await httpGet( + `${import.meta.env.PUBLIC_API_URL}/v1-get-ai-roadmap-limit`, + ); if (error || !response) { toast.error(error?.message || 'Something went wrong'); return; } - const { limit, used } = response; + const { limit, used, topicLimit, topicUsed } = response; setRoadmapLimit(limit); setRoadmapLimitUsed(used); + setRoadmapTopicLimit(topicLimit); + setRoadmapTopicLimitUsed(topicUsed); }; const loadAIRoadmap = async (roadmapId: string) => { @@ -369,6 +378,11 @@ export function GenerateRoadmap() { parentTitle={selectedTopic.parentTitle} onClose={() => setSelectedTopic(null)} roadmapId={currentRoadmap?.id || ''} + topicLimit={roadmapTopicLimit} + topicLimitUsed={roadmapTopicLimitUsed} + onTopicContentGenerateComplete={async () => { + await loadAIRoadmapLimit(); + }} /> )} @@ -389,23 +403,23 @@ export function GenerateRoadmap() { Beta -

+

This is an AI generated roadmap and is not verified by{' '} roadmap.sh. We are currently in beta and working hard to improve the quality of the generated roadmaps.

-

+

Explore other AI Roadmaps Visit Official Roadmaps diff --git a/src/components/GenerateRoadmap/RoadmapTopicDetail.tsx b/src/components/GenerateRoadmap/RoadmapTopicDetail.tsx index 8facf5ad2..1e37ad7cf 100644 --- a/src/components/GenerateRoadmap/RoadmapTopicDetail.tsx +++ b/src/components/GenerateRoadmap/RoadmapTopicDetail.tsx @@ -6,16 +6,29 @@ import { markdownToHtml } from '../../lib/markdown'; import { Ban, FileText, X } from 'lucide-react'; import { Spinner } from '../ReactIcons/Spinner'; import type { RoadmapNodeDetails } from './GenerateRoadmap'; -import { removeAuthToken } from '../../lib/jwt'; +import { isLoggedIn, removeAuthToken } from '../../lib/jwt'; import { readAIRoadmapContentStream } from '../../helper/read-stream'; +import { cn } from '../../lib/classname'; +import { showLoginPopup } from '../../lib/popup'; type RoadmapTopicDetailProps = RoadmapNodeDetails & { onClose?: () => void; roadmapId: string; + topicLimitUsed: number; + topicLimit: number; + onTopicContentGenerateComplete?: () => void; }; export function RoadmapTopicDetail(props: RoadmapTopicDetailProps) { - const { onClose, roadmapId, nodeTitle, parentTitle } = props; + const { + onClose, + roadmapId, + nodeTitle, + parentTitle, + topicLimit, + topicLimitUsed, + onTopicContentGenerateComplete, + } = props; const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(''); @@ -28,6 +41,12 @@ export function RoadmapTopicDetail(props: RoadmapTopicDetailProps) { setIsLoading(true); setError(''); + if (topicLimitUsed >= topicLimit) { + setError('You have reached the limit of topics'); + setIsLoading(false); + return; + } + if (!roadmapId || !nodeTitle) { setIsLoading(false); setError('Invalid roadmap id or node title'); @@ -76,6 +95,7 @@ export function RoadmapTopicDetail(props: RoadmapTopicDetailProps) { setTopicHtml(markdownToHtml(result, false)); }, }); + onTopicContentGenerateComplete?.(); }; // Close the topic detail when user clicks outside the topic detail @@ -109,8 +129,35 @@ export function RoadmapTopicDetail(props: RoadmapTopicDetailProps) { tabIndex={0} className="fixed right-0 top-0 z-40 h-screen w-full overflow-y-auto bg-white p-4 focus:outline-0 sm:max-w-[600px] sm:p-6" > +

+ + + {topicLimitUsed} of {topicLimit} + {' '} + topic content generated. + + {!isLoggedIn() && ( + + )} +
+ {isLoading && ( -
+