import React, { useEffect, useState, useCallback, useRef } from 'react'; import mermaid from 'mermaid'; import { TransformWrapper, TransformComponent } from 'react-zoom-pan-pinch'; import { Note, NoteType } from '../../types'; import { chatWithStep } from '../../services/geminiService'; import ReactMarkdown from 'react-markdown'; import { motion, AnimatePresence } from 'framer-motion'; interface FlowchartViewProps { notes: Record; projectTitle: string; rootNoteId: string; } interface ChatMessage { role: 'user' | 'model'; content: string; } const FlowchartView: React.FC = ({ notes, projectTitle, rootNoteId }) => { const [svgContent, setSvgContent] = useState(''); const [isRendering, setIsRendering] = useState(false); const [selectedNoteId, setSelectedNoteId] = useState(null); const [chatHistories, setChatHistories] = useState>({}); const [userInput, setUserInput] = useState(''); const [isAsking, setIsAsking] = useState(false); const chatEndRef = useRef(null); useEffect(() => { mermaid.initialize({ startOnLoad: false, theme: 'dark', securityLevel: 'loose', flowchart: { useMaxWidth: true, htmlLabels: true, curve: 'basis' } }); }, []); const generateMermaidCode = useCallback(() => { if (!notes[rootNoteId]) return ""; let code = "graph TD\n"; // Styling code += "classDef root fill:#4f46e5,stroke:#fff,stroke-width:2px,color:#fff,cursor:pointer;\n"; code += "classDef text fill:#1e293b,stroke:#475569,stroke-width:1px,color:#cbd5e1,cursor:pointer;\n"; code += "classDef code fill:#0f172a,stroke:#3b82f6,stroke-width:1px,color:#60a5fa,cursor:pointer;\n"; code += "classDef image fill:#1e293b,stroke:#8b5cf6,stroke-width:1px,color:#a78bfa,cursor:pointer;\n"; code += "classDef selected fill:#fbbf24,stroke:#fff,stroke-width:3px,color:#000,cursor:pointer;\n"; const visited = new Set(); const processNote = (noteId: string) => { if (visited.has(noteId)) return ""; visited.add(noteId); const note = notes[noteId]; if (!note) return ""; let nodeCode = ""; const id = note.id.replace(/-/g, ''); const title = note.title.replace(/[()"[\]{}]/g, "'"); // Node definition const isSelected = selectedNoteId === note.id; const styleClass = isSelected ? 'selected' : ( note.type === NoteType.ROOT ? 'root' : note.type === NoteType.CODE ? 'code' : note.type === NoteType.IMAGE ? 'image' : 'text' ); if (note.type === NoteType.ROOT) { nodeCode += ` ${id}("${title}"):::${styleClass}\n`; } else { nodeCode += ` ${id}["${title}"]:::${styleClass}\n`; } // Connections if (note.children && note.children.length > 0) { note.children.forEach(childId => { const child = notes[childId]; if (child) { const childCleanId = child.id.replace(/-/g, ''); nodeCode += ` ${id} --> ${childCleanId}\n`; nodeCode += processNote(childId); } }); } return nodeCode; }; code += processNote(rootNoteId); return code; }, [notes, rootNoteId, selectedNoteId]); useEffect(() => { const code = generateMermaidCode(); if (!code) return; let isMounted = true; const renderDiagram = async () => { setIsRendering(true); try { const id = `mermaid-render-${Date.now()}`; const { svg } = await mermaid.render(id, code); if (isMounted) { setSvgContent(svg); } } catch (error) { console.error("Mermaid render error:", error); } finally { if (isMounted) setIsRendering(false); } }; renderDiagram(); return () => { isMounted = false; }; }, [generateMermaidCode]); // Handle node clicks const handleSvgClick = (e: React.MouseEvent) => { const target = e.target as SVGElement; const node = target.closest('.node'); if (node) { const nodeIdClean = node.id.split('-')[0]; // Mermaid nodes have IDs // Map back to our IDs const foundId = Object.keys(notes).find(id => id.replace(/-/g, '') === nodeIdClean); if (foundId) { setSelectedNoteId(foundId); } } }; const handleSendMessage = async () => { if (!selectedNoteId || !userInput.trim() || isAsking) return; const note = notes[selectedNoteId]; const currentHistory = chatHistories[selectedNoteId] || []; const newHistory: ChatMessage[] = [...currentHistory, { role: 'user', content: userInput }]; setChatHistories(prev => ({ ...prev, [selectedNoteId]: newHistory })); setUserInput(''); setIsAsking(true); try { const response = await chatWithStep( projectTitle, note.title, note.content, userInput, currentHistory.map(m => ({ role: m.role, parts: [{ text: m.content }] })) ); setChatHistories(prev => ({ ...prev, [selectedNoteId]: [...newHistory, { role: 'model', content: response }] })); } catch (error) { console.error("Chat error:", error); } finally { setIsAsking(false); } }; useEffect(() => { chatEndRef.current?.scrollIntoView({ behavior: 'smooth' }); }, [chatHistories, selectedNoteId]); const selectedNote = selectedNoteId ? notes[selectedNoteId] : null; return (

Akıl Haritası Tuvali

"{projectTitle}" projesinin etkileşimli haritası

{isRendering && (
Modelleniyor...
)}
Kök Bilgi
{/* Canvas Area */}
{({ zoomIn, zoomOut, resetTransform }) => ( <>
)}
{/* AI Chat Assistant Sidebar */} {selectedNote && (

{selectedNote.title}

Adım Asistanı

{(!chatHistories[selectedNoteId || ''] || chatHistories[selectedNoteId || ''].length === 0) && (

Bu adım hakkında
AI asistanına soru sor.

)} {chatHistories[selectedNoteId || '']?.map((msg, idx) => (
{msg.content}
))} {isAsking && (
)}
setUserInput(e.target.value)} onKeyPress={(e) => e.key === 'Enter' && handleSendMessage()} placeholder="Bir şey sor..." className="w-full bg-white/5 border border-white/10 rounded-2xl px-5 py-4 text-sm text-white focus:outline-none focus:border-indigo-500 transition-all pr-12" />

Gemini 3 Flash Pro

)}
); }; export default FlowchartView;