diff --git a/src/components/GenerateRoadmap/GenerateRoadmap.tsx b/src/components/GenerateRoadmap/GenerateRoadmap.tsx index 07102d431..83f622384 100644 --- a/src/components/GenerateRoadmap/GenerateRoadmap.tsx +++ b/src/components/GenerateRoadmap/GenerateRoadmap.tsx @@ -11,6 +11,8 @@ import { RoadmapSearch } from './RoadmapSearch.tsx'; import { Spinner } from '../ReactIcons/Spinner.tsx'; import { Download, PenSquare, Wand } from 'lucide-react'; import { ShareRoadmapButton } from '../ShareRoadmapButton.tsx'; +import { httpPost } from '../../lib/http.ts'; +import { pageProgressMessage } from '../../stores/page.ts'; export function GenerateRoadmap() { const roadmapContainerRef = useRef(null); @@ -20,6 +22,8 @@ export function GenerateRoadmap() { const [hasSubmitted, setHasSubmitted] = useState(false); const [isLoading, setIsLoading] = useState(false); const [roadmapTopic, setRoadmapTopic] = useState(''); + const [nodes, setNodes] = useState([]); + const [edges, setEdges] = useState([]); const handleSubmit = async (e: FormEvent) => { e.preventDefault(); @@ -66,17 +70,58 @@ export function GenerateRoadmap() { return; } - await readAIRoadmapStream(reader, async (result) => { - const { nodes, edges } = generateAIRoadmapFromText(result); - const svg = await renderFlowJSON({ nodes, edges }); - if (roadmapContainerRef?.current) { - replaceChildren(roadmapContainerRef?.current, svg); - } + await readAIRoadmapStream(reader, { + onStream: async (result) => { + const { nodes, edges } = generateAIRoadmapFromText(result); + const svg = await renderFlowJSON({ nodes, edges }); + if (roadmapContainerRef?.current) { + replaceChildren(roadmapContainerRef?.current, svg); + } + }, + onStreamEnd: async (result) => { + const { nodes, edges } = generateAIRoadmapFromText(result); + setNodes( + nodes.map((node) => ({ + ...node, + + // To reset the width and height of the node + // so that it can be calculated based on the content in the editor + width: undefined, + height: undefined, + style: { + ...node.style, + width: undefined, + height: undefined, + }, + })), + ); + setEdges(edges); + }, }); setIsLoading(false); }; + const editGeneratedRoadmap = async () => { + pageProgressMessage.set('Redirecting to Editor'); + + const { response, error } = await httpPost<{ + roadmapId: string; + }>(`${import.meta.env.PUBLIC_API_URL}/v1-edit-ai-generated-roadmap`, { + title: roadmapTopic, + nodes, + edges, + }); + + if (error || !response) { + toast.error(error?.message || 'Something went wrong'); + setIsLoading(false); + return; + } + + window.location.href = `${import.meta.env.PUBLIC_EDITOR_APP_URL}/${response.roadmapId}`; + }; + if (!hasSubmitted) { return ( - diff --git a/src/helper/read-stream.ts b/src/helper/read-stream.ts index 1e881daf0..422c89c85 100644 --- a/src/helper/read-stream.ts +++ b/src/helper/read-stream.ts @@ -2,7 +2,13 @@ const NEW_LINE = '\n'.charCodeAt(0); export async function readAIRoadmapStream( reader: ReadableStreamDefaultReader, - renderRoadmap: (roadmap: string) => void, + { + onStream, + onStreamEnd, + }: { + onStream?: (roadmap: string) => void; + onStreamEnd?: (roadmap: string) => void; + }, ) { const decoder = new TextDecoder('utf-8'); let result = ''; @@ -21,7 +27,7 @@ export async function readAIRoadmapStream( for (let i = 0; i < value.length; i++) { if (value[i] === NEW_LINE) { result += decoder.decode(value.slice(start, i + 1)); - renderRoadmap(result); + onStream?.(result); start = i + 1; } } @@ -31,6 +37,7 @@ export async function readAIRoadmapStream( } } - renderRoadmap(result); + onStream?.(result); + onStreamEnd?.(result); reader.releaseLock(); }