|
|
@ -50,6 +50,7 @@ export type GetAIRoadmapLimitResponse = { |
|
|
|
}; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const ROADMAP_ID_REGEX = new RegExp('@ROADMAPID:(\\w+)@'); |
|
|
|
const ROADMAP_ID_REGEX = new RegExp('@ROADMAPID:(\\w+)@'); |
|
|
|
|
|
|
|
const ROADMAP_SLUG_REGEX = new RegExp(/@ROADMAPSLUG:([\w-]+)@/); |
|
|
|
|
|
|
|
|
|
|
|
export type RoadmapNodeDetails = { |
|
|
|
export type RoadmapNodeDetails = { |
|
|
|
nodeId: string; |
|
|
|
nodeId: string; |
|
|
@ -89,11 +90,11 @@ type GetAIRoadmapResponse = { |
|
|
|
|
|
|
|
|
|
|
|
type GenerateRoadmapProps = { |
|
|
|
type GenerateRoadmapProps = { |
|
|
|
roadmapId?: string; |
|
|
|
roadmapId?: string; |
|
|
|
t?: string; |
|
|
|
slug?: string; |
|
|
|
}; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
export function GenerateRoadmap(props: GenerateRoadmapProps) { |
|
|
|
export function GenerateRoadmap(props: GenerateRoadmapProps) { |
|
|
|
const { roadmapId, t: term = '' } = props; |
|
|
|
const { roadmapId: defaultRoadmapId, slug: defaultRoadmapSlug } = props; |
|
|
|
|
|
|
|
|
|
|
|
const roadmapContainerRef = useRef<HTMLDivElement>(null); |
|
|
|
const roadmapContainerRef = useRef<HTMLDivElement>(null); |
|
|
|
|
|
|
|
|
|
|
@ -102,13 +103,19 @@ export function GenerateRoadmap(props: GenerateRoadmapProps) { |
|
|
|
}; |
|
|
|
}; |
|
|
|
const toast = useToast(); |
|
|
|
const toast = useToast(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const [roadmapId, setRoadmapId] = useState<string | undefined>( |
|
|
|
|
|
|
|
defaultRoadmapId, |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
const [roadmapSlug, setRoadmapSlug] = useState<string | undefined>( |
|
|
|
|
|
|
|
defaultRoadmapSlug, |
|
|
|
|
|
|
|
); |
|
|
|
const [hasSubmitted, setHasSubmitted] = useState<boolean>(Boolean(roadmapId)); |
|
|
|
const [hasSubmitted, setHasSubmitted] = useState<boolean>(Boolean(roadmapId)); |
|
|
|
const [isLoading, setIsLoading] = useState(false); |
|
|
|
const [isLoading, setIsLoading] = useState(false); |
|
|
|
const [isLoadingResults, setIsLoadingResults] = useState(false); |
|
|
|
const [isLoadingResults, setIsLoadingResults] = useState(false); |
|
|
|
const [roadmapTerm, setRoadmapTerm] = useState(term); |
|
|
|
const [roadmapTerm, setRoadmapTerm] = useState(''); |
|
|
|
const [generatedRoadmapContent, setGeneratedRoadmapContent] = useState(''); |
|
|
|
|
|
|
|
const [currentRoadmap, setCurrentRoadmap] = |
|
|
|
const [currentRoadmap, setCurrentRoadmap] = |
|
|
|
useState<GetAIRoadmapResponse | null>(null); |
|
|
|
useState<GetAIRoadmapResponse | null>(null); |
|
|
|
|
|
|
|
const [generatedRoadmapContent, setGeneratedRoadmapContent] = useState(''); |
|
|
|
const [selectedNode, setSelectedNode] = useState<RoadmapNodeDetails | null>( |
|
|
|
const [selectedNode, setSelectedNode] = useState<RoadmapNodeDetails | null>( |
|
|
|
null, |
|
|
|
null, |
|
|
|
); |
|
|
|
); |
|
|
@ -140,6 +147,8 @@ export function GenerateRoadmap(props: GenerateRoadmapProps) { |
|
|
|
deleteUrlParam('id'); |
|
|
|
deleteUrlParam('id'); |
|
|
|
setCurrentRoadmap(null); |
|
|
|
setCurrentRoadmap(null); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const origin = window.location.origin; |
|
|
|
|
|
|
|
window.history.pushState(null, '', `${origin}/ai`); |
|
|
|
const response = await fetch( |
|
|
|
const response = await fetch( |
|
|
|
`${import.meta.env.PUBLIC_API_URL}/v1-generate-ai-roadmap`, |
|
|
|
`${import.meta.env.PUBLIC_API_URL}/v1-generate-ai-roadmap`, |
|
|
|
{ |
|
|
|
{ |
|
|
@ -175,13 +184,21 @@ export function GenerateRoadmap(props: GenerateRoadmapProps) { |
|
|
|
|
|
|
|
|
|
|
|
await readAIRoadmapStream(reader, { |
|
|
|
await readAIRoadmapStream(reader, { |
|
|
|
onStream: async (result) => { |
|
|
|
onStream: async (result) => { |
|
|
|
if (result.includes('@ROADMAPID')) { |
|
|
|
if (result.includes('@ROADMAPID') || result.includes('@ROADMAPSLUG')) { |
|
|
|
// @ROADMAPID: is a special token that we use to identify the roadmap
|
|
|
|
// @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
|
|
|
|
// @ROADMAPID:1234@ is the format, we will remove the token and the id
|
|
|
|
// and replace it with a empty string
|
|
|
|
// and replace it with a empty string
|
|
|
|
const roadmapId = result.match(ROADMAP_ID_REGEX)?.[1] || ''; |
|
|
|
const roadmapId = result.match(ROADMAP_ID_REGEX)?.[1] || ''; |
|
|
|
setUrlParams({ id: roadmapId }); |
|
|
|
const roadmapSlug = result.match(ROADMAP_SLUG_REGEX)?.[1] || ''; |
|
|
|
result = result.replace(ROADMAP_ID_REGEX, ''); |
|
|
|
|
|
|
|
|
|
|
|
window.history.pushState(null, '', `${origin}/ai/${roadmapSlug}`); |
|
|
|
|
|
|
|
result = result |
|
|
|
|
|
|
|
.replace(ROADMAP_ID_REGEX, '') |
|
|
|
|
|
|
|
.replace(ROADMAP_SLUG_REGEX, ''); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
setRoadmapId(roadmapId); |
|
|
|
|
|
|
|
setRoadmapSlug(roadmapSlug); |
|
|
|
|
|
|
|
|
|
|
|
const roadmapTitle = |
|
|
|
const roadmapTitle = |
|
|
|
result.trim().split('\n')[0]?.replace('#', '')?.trim() || term; |
|
|
|
result.trim().split('\n')[0]?.replace('#', '')?.trim() || term; |
|
|
|
setRoadmapTerm(roadmapTitle); |
|
|
|
setRoadmapTerm(roadmapTitle); |
|
|
@ -196,7 +213,10 @@ export function GenerateRoadmap(props: GenerateRoadmapProps) { |
|
|
|
await renderRoadmap(result); |
|
|
|
await renderRoadmap(result); |
|
|
|
}, |
|
|
|
}, |
|
|
|
onStreamEnd: async (result) => { |
|
|
|
onStreamEnd: async (result) => { |
|
|
|
result = result.replace(ROADMAP_ID_REGEX, ''); |
|
|
|
result = result |
|
|
|
|
|
|
|
.replace(ROADMAP_ID_REGEX, '') |
|
|
|
|
|
|
|
.replace(ROADMAP_SLUG_REGEX, ''); |
|
|
|
|
|
|
|
|
|
|
|
setGeneratedRoadmapContent(result); |
|
|
|
setGeneratedRoadmapContent(result); |
|
|
|
loadAIRoadmapLimit().finally(() => {}); |
|
|
|
loadAIRoadmapLimit().finally(() => {}); |
|
|
|
}, |
|
|
|
}, |
|
|
@ -391,7 +411,6 @@ export function GenerateRoadmap(props: GenerateRoadmapProps) { |
|
|
|
return; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
setHasSubmitted(true); |
|
|
|
|
|
|
|
loadAIRoadmap(roadmapId).finally(() => { |
|
|
|
loadAIRoadmap(roadmapId).finally(() => { |
|
|
|
pageProgressMessage.set(''); |
|
|
|
pageProgressMessage.set(''); |
|
|
|
}); |
|
|
|
}); |
|
|
@ -415,7 +434,7 @@ export function GenerateRoadmap(props: GenerateRoadmapProps) { |
|
|
|
); |
|
|
|
); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const pageUrl = `https://roadmap.sh/ai?id=${roadmapId}`; |
|
|
|
const pageUrl = `https://roadmap.sh/ai/${roadmapSlug}`; |
|
|
|
const canGenerateMore = roadmapLimitUsed < roadmapLimit; |
|
|
|
const canGenerateMore = roadmapLimitUsed < roadmapLimit; |
|
|
|
|
|
|
|
|
|
|
|
return ( |
|
|
|
return ( |
|
|
@ -530,7 +549,7 @@ export function GenerateRoadmap(props: GenerateRoadmapProps) { |
|
|
|
)} |
|
|
|
)} |
|
|
|
{!isAuthenticatedUser && ( |
|
|
|
{!isAuthenticatedUser && ( |
|
|
|
<button |
|
|
|
<button |
|
|
|
className="rounded-xl border border-current px-2.5 py-0.5 text-left text-sm font-medium text-blue-500 transition-colors hover:bg-blue-500 hover:text-white sm:text-center" |
|
|
|
className="mt-2 rounded-xl border border-current px-2.5 py-0.5 text-left text-sm font-medium text-blue-500 transition-colors hover:bg-blue-500 hover:text-white sm:text-center" |
|
|
|
onClick={showLoginPopup} |
|
|
|
onClick={showLoginPopup} |
|
|
|
> |
|
|
|
> |
|
|
|
Login to generate your own roadmaps |
|
|
|
Login to generate your own roadmaps |
|
|
|