diff --git a/package-lock.json b/package-lock.json
index b72de8c..df13162 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -72,7 +72,6 @@
"integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.27.1",
@@ -1738,7 +1737,6 @@
"integrity": "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==",
"devOptional": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"csstype": "^3.0.2"
}
@@ -1780,7 +1778,6 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
- "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -1902,7 +1899,6 @@
}
],
"license": "MIT",
- "peer": true,
"dependencies": {
"caniuse-lite": "^1.0.30001726",
"electron-to-chromium": "^1.5.173",
@@ -2117,7 +2113,6 @@
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
"license": "ISC",
- "peer": true,
"engines": {
"node": ">=12"
}
@@ -2337,7 +2332,6 @@
"integrity": "sha512-QldCVh/ztyKJJZLr4jXNUByx3gR+TDYZCRXEktiZoUR3PGy4qCmSbkxcIle8GEwGpb5JBZazlaJ/CxLidXdEbQ==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -3180,7 +3174,6 @@
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
"dev": true,
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=12"
},
@@ -3248,7 +3241,6 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
"integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -3258,7 +3250,6 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
"integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
"license": "MIT",
- "peer": true,
"dependencies": {
"scheduler": "^0.26.0"
},
@@ -3523,7 +3514,6 @@
"integrity": "sha512-SkaSguuS7nnmV7mfJ8l81JGBFV7Gvzp8IzgE8A8t23+AxuNX61Q5H1Tpz5efduSN7NHC8nQXD3sKQKZAu5mNEA==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.4.6",
diff --git a/src/App.css b/src/App.css
index b9d355d..c36fa0a 100644
--- a/src/App.css
+++ b/src/App.css
@@ -40,3 +40,35 @@
.read-the-docs {
color: #888;
}
+
+/* 📍 Color Legend Styles */
+.color-legend {
+ position: absolute;
+ top: 20px;
+ left: 20px;
+ background: rgba(34, 34, 34, 0.9);
+ padding: 12px;
+ border-radius: 10px;
+ border: 1px solid #444;
+ color: white;
+ z-index: 1000;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ pointer-events: none;
+}
+
+.legend-item {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-size: 0.9rem;
+ font-weight: 500;
+}
+
+.dot {
+ width: 12px;
+ height: 12px;
+ border-radius: 50%;
+ display: inline-block;
+}
diff --git a/src/App.jsx b/src/App.jsx
index 5f3df15..f3568e4 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -1,5 +1,5 @@
// 📦 Imports
-import React, { useState, useRef } from 'react';
+import React, { useState, useRef, useEffect } from 'react';
import ReactFlow, {
Background,
Controls,
@@ -8,14 +8,21 @@ import ReactFlow, {
applyNodeChanges,
} from 'reactflow';
import 'reactflow/dist/style.css';
+import './App.css';
import axios from 'axios';
import html2canvas from 'html2canvas';
import { saveAs } from 'file-saver';
// 🧠 Initial State & Helpers
-const initialNodes = [];
-const initialEdges = [];
-let nodeId = 1;
+const initialNodes = JSON.parse(localStorage.getItem('nodes')) || [];
+const initialEdges = JSON.parse(localStorage.getItem('edges')) || [];
+
+const getMaxId = (nodes) => {
+ const ids = nodes.map((n) => parseInt(n.id)).filter((id) => !isNaN(id));
+ return ids.length > 0 ? Math.max(...ids) : 0;
+};
+
+let nodeId = getMaxId(initialNodes) + 1;
const getColor = (type) => {
switch (type) {
@@ -27,9 +34,9 @@ const getColor = (type) => {
};
// 🤖 AI Logic with Domino Effect Thinking (Updated: Using Puter AI API)
-const createAIChildren = async (text, parentId, setNodes, setEdges, stopGenerationRef) => {
+const createAIChildren = async (text, parentId, setNodes, setEdges, stopGenerationRef, parentPos, depth = 1) => {
try {
- if (stopGenerationRef.current) return;
+ if (stopGenerationRef.current || depth < 0) return;
const res = await axios.post(
'https://api.puter.com/v1/chat/completions',
{
@@ -69,8 +76,8 @@ Return only 3 outputs: one of each type.`
id: newId,
data: { label: line.replace(/^\d+\.\s*/, '') },
position: {
- x: 300 + Math.random() * 300,
- y: 200 + Math.random() * 300
+ x: parentPos.x + 350,
+ y: parentPos.y + (idx - 1) * 150
},
style: {
background: getColor(childTypes[idx]),
@@ -94,9 +101,11 @@ Return only 3 outputs: one of each type.`
setEdges((eds) => [...eds, ...newEdges]);
// Recursively expand each child one level deep
- for (const n of newNodes) {
- if (stopGenerationRef.current) break;
- await createAIChildren(n.data.label, n.id, setNodes, setEdges, stopGenerationRef);
+ if (depth > 0) {
+ for (const n of newNodes) {
+ if (stopGenerationRef.current) break;
+ await createAIChildren(n.data.label, n.id, setNodes, setEdges, stopGenerationRef, n.position, depth - 1);
+ }
}
} catch (err) {
@@ -112,6 +121,12 @@ export default function App() {
const [nodes, setNodes] = useState(initialNodes);
const [edges, setEdges] = useState(initialEdges);
const [prompt, setPrompt] = useState('');
+
+ // 💾 Persistence
+ useEffect(() => {
+ localStorage.setItem('nodes', JSON.stringify(nodes));
+ localStorage.setItem('edges', JSON.stringify(edges));
+ }, [nodes, edges]);
const [loading, setLoading] = useState(false);
const stopGenerationRef = useRef(false);
const reactFlowWrapper = useRef(null);
@@ -150,7 +165,7 @@ export default function App() {
setNodes([rootNode]);
setEdges([]);
- await createAIChildren(prompt, rootId, setNodes, setEdges, stopGenerationRef);
+ await createAIChildren(prompt, rootId, setNodes, setEdges, stopGenerationRef, rootNode.position);
setPrompt('');
setLoading(false);
@@ -161,10 +176,21 @@ export default function App() {
setLoading(false);
};
+ const handleClearAll = () => {
+ if (window.confirm('Are you sure you want to clear the entire mind map?')) {
+ setNodes([]);
+ setEdges([]);
+ localStorage.removeItem('nodes');
+ localStorage.removeItem('edges');
+ // Reset nodeId for fresh start
+ nodeId = 1;
+ }
+ };
+
const onNodeClick = async (_event, node) => {
stopGenerationRef.current = false;
setLoading(true);
- await createAIChildren(node.data.label, node.id, setNodes, setEdges, stopGenerationRef);
+ await createAIChildren(node.data.label, node.id, setNodes, setEdges, stopGenerationRef, node.position);
setLoading(false);
};
@@ -246,6 +272,22 @@ export default function App() {
>
Export as PNG
+
@@ -263,6 +305,13 @@ export default function App() {