computer-scienceangular-roadmapbackend-roadmapblockchain-roadmapdba-roadmapdeveloper-roadmapdevops-roadmapfrontend-roadmapgo-roadmaphactoberfestjava-roadmapjavascript-roadmapnodejs-roadmappython-roadmapqa-roadmapreact-roadmaproadmapstudy-planvue-roadmapweb3-roadmap
302 lines
6.4 KiB
302 lines
6.4 KiB
import { nanoid } from 'nanoid'; |
|
|
|
export const IS_KEY_ONLY_ROADMAP_GENERATION = false; |
|
|
|
type Lesson = string; |
|
|
|
type Module = { |
|
title: string; |
|
lessons: Lesson[]; |
|
}; |
|
|
|
export type AiCourse = { |
|
title: string; |
|
modules: Module[]; |
|
difficulty: string; |
|
done: string[]; |
|
}; |
|
|
|
export function generateAiCourseStructure( |
|
data: string, |
|
): Omit<AiCourse, 'difficulty'> { |
|
const lines = data.split('\n'); |
|
let title = ''; |
|
const modules: Module[] = []; |
|
let currentModule: Module | null = null; |
|
|
|
for (let i = 0; i < lines.length; i++) { |
|
const line = lines[i].trim(); |
|
|
|
if (i === 0 && line.startsWith('#')) { |
|
// First line is the title |
|
title = line.replace('#', '').trim(); |
|
} else if (line.startsWith('## ')) { |
|
// New module |
|
if (currentModule) { |
|
modules.push(currentModule); |
|
} |
|
currentModule = { |
|
title: line.replace('## ', ''), |
|
lessons: [], |
|
}; |
|
// Removed auto-expand code to keep modules collapsed by default |
|
} else if (line.startsWith('- ') && currentModule) { |
|
// Lesson within current module |
|
currentModule.lessons.push(line.replace('- ', '')); |
|
} |
|
} |
|
|
|
// Add the last module if it exists |
|
if (currentModule) { |
|
modules.push(currentModule); |
|
} |
|
|
|
return { |
|
title, |
|
modules, |
|
done: [], |
|
}; |
|
} |
|
|
|
type CourseFineTuneData = { |
|
about: string; |
|
goal: string; |
|
customInstructions: string; |
|
}; |
|
|
|
export function storeFineTuneData(meta: CourseFineTuneData) { |
|
const sessionId = Date.now().toString(); |
|
|
|
localStorage.setItem(sessionId, JSON.stringify(meta)); |
|
localStorage.setItem('lastSessionId', sessionId); |
|
|
|
return sessionId; |
|
} |
|
|
|
export function getCourseFineTuneData( |
|
sessionId: string, |
|
): CourseFineTuneData | null { |
|
const meta = localStorage.getItem(sessionId); |
|
if (!meta) { |
|
return null; |
|
} |
|
|
|
return JSON.parse(meta); |
|
} |
|
|
|
export function getLastSessionId(): string | null { |
|
return localStorage.getItem('lastSessionId'); |
|
} |
|
|
|
export function clearFineTuneData() { |
|
const sessionId = getLastSessionId(); |
|
if (sessionId) { |
|
localStorage.removeItem(sessionId); |
|
} |
|
|
|
localStorage.removeItem('lastSessionId'); |
|
} |
|
|
|
const NEW_LINE = '\n'.charCodeAt(0); |
|
|
|
export async function readAIRoadmapStream( |
|
reader: ReadableStreamDefaultReader<Uint8Array>, |
|
{ |
|
onStream, |
|
onStreamEnd, |
|
}: { |
|
onStream?: (roadmap: string) => void; |
|
onStreamEnd?: (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)); |
|
onStream?.(result); |
|
start = i + 1; |
|
} |
|
} |
|
if (start < value.length) { |
|
result += decoder.decode(value.slice(start)); |
|
} |
|
} |
|
} |
|
|
|
onStream?.(result); |
|
onStreamEnd?.(result); |
|
reader.releaseLock(); |
|
} |
|
|
|
export async function readAIRoadmapContentStream( |
|
reader: ReadableStreamDefaultReader<Uint8Array>, |
|
{ |
|
onStream, |
|
onStreamEnd, |
|
}: { |
|
onStream?: (roadmap: string) => void; |
|
onStreamEnd?: (roadmap: string) => void; |
|
}, |
|
) { |
|
const decoder = new TextDecoder('utf-8'); |
|
let result = ''; |
|
|
|
while (true) { |
|
const { value, done } = await reader.read(); |
|
if (done) { |
|
break; |
|
} |
|
|
|
if (value) { |
|
result += decoder.decode(value); |
|
onStream?.(result); |
|
} |
|
} |
|
|
|
onStream?.(result); |
|
onStreamEnd?.(result); |
|
reader.releaseLock(); |
|
} |
|
|
|
export async function readStream( |
|
reader: ReadableStreamDefaultReader<Uint8Array>, |
|
{ |
|
onStream, |
|
onStreamEnd, |
|
}: { |
|
onStream?: (course: string) => void; |
|
onStreamEnd?: (course: string) => void; |
|
}, |
|
) { |
|
const decoder = new TextDecoder('utf-8'); |
|
let result = ''; |
|
|
|
while (true) { |
|
const { value, done } = await reader.read(); |
|
if (done) { |
|
break; |
|
} |
|
|
|
// Process the stream data as it comes in |
|
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)); |
|
onStream?.(result); |
|
start = i + 1; |
|
} |
|
} |
|
if (start < value.length) { |
|
result += decoder.decode(value.slice(start)); |
|
} |
|
} |
|
} |
|
|
|
onStream?.(result); |
|
onStreamEnd?.(result); |
|
reader.releaseLock(); |
|
} |
|
|
|
export type SubTopic = { |
|
id: string; |
|
type: 'subtopic'; |
|
label: string; |
|
}; |
|
|
|
export type Topic = { |
|
id: string; |
|
type: 'topic'; |
|
label: string; |
|
children?: SubTopic[]; |
|
}; |
|
|
|
export type Label = { |
|
id: string; |
|
type: 'label'; |
|
label: string; |
|
}; |
|
|
|
export type Title = { |
|
id: string; |
|
type: 'title'; |
|
label: string; |
|
}; |
|
|
|
export type ResultItem = Title | Topic | Label; |
|
|
|
export function generateAICourseRoadmapStructure( |
|
data: string, |
|
isCourseRoadmap: boolean = false, |
|
): ResultItem[] { |
|
const lines = data.split('\n'); |
|
|
|
const result: ResultItem[] = []; |
|
let currentTopic: Topic | null = null; |
|
|
|
for (let i = 0; i < lines.length; i++) { |
|
const line = lines[i].trim(); |
|
if (line.startsWith('###')) { |
|
if (currentTopic) { |
|
result.push(currentTopic); |
|
} |
|
|
|
const label = line.replace('###', '').trim(); |
|
currentTopic = { |
|
id: nanoid(), |
|
type: 'topic', |
|
label, |
|
children: [], |
|
}; |
|
} else if (line.startsWith('##')) { |
|
result.push({ |
|
id: nanoid(), |
|
type: 'label', |
|
label: line.replace('##', '').trim(), |
|
}); |
|
} else if (i === 0 && line.startsWith('#')) { |
|
const title = line.replace('#', '').trim(); |
|
result.push({ |
|
id: nanoid(), |
|
type: 'title', |
|
label: title, |
|
}); |
|
} else if (line.startsWith('-')) { |
|
if (currentTopic) { |
|
const label = line.replace('-', '').trim(); |
|
|
|
let id = nanoid(); |
|
if (isCourseRoadmap) { |
|
const currentTopicIndex = result.length - 1; |
|
const subTopicIndex = currentTopic.children?.length || 0; |
|
id = `${currentTopicIndex}-${subTopicIndex}`; |
|
} |
|
|
|
currentTopic.children?.push({ |
|
id, |
|
type: 'subtopic', |
|
label, |
|
}); |
|
} |
|
} |
|
} |
|
|
|
if (currentTopic) { |
|
result.push(currentTopic); |
|
} |
|
|
|
return result; |
|
}
|
|
|