|
|
import React, { useState } from 'react';
|
|
|
import { MindMapData } from './ChatModal.d';
|
|
|
|
|
|
interface ChatModalProps {
|
|
|
open: boolean;
|
|
|
onClose: () => void;
|
|
|
onImportJSON: (event: any) => void;
|
|
|
currentMindMapData: MindMapData;
|
|
|
}
|
|
|
|
|
|
const createSystemPrompt = (currentData: MindMapData) => {
|
|
|
return `You are the best mindmap builder to build and optimize mindmap content to return the best organized json response for reactflow library.
|
|
|
|
|
|
Current Mindmap State:
|
|
|
- Name: ${currentData.mindMapName}
|
|
|
- Number of Nodes: ${currentData.nodes.length}
|
|
|
- Number of Connections: ${currentData.edges.length}
|
|
|
- Current Configuration: ${JSON.stringify(currentData.config)}
|
|
|
|
|
|
IMPORTANT: Your response must be a valid JSON object that follows this exact structure:
|
|
|
{
|
|
|
"id": "string (optional)",
|
|
|
"name": "string",
|
|
|
"updatedAt": "string (ISO date)",
|
|
|
"nodes": [
|
|
|
{
|
|
|
"id": "string",
|
|
|
"type": "editableColor",
|
|
|
"data": {
|
|
|
"label": "string",
|
|
|
"bgColor": "string (hex color)",
|
|
|
"textColor": "string (hex color)"
|
|
|
},
|
|
|
"position": {
|
|
|
"x": number,
|
|
|
"y": number
|
|
|
},
|
|
|
"width": number,
|
|
|
"height": number
|
|
|
}
|
|
|
],
|
|
|
"edges": [
|
|
|
{
|
|
|
"id": "string (reactflow__edge-{source}-{target})",
|
|
|
"source": "string (node id)",
|
|
|
"target": "string (node id)"
|
|
|
}
|
|
|
],
|
|
|
"config": {
|
|
|
"nodeBgColor": "string (hex color)",
|
|
|
"nodeTextColor": "string (hex color)"
|
|
|
}
|
|
|
}
|
|
|
|
|
|
Please analyze the current mindmap structure and provide optimized suggestions or modifications based on the user's input. Return ONLY the JSON object without any additional text or markdown formatting.`;
|
|
|
};
|
|
|
|
|
|
const ChatModal: React.FC<ChatModalProps> = ({ open, onClose, onImportJSON, currentMindMapData }) => {
|
|
|
const [userInput, setUserInput] = useState('');
|
|
|
const [response, setResponse] = useState('');
|
|
|
const [loading, setLoading] = useState(false);
|
|
|
const [jsonReady, setJsonReady] = useState(false);
|
|
|
|
|
|
if (!open) return null;
|
|
|
|
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
|
e.preventDefault();
|
|
|
setLoading(true);
|
|
|
setResponse('');
|
|
|
setJsonReady(false);
|
|
|
try {
|
|
|
|
|
|
const apiUrl = process.env.REACT_APP_CHAT_API_URL || 'http://localhost:3001/api/chat';
|
|
|
console.log('Sending request to:', apiUrl);
|
|
|
|
|
|
const systemPrompt = createSystemPrompt(currentMindMapData);
|
|
|
|
|
|
const res = await fetch(apiUrl, {
|
|
|
method: 'POST',
|
|
|
headers: {
|
|
|
'Content-Type': 'application/json',
|
|
|
'Accept': 'application/json'
|
|
|
},
|
|
|
body: JSON.stringify({
|
|
|
model: 'mistral-large-latest',
|
|
|
stream: false,
|
|
|
messages: [
|
|
|
{ role: 'system', content: systemPrompt },
|
|
|
{ role: 'user', content: userInput },
|
|
|
],
|
|
|
}),
|
|
|
});
|
|
|
|
|
|
if (!res.ok) {
|
|
|
throw new Error(`HTTP error! status: ${res.status}`);
|
|
|
}
|
|
|
|
|
|
const data = await res.json();
|
|
|
console.log('API Response:', data);
|
|
|
|
|
|
let content = data.choices?.[0]?.message?.content || data.content || JSON.stringify(data);
|
|
|
setResponse(content);
|
|
|
|
|
|
|
|
|
let jsonText = content;
|
|
|
const match = content.match(/```json\s*([\s\S]*?)```/i) || content.match(/```([\s\S]*?)```/i);
|
|
|
if (match) {
|
|
|
jsonText = match[1].trim();
|
|
|
}
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
console.log('Raw content:', content);
|
|
|
console.log('Extracted JSON text:', jsonText);
|
|
|
|
|
|
|
|
|
jsonText = jsonText.replace(/[\u0000-\u001F\u007F-\u009F]/g, '');
|
|
|
jsonText = jsonText.replace(/\n/g, ' ').replace(/\r/g, '');
|
|
|
jsonText = jsonText.replace(/\s+/g, ' ').trim();
|
|
|
|
|
|
const parsedJson = JSON.parse(jsonText);
|
|
|
console.log('Parsed JSON:', parsedJson);
|
|
|
setJsonReady(true);
|
|
|
} catch (parseError) {
|
|
|
console.error('JSON Parse Error:', parseError);
|
|
|
console.error('Failed JSON text:', jsonText);
|
|
|
setJsonReady(false);
|
|
|
setResponse(prev => prev + '\n\n[Error: Invalid JSON format. Please try rephrasing your prompt or check the AI output.]');
|
|
|
}
|
|
|
} catch (err) {
|
|
|
console.error('API Error:', err);
|
|
|
setResponse(`Error: ${err instanceof Error ? err.message : 'Failed to connect to chat service'}`);
|
|
|
setJsonReady(false);
|
|
|
} finally {
|
|
|
setLoading(false);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
const handleImport = () => {
|
|
|
try {
|
|
|
let jsonText = response;
|
|
|
const match = response.match(/```json\s*([\s\S]*?)```/i) || response.match(/```([\s\S]*?)```/i);
|
|
|
if (match) {
|
|
|
jsonText = match[1].trim();
|
|
|
}
|
|
|
const file = new File([jsonText], 'mindmap.json', { type: 'application/json' });
|
|
|
const event = { target: { files: [file] } };
|
|
|
onImportJSON(event);
|
|
|
onClose();
|
|
|
} catch {}
|
|
|
};
|
|
|
|
|
|
return (
|
|
|
<div style={{ position: 'fixed', top: 0, left: 0, width: '100vw', height: '100vh', background: 'rgba(0,0,0,0.25)', zIndex: 20000, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
|
|
<div style={{ background: '#fff', borderRadius: 12, boxShadow: '0 4px 24px rgba(0,0,0,0.18)', padding: 32, minWidth: 340, maxWidth: 480, width: '100%', position: 'relative' }}>
|
|
|
<button onClick={onClose} style={{ position: 'absolute', top: 12, right: 16, background: 'none', border: 'none', fontSize: 22, cursor: 'pointer' }} aria-label="Close chat">×</button>
|
|
|
<h3 style={{ marginTop: 0 }}>Mind Map Chat Assistant</h3>
|
|
|
<form onSubmit={handleSubmit} style={{ marginBottom: 16 }}>
|
|
|
<textarea
|
|
|
value={userInput}
|
|
|
onChange={e => setUserInput(e.target.value)}
|
|
|
rows={4}
|
|
|
style={{ width: '100%', borderRadius: 6, border: '1.5px solid #e0e0e0', padding: 8, fontSize: 15, marginBottom: 8 }}
|
|
|
placeholder="Describe your mind map or ask for optimization..."
|
|
|
required
|
|
|
/>
|
|
|
<button type="submit" style={{ background: '#007bff', color: '#fff', border: 'none', borderRadius: 6, padding: '8px 18px', fontWeight: 600, fontSize: 15, cursor: 'pointer' }} disabled={loading}>
|
|
|
{loading ? 'Thinking...' : 'Send'}
|
|
|
</button>
|
|
|
</form>
|
|
|
<div style={{ minHeight: 60, background: '#f7fafc', borderRadius: 6, padding: 10, fontSize: 14, color: '#222', marginBottom: 8, whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>
|
|
|
{loading ? 'Loading...' : response}
|
|
|
</div>
|
|
|
{jsonReady && (
|
|
|
<button onClick={handleImport} style={{ background: '#28a745', color: '#fff', border: 'none', borderRadius: 6, padding: '8px 18px', fontWeight: 600, fontSize: 15, cursor: 'pointer', marginTop: 4 }}>
|
|
|
Import to Mind Map
|
|
|
</button>
|
|
|
)}
|
|
|
</div>
|
|
|
</div>
|
|
|
);
|
|
|
};
|
|
|
|
|
|
export default ChatModal; |