From c286789e25436a066faa40e319c7a3aa2be25c70 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 16 Jan 2026 03:55:55 +0000 Subject: [PATCH] feat: Add customizable AI system prompt This commit introduces a new feature that allows users to customize the AI's system prompt. A "Settings" button has been added to the header, which opens a modal containing a textarea where users can input their own system prompt. The custom prompt is saved to `localStorage` and is used in the AI's cause-and-effect analysis. A "Restore Default" button is also included to revert to the original prompt. This feature gives users more control over the AI's responses, allowing them to tailor the mind map generation for different purposes. --- src/App.jsx | 79 ++++++++++++++++++++++++------ src/SettingsModal.jsx | 110 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 173 insertions(+), 16 deletions(-) create mode 100644 src/SettingsModal.jsx diff --git a/src/App.jsx b/src/App.jsx index 5f3df15..71f91c7 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, @@ -11,12 +11,25 @@ import 'reactflow/dist/style.css'; import axios from 'axios'; import html2canvas from 'html2canvas'; import { saveAs } from 'file-saver'; +import SettingsModal from './SettingsModal'; // 🧠 Initial State & Helpers const initialNodes = []; const initialEdges = []; let nodeId = 1; +const defaultSystemPrompt = `You are an expert in cause-and-effect analysis. + +Given a news headline or event, break it down into a chain of consequences, like a domino effect. + +Each node must: +- Be short (max 15 words) +- Represent one specific consequence +- Be categorized as either: 'positive', 'neutral', or 'negative' +- Be logically linked to the prompt + +Return only 3 outputs: one of each type.`; + const getColor = (type) => { switch (type) { case 'positive': return '#00c853'; @@ -27,7 +40,7 @@ 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, systemPrompt) => { try { if (stopGenerationRef.current) return; const res = await axios.post( @@ -37,17 +50,7 @@ const createAIChildren = async (text, parentId, setNodes, setEdges, stopGenerati messages: [ { role: 'system', - content: `You are an expert in cause-and-effect analysis. - -Given a news headline or event, break it down into a chain of consequences, like a domino effect. - -Each node must: -- Be short (max 15 words) -- Represent one specific consequence -- Be categorized as either: 'positive', 'neutral', or 'negative' -- Be logically linked to the prompt - -Return only 3 outputs: one of each type.` + content: systemPrompt, }, { role: 'user', content: text } ] @@ -96,7 +99,7 @@ Return only 3 outputs: one of each type.` // 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); + await createAIChildren(n.data.label, n.id, setNodes, setEdges, stopGenerationRef, systemPrompt); } } catch (err) { @@ -113,9 +116,20 @@ export default function App() { const [edges, setEdges] = useState(initialEdges); const [prompt, setPrompt] = useState(''); const [loading, setLoading] = useState(false); + const [isSettingsOpen, setIsSettingsOpen] = useState(false); + const [systemPrompt, setSystemPrompt] = useState(defaultSystemPrompt); + const [tempPrompt, setTempPrompt] = useState(systemPrompt); const stopGenerationRef = useRef(false); const reactFlowWrapper = useRef(null); + useEffect(() => { + const storedPrompt = localStorage.getItem('systemPrompt'); + if (storedPrompt) { + setSystemPrompt(storedPrompt); + setTempPrompt(storedPrompt); + } + }, []); + const handleExport = () => { if (reactFlowWrapper.current) { html2canvas(reactFlowWrapper.current).then((canvas) => { @@ -150,7 +164,7 @@ export default function App() { setNodes([rootNode]); setEdges([]); - await createAIChildren(prompt, rootId, setNodes, setEdges, stopGenerationRef); + await createAIChildren(prompt, rootId, setNodes, setEdges, stopGenerationRef, systemPrompt); setPrompt(''); setLoading(false); @@ -164,10 +178,20 @@ export default function App() { 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, systemPrompt); setLoading(false); }; + const handleSavePrompt = () => { + setSystemPrompt(tempPrompt); + localStorage.setItem('systemPrompt', tempPrompt); + setIsSettingsOpen(false); + }; + + const handleRestoreDefault = () => { + setTempPrompt(defaultSystemPrompt); + }; + return (
{/* 🔝 Header */} @@ -246,6 +270,20 @@ export default function App() { > Export as PNG +
@@ -265,6 +303,15 @@ export default function App() { + + setIsSettingsOpen(false)} + systemPrompt={tempPrompt} + setSystemPrompt={setTempPrompt} + handleSave={handleSavePrompt} + handleRestoreDefault={handleRestoreDefault} + /> ); } diff --git a/src/SettingsModal.jsx b/src/SettingsModal.jsx new file mode 100644 index 0000000..0cfb4d7 --- /dev/null +++ b/src/SettingsModal.jsx @@ -0,0 +1,110 @@ +// ⚙️ SettingsModal.jsx +import React from 'react'; + +const SettingsModal = ({ isOpen, onClose, systemPrompt, setSystemPrompt, handleSave, handleRestoreDefault }) => { + if (!isOpen) return null; + + return ( +
+
+

Settings

+

+ Customize the AI's instructions to tailor its responses for your specific needs. +

+