chore: limit roadmap topic content generation

fix/ai-roadmap
Arik Chakma 9 months ago
parent ecb803e78d
commit 560e9b0229
  1. 41
      src/components/GenerateRoadmap/GenerateRoadmap.tsx
  2. 53
      src/components/GenerateRoadmap/RoadmapTopicDetail.tsx

@ -29,6 +29,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 = {
@ -84,6 +91,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);
@ -240,19 +249,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<GetAIRoadmapLimitResponse>(
`${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) => {
@ -360,6 +370,11 @@ export function GenerateRoadmap() {
parentTitle={selectedTopic.parentTitle}
onClose={() => setSelectedTopic(null)}
roadmapId={currentRoadmap?.id || ''}
topicLimit={roadmapTopicLimit}
topicLimitUsed={roadmapTopicLimitUsed}
onTopicContentGenerateComplete={async () => {
await loadAIRoadmapLimit();
}}
/>
)}
@ -393,10 +408,14 @@ export function GenerateRoadmap() {
</span>
{!isLoggedIn() && (
<button
className="rounded-xl border border-current px-1.5 py-0.5 text-sm font-medium text-blue-500 text-left sm:text-center"
className="rounded-xl border border-current px-1.5 py-0.5 text-left text-sm font-medium text-blue-500 sm:text-center"
onClick={showLoginPopup}
>
Generate more by <span className='font-semibold'>signing up (free, takes 2s)</span> or <span className='font-semibold'>logging in</span>
Generate more by{' '}
<span className="font-semibold">
signing up (free, takes 2s)
</span>{' '}
or <span className="font-semibold">logging in</span>
</button>
)}
</div>
@ -472,12 +491,14 @@ export function GenerateRoadmap() {
disabled={isLoading}
>
<Save size={15} />
<span className='hidden sm:inline'>Track your Progress on this Roadmap</span>
<span className='inline sm:hidden'>Track Progress</span>
<span className="hidden sm:inline">
Track your Progress on this Roadmap
</span>
<span className="inline sm:hidden">Track Progress</span>
</button>
<button
className="hidden sm:inline-flex items-center justify-center gap-2 rounded-md bg-gray-200 py-1.5 pl-2.5 pr-3 text-xs font-medium text-black transition-colors duration-300 hover:bg-gray-300 sm:text-sm"
className="hidden items-center justify-center gap-2 rounded-md bg-gray-200 py-1.5 pl-2.5 pr-3 text-xs font-medium text-black transition-colors duration-300 hover:bg-gray-300 sm:inline-flex sm:text-sm"
onClick={async () => {
const roadmapId = await saveAIRoadmap();
if (roadmapId) {

@ -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"
>
<div className="flex flex-col items-start gap-2">
<span>
<span
className={cn(
'mr-0.5 inline-block rounded-xl border px-1.5 text-center text-sm tabular-nums text-gray-800',
{
'animate-pulse border-zinc-300 bg-zinc-300 text-zinc-300':
!topicLimit,
},
)}
>
{topicLimitUsed} of {topicLimit}
</span>{' '}
topic content generated.
</span>
{!isLoggedIn() && (
<button
className="rounded-xl border border-current px-1.5 py-0.5 text-left text-sm font-medium text-blue-500 sm:text-center"
onClick={showLoginPopup}
>
Generate more by{' '}
<span className="font-semibold">signing up (free, takes 2s)</span>{' '}
or <span className="font-semibold">logging in</span>
</button>
)}
</div>
{isLoading && (
<div className="flex w-full justify-center">
<div className="mt-6 flex w-full justify-center">
<Spinner
outerFill="#d1d5db"
className="h-6 w-6 sm:h-12 sm:w-12"

Loading…
Cancel
Save