feat: add roadmap stream

ai/roadmap
Arik Chakma 11 months ago committed by Kamran Ahmed
parent 58f12af2d7
commit 90df14c545
  1. 47
      src/components/GenerateRoadmap/GenerateRoadmap.tsx
  2. 36
      src/helper/read-stream.ts

@ -2,9 +2,11 @@ import { useRef, useState, type FormEvent } from 'react';
import './GenerateRoadmap.css';
import { httpPost } from '../../lib/http';
import { useToast } from '../../hooks/use-toast';
import { generateRoadmapFromJSON } from '../../../editor/utils/roadmap-generator';
import { generateAIRoadmapFromText } from '../../../editor/utils/roadmap-generator';
import { renderFlowJSON } from '../../../editor/renderer/renderer';
import { replaceChildren } from '../../lib/dom';
import { readAIRoadmapStream } from '../../helper/read-stream';
import { removeAuthToken } from '../../lib/jwt';
export function GenerateRoadmap() {
const roadmapContainerRef = useRef<HTMLDivElement>(null);
@ -18,24 +20,47 @@ export function GenerateRoadmap() {
e.preventDefault();
setIsLoading(true);
const { response, error } = await httpPost(
`${import.meta.env.PUBLIC_API_URL}/v1-generate-roadmap`,
const response = await fetch(
`${import.meta.env.PUBLIC_API_URL}/v1-generate-ai-roadmap`,
{
title: roadmapName,
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
credentials: 'include',
body: JSON.stringify({ title: roadmapName }),
},
);
if (error || !response) {
if (!response.ok) {
const data = await response.json();
toast.error(data?.message || 'Something went wrong');
setIsLoading(false);
toast.error(error?.message || 'Something went wrong');
return;
// Logout user if token is invalid
if (data.status === 401) {
removeAuthToken();
window.location.reload();
}
}
const { nodes, edges } = generateRoadmapFromJSON(response as any);
const svg = await renderFlowJSON({ nodes, edges });
if (roadmapContainerRef?.current) {
replaceChildren(roadmapContainerRef?.current, svg);
const reader = response.body?.getReader();
if (!reader) {
setIsLoading(false);
toast.error('Something went wrong');
return;
}
await readAIRoadmapStream(reader, async (result) => {
const { nodes, edges } = generateAIRoadmapFromText(result);
const svg = await renderFlowJSON({ nodes, edges });
if (roadmapContainerRef?.current) {
replaceChildren(roadmapContainerRef?.current, svg);
}
});
setIsLoading(false);
};

@ -0,0 +1,36 @@
const NEW_LINE = '\n'.charCodeAt(0);
export async function readAIRoadmapStream(
reader: ReadableStreamDefaultReader<Uint8Array>,
renderRoadmap: (roadmap: string) => void,
) {
const decoder = new TextDecoder('utf-8');
let result = '';
while (true) {
const { value, done } = await reader.read();
if (done) {
break;
}
// We will call the renderRoadmap callback whenever we encounter
// a new line with the result until the new line
// otherwise, we will keep appending the result to the previous result
if (value) {
let start = 0;
for (let i = 0; i < value.length; i++) {
if (value[i] === NEW_LINE) {
result += decoder.decode(value.slice(start, i + 1));
renderRoadmap(result);
start = i + 1;
}
}
if (start < value.length) {
result += decoder.decode(value.slice(start));
}
}
}
reader.releaseLock();
renderRoadmap(result);
}
Loading…
Cancel
Save