feat: add limit count

ai/roadmap
Arik Chakma 11 months ago
parent 1aa54662d6
commit b8bdbcb1c5
  1. 101
      src/components/GenerateRoadmap/GenerateRoadmap.tsx
  2. 15
      src/components/GenerateRoadmap/RoadmapSearch.tsx

@ -1,4 +1,4 @@
import { useRef, useState, type FormEvent } from 'react'; import { useEffect, useRef, useState, type FormEvent } from 'react';
import fp from '@fingerprintjs/fingerprintjs'; import fp from '@fingerprintjs/fingerprintjs';
import './GenerateRoadmap.css'; import './GenerateRoadmap.css';
import { useToast } from '../../hooks/use-toast'; import { useToast } from '../../hooks/use-toast';
@ -11,12 +11,16 @@ import { RoadmapSearch } from './RoadmapSearch.tsx';
import { Spinner } from '../ReactIcons/Spinner.tsx'; import { Spinner } from '../ReactIcons/Spinner.tsx';
import { Download, PenSquare, Wand } from 'lucide-react'; import { Download, PenSquare, Wand } from 'lucide-react';
import { ShareRoadmapButton } from '../ShareRoadmapButton.tsx'; import { ShareRoadmapButton } from '../ShareRoadmapButton.tsx';
import { httpPost } from '../../lib/http.ts'; import { httpGet, httpPost } from '../../lib/http.ts';
import { pageProgressMessage } from '../../stores/page.ts'; import { pageProgressMessage } from '../../stores/page.ts';
import { getUrlParams, setUrlParams } from '../../lib/browser.ts';
const ROADMAP_ID_REGEX = new RegExp('@ROADMAPID:(\\w+)@');
export function GenerateRoadmap() { export function GenerateRoadmap() {
const roadmapContainerRef = useRef<HTMLDivElement>(null); const roadmapContainerRef = useRef<HTMLDivElement>(null);
const { id: roadmapId } = getUrlParams() as { id: string };
const toast = useToast(); const toast = useToast();
const [hasSubmitted, setHasSubmitted] = useState<boolean>(false); const [hasSubmitted, setHasSubmitted] = useState<boolean>(false);
@ -24,6 +28,9 @@ export function GenerateRoadmap() {
const [roadmapTopic, setRoadmapTopic] = useState(''); const [roadmapTopic, setRoadmapTopic] = useState('');
const [generatedRoadmap, setGeneratedRoadmap] = useState(''); const [generatedRoadmap, setGeneratedRoadmap] = useState('');
const [roadmapLimit, setRoadmapLimit] = useState(0);
const [roadmapLimitUsed, setRoadmapLimitUsed] = useState(0);
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => { const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault(); e.preventDefault();
setIsLoading(true); setIsLoading(true);
@ -71,6 +78,15 @@ export function GenerateRoadmap() {
await readAIRoadmapStream(reader, { await readAIRoadmapStream(reader, {
onStream: async (result) => { onStream: async (result) => {
if (result.includes('@ROADMAPID')) {
// @ROADMAPID: is a special token that we use to identify the roadmap
// @ROADMAPID:1234@ is the format, we will remove the token and the id
// and replace it with a empty string
const roadmapId = result.match(ROADMAP_ID_REGEX)?.[1] || '';
setUrlParams({ id: roadmapId });
result = result.replace(ROADMAP_ID_REGEX, '');
}
const { nodes, edges } = generateAIRoadmapFromText(result); const { nodes, edges } = generateAIRoadmapFromText(result);
const svg = await renderFlowJSON({ nodes, edges }); const svg = await renderFlowJSON({ nodes, edges });
if (roadmapContainerRef?.current) { if (roadmapContainerRef?.current) {
@ -78,7 +94,9 @@ export function GenerateRoadmap() {
} }
}, },
onStreamEnd: async (result) => { onStreamEnd: async (result) => {
result = result.replace(ROADMAP_ID_REGEX, '');
setGeneratedRoadmap(result); setGeneratedRoadmap(result);
loadAIRoadmapLimit().finally(() => {});
}, },
}); });
@ -159,16 +177,83 @@ export function GenerateRoadmap() {
} }
}; };
const loadAIRoadmapLimit = async () => {
pageProgressMessage.set('Loading Roadmap Limit');
const { response, error } = await httpGet<{
limit: number;
used: number;
}>(`${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;
setRoadmapLimit(limit);
setRoadmapLimitUsed(used);
pageProgressMessage.set('');
};
const loadAIRoadmap = async (roadmapId: string) => {
setIsLoading(true);
pageProgressMessage.set('Loading Roadmap');
const { response, error } = await httpGet<{
topic: string;
data: string;
}>(`${import.meta.env.PUBLIC_API_URL}/v1-get-ai-roadmap/${roadmapId}`);
if (error || !response) {
toast.error(error?.message || 'Something went wrong');
setIsLoading(false);
return;
}
const { topic, data } = response;
const { nodes, edges } = generateAIRoadmapFromText(data);
const svg = await renderFlowJSON({ nodes, edges });
if (roadmapContainerRef?.current) {
replaceChildren(roadmapContainerRef?.current, svg);
}
setRoadmapTopic(topic);
setGeneratedRoadmap(data);
};
useEffect(() => {
loadAIRoadmapLimit().finally(() => {});
}, []);
useEffect(() => {
if (!roadmapId) {
return;
}
setHasSubmitted(true);
loadAIRoadmap(roadmapId).then(() => {
setIsLoading(false);
pageProgressMessage.set('');
});
}, [roadmapId]);
if (!hasSubmitted) { if (!hasSubmitted) {
return ( return (
<RoadmapSearch <RoadmapSearch
roadmapTopic={roadmapTopic} roadmapTopic={roadmapTopic}
setRoadmapTopic={setRoadmapTopic} setRoadmapTopic={setRoadmapTopic}
handleSubmit={handleSubmit} handleSubmit={handleSubmit}
limit={roadmapLimit}
limitUsed={roadmapLimitUsed}
/> />
); );
} }
const pageUrl = `https://roadmap.sh/ai?id=${roadmapId}`;
return ( return (
<section className="flex flex-grow flex-col bg-gray-100"> <section className="flex flex-grow flex-col bg-gray-100">
<div className="flex items-center justify-center border-b bg-white py-6"> <div className="flex items-center justify-center border-b bg-white py-6">
@ -182,7 +267,7 @@ export function GenerateRoadmap() {
<div className="flex max-w-[600px] flex-grow flex-col items-center"> <div className="flex max-w-[600px] flex-grow flex-col items-center">
<div className="mt-2 flex w-full items-center justify-between text-sm"> <div className="mt-2 flex w-full items-center justify-between text-sm">
<span className="text-gray-800"> <span className="text-gray-800">
0 of 5 roadmaps generated{' '} {roadmapLimitUsed} of {roadmapLimit} roadmaps generated{' '}
<button className="font-medium text-black underline underline-offset-2"> <button className="font-medium text-black underline underline-offset-2">
Login to increase your limit Login to increase your limit
</button> </button>
@ -219,10 +304,12 @@ export function GenerateRoadmap() {
<Download size={15} /> <Download size={15} />
Download Download
</button> </button>
<ShareRoadmapButton {roadmapId && (
description={'c'} <ShareRoadmapButton
pageUrl={'https://roadmap.sh'} description={`Check out ${roadmapTopic} roadmap I generated on roadmap.sh`}
/> pageUrl={pageUrl}
/>
)}
</div> </div>
<button <button
className="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 transition-opacity duration-300 hover:bg-gray-300 sm:text-sm" className="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 transition-opacity duration-300 hover:bg-gray-300 sm:text-sm"

@ -5,10 +5,18 @@ type RoadmapSearchProps = {
roadmapTopic: string; roadmapTopic: string;
setRoadmapTopic: (topic: string) => void; setRoadmapTopic: (topic: string) => void;
handleSubmit: (e: FormEvent<HTMLFormElement>) => void; handleSubmit: (e: FormEvent<HTMLFormElement>) => void;
limit: number;
limitUsed: number;
}; };
export function RoadmapSearch(props: RoadmapSearchProps) { export function RoadmapSearch(props: RoadmapSearchProps) {
const { roadmapTopic, setRoadmapTopic, handleSubmit } = props; const {
roadmapTopic,
setRoadmapTopic,
handleSubmit,
limit = 0,
limitUsed = 0,
} = props;
return ( return (
<div className="flex flex-grow flex-col items-center justify-center py-6"> <div className="flex flex-grow flex-col items-center justify-center py-6">
@ -39,7 +47,10 @@ export function RoadmapSearch(props: RoadmapSearchProps) {
</form> </form>
<div className="mb-36"> <div className="mb-36">
<p className="text-gray-500"> <p className="text-gray-500">
You have generated <span className="text-gray-800">0 of 5</span>{' '} You have generated{' '}
<span className="text-gray-800">
{limitUsed} of ${limit}
</span>{' '}
roadmaps today.{' '} roadmaps today.{' '}
<button className="font-semibold text-black underline underline-offset-2"> <button className="font-semibold text-black underline underline-offset-2">
Log in to increase your limit Log in to increase your limit

Loading…
Cancel
Save