import '../GenerateRoadmap/GenerateRoadmap.css'; import { renderFlowJSON } from '../../../editor/renderer/renderer'; import { generateAICourseRoadmapStructure, readAIRoadmapStream, type ResultItem } from '../../lib/ai'; import { useCallback, useEffect, useRef, useState, type Dispatch, type SetStateAction, type MouseEvent } from 'react'; import type { AICourseViewMode } from './AICourseContent'; import { replaceChildren } from '../../lib/dom'; import { Frown, Loader2Icon } from 'lucide-react'; import { renderTopicProgress } from '../../lib/resource-progress'; import { queryClient } from '../../stores/query-client'; import { useQuery } from '@tanstack/react-query'; import { billingDetailsOptions } from '../../queries/billing'; import { AICourseOutlineHeader } from './AICourseOutlineHeader'; import type { AiCourse } from '../../lib/ai'; import { generateAIRoadmapFromText } from '../../utils/roadmapUtils'; // 'someUtilityFunction' kaldırıldı export type AICourseRoadmapViewProps = { done: string[]; courseSlug: string; course: AiCourse; isLoading: boolean; onRegenerateOutline: (prompt?: string) => void; setActiveModuleIndex: (index: number) => void; setActiveLessonIndex: (index: number) => void; setViewMode: (mode: AICourseViewMode) => void; onUpgradeClick: () => void; setExpandedModules: Dispatch>>; viewMode: AICourseViewMode; }; export function AICourseRoadmapView(props: AICourseRoadmapViewProps) { const { done = [], courseSlug, course, isLoading, onRegenerateOutline, setActiveModuleIndex, setActiveLessonIndex, setViewMode, setExpandedModules, onUpgradeClick, viewMode, } = props; const containerEl = useRef(null); const [roadmapStructure, setRoadmapStructure] = useState([]); const [isGenerating, setIsGenerating] = useState(false); const [error, setError] = useState(null); const { data: userBillingDetails, isLoading: isBillingDetailsLoading } = useQuery(billingDetailsOptions(), queryClient); const isPaidUser = userBillingDetails?.status === 'active'; const generateAICourseRoadmap = async (courseSlug: string) => { try { const response = await fetch( `${import.meta.env.PUBLIC_API_URL}/v1-generate-ai-course-roadmap/${courseSlug}`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, credentials: 'include', }, ); if (!response.ok) { const data = await response.json(); console.error( 'Error generating course roadmap:', data?.message || 'Something went wrong', ); setError(data?.message || 'Something went wrong'); setIsGenerating(false); return; } const reader = response.body?.getReader(); if (!reader) { console.error('Failed to get reader from response'); setError('Something went wrong'); setIsGenerating(false); return; } setIsGenerating(true); await readAIRoadmapStream(reader, { onStream: async (result) => { const roadmap = generateAICourseRoadmapStructure(result, true); const { nodes, edges } = generateAIRoadmapFromText({ nodes: roadmap.filter((item) => item.type === 'node' as any) as ResultItem[], edges: roadmap.filter((item) => item.type === 'edge' as any) as ResultItem[], }); const svg = await renderFlowJSON({ nodes, edges }); if (svg !== undefined && svg !== null) { replaceChildren(containerEl.current!, svg); // Fixed usage } }, onStreamEnd: async (result) => { const roadmap = generateAICourseRoadmapStructure(result, true); const { nodes, edges } = generateAIRoadmapFromText({ nodes: roadmap.filter((item) => item.type === 'node' as any) as ResultItem[], edges: roadmap.filter((item) => item.type === 'edge' as any) as ResultItem[], }); const svg = await renderFlowJSON({ nodes, edges }); if (svg !== undefined && svg !== null) { replaceChildren(containerEl.current!, svg); // Fixed usage } setRoadmapStructure(roadmap); setIsGenerating(false); done.forEach((id) => { renderTopicProgress(id, 'done'); }); const modules = roadmap.filter((item) => item.type === 'topic'); for (const module of modules) { const moduleId = module.id; const isAllLessonsDone = module?.children?.every((child) => done.includes(child.id)) ?? false; if (isAllLessonsDone) { renderTopicProgress(moduleId, 'done'); } } }, }); } catch (error) { console.error('Error generating course roadmap:', error); setError('Something went wrong'); setIsGenerating(false); } }; useEffect(() => { if (!courseSlug) { return; } generateAICourseRoadmap(courseSlug); }, []); const handleNodeClick = useCallback( (e: MouseEvent) => { if (isGenerating) { return; } const target = e.target as SVGElement; const targetGroup = (target?.closest('g') as SVGElement) || {}; const nodeId = targetGroup?.dataset?.nodeId; const nodeType = targetGroup?.dataset?.type; if (!nodeId || !nodeType) { return null; } if (nodeType === 'topic') { const topicIndex = roadmapStructure .filter((item) => item.type === 'topic') .findIndex((item) => item.id === nodeId); setExpandedModules((prev) => { const newState: Record = {}; roadmapStructure.forEach((_, idx) => { newState[idx] = false; }); newState[topicIndex] = true; return newState; }); setActiveModuleIndex(topicIndex); setActiveLessonIndex(0); setViewMode('module'); return; } if (nodeType !== 'subtopic') { return null; } const [moduleIndex, topicIndex] = nodeId.split('-').map(Number); setExpandedModules((prev) => { const newState: Record = {}; roadmapStructure.forEach((_, idx) => { newState[idx] = false; }); newState[moduleIndex] = true; return newState; }); setActiveModuleIndex(moduleIndex); setActiveLessonIndex(topicIndex); setViewMode('module'); }, [ roadmapStructure, setExpandedModules, setActiveModuleIndex, setActiveLessonIndex, setViewMode, ], ); return (
{ setViewMode('outline'); onRegenerateOutline(prompt); }} viewMode={viewMode} setViewMode={setViewMode} /> {isLoading && (
)} {error && !isGenerating && (

{error || 'Something went wrong'}

{!isPaidUser && (error || '')?.includes('limit') && ( )}
)}
); }