Auto commit: 2026-03-13T09:39:35.644Z
This commit is contained in:
parent
134adfbc75
commit
4bea8583e5
BIN
assets/pasted-20260312-161138-4fa78450.png
Normal file
BIN
assets/pasted-20260312-161138-4fa78450.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 503 KiB |
@ -1,7 +1,7 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container-fluid mt-4">
|
<div class="container-fluid d-flex flex-column pt-4" style="height: 100vh; max-height: 100vh; overflow: hidden;">
|
||||||
<div class="row mb-3">
|
<div class="row mb-3 flex-shrink-0">
|
||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
<h1 class="mb-0">{{ project.title }}</h1>
|
<h1 class="mb-0">{{ project.title }}</h1>
|
||||||
<p class="text-muted mb-0">{{ project.industry }} | <strong>Goal:</strong> {{ project.goal }}</p>
|
<p class="text-muted mb-0">{{ project.industry }} | <strong>Goal:</strong> {{ project.goal }}</p>
|
||||||
@ -11,12 +11,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row flex-grow-1 pb-3" style="min-height: 0;">
|
||||||
<!-- Mind Map Visualization Column -->
|
<div class="col-12 h-100">
|
||||||
<div class="col-md-8">
|
<div class="card shadow-sm h-100 w-100 d-flex flex-column" style="border-radius: 12px; overflow: hidden; border: 1px solid rgba(0,0,0,0.08);">
|
||||||
<div class="card shadow-sm h-100">
|
<div class="card-header bg-white d-flex justify-content-between align-items-center flex-shrink-0" style="border-bottom: 1px solid rgba(0,0,0,0.05);">
|
||||||
<div class="card-header bg-white d-flex justify-content-between align-items-center">
|
<h5 class="mb-0 fw-bold">Interactive Mind Map</h5>
|
||||||
<h5 class="mb-0">Interactive Mind Map</h5>
|
|
||||||
<div>
|
<div>
|
||||||
<form method="POST" action="{% url 'regenerate_mindmap' project.pk %}" class="d-inline" onsubmit="return confirm('Are you sure you want to regenerate the entire map? This will delete all current nodes.');">
|
<form method="POST" action="{% url 'regenerate_mindmap' project.pk %}" class="d-inline" onsubmit="return confirm('Are you sure you want to regenerate the entire map? This will delete all current nodes.');">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
@ -25,50 +24,53 @@
|
|||||||
<button class="btn btn-sm btn-outline-primary" onclick="fitNetwork()">Reset View</button>
|
<button class="btn btn-sm btn-outline-primary" onclick="fitNetwork()">Reset View</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body p-0" style="height: 600px; position: relative;">
|
|
||||||
|
<div class="card-body p-0 position-relative flex-grow-1" style="min-height: 0; overflow: hidden; background: linear-gradient(135deg, #fdfbfb 0%, #ebedee 100%);">
|
||||||
{% if not nodes %}
|
{% if not nodes %}
|
||||||
<div class="position-absolute w-100 h-100 d-flex flex-column justify-content-center align-items-center" style="z-index: 10; background: rgba(255,255,255,0.9);">
|
<div class="position-absolute w-100 h-100 d-flex flex-column justify-content-center align-items-center" style="z-index: 10; background: rgba(255,255,255,0.9);">
|
||||||
<h4 class="text-muted mb-3">The mind map is empty</h4>
|
<h4 class="text-muted mb-3">The mind map is empty</h4>
|
||||||
<p class="text-muted mb-4">Something went wrong during generation, or the map was cleared.</p>
|
<p class="text-muted mb-4">Something went wrong during generation, or the map was cleared.</p>
|
||||||
<form method="POST" action="{% url 'regenerate_mindmap' project.pk %}">
|
<form method="POST" action="{% url 'regenerate_mindmap' project.pk %}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<button type="submit" class="btn btn-primary px-4 py-2">
|
<button type="submit" class="btn btn-primary px-4 py-2 shadow-sm rounded-pill">
|
||||||
Generate Map Now
|
Generate Map Now
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<!-- The vis-network container -->
|
<!-- The vis-network container -->
|
||||||
<div id="mynetwork" style="width: 100%; height: 100%;"></div>
|
<div id="mynetwork" style="width: 100%; height: 100%;"></div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Sidebar for Node Details & AI Chat -->
|
<!-- Floating Panels in Top Right -->
|
||||||
<div class="col-md-4 d-flex flex-column">
|
<div class="position-absolute d-flex flex-column" style="top: 15px; right: 15px; width: 350px; z-index: 1000; bottom: 15px; pointer-events: none;">
|
||||||
<!-- Node Details Panel -->
|
|
||||||
<div class="card shadow-sm mb-3" style="flex: 1;">
|
<!-- Node Details Panel -->
|
||||||
<div class="card-header bg-white">
|
<div class="card shadow mb-3" style="pointer-events: auto; flex-shrink: 0; border: 1px solid rgba(255,255,255,0.4); border-radius: 12px; background: rgba(255, 255, 255, 0.85); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);">
|
||||||
<h5 class="mb-0">Node Details</h5>
|
<div class="card-header py-2" style="background: transparent; border-bottom: 1px solid rgba(0,0,0,0.05); border-radius: 12px 12px 0 0;">
|
||||||
</div>
|
<h6 class="mb-0 fw-bold text-primary">Node Details</h6>
|
||||||
<div class="card-body" id="node-details">
|
</div>
|
||||||
<p class="text-muted">Click on a node or edge in the map to see its details here.</p>
|
<div class="card-body py-2" id="node-details" style="max-height: 200px; overflow-y: auto;">
|
||||||
</div>
|
<p class="text-muted small mb-0">Click on a node or edge in the map to see its details here.</p>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- AI Chat Panel -->
|
||||||
|
<div class="card shadow d-flex flex-column" style="pointer-events: auto; flex: 1; min-height: 0; border: 1px solid rgba(255,255,255,0.4); border-radius: 12px; background: rgba(255, 255, 255, 0.85); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);">
|
||||||
|
<div class="card-header py-2" style="background: transparent; border-bottom: 1px solid rgba(0,0,0,0.05); border-radius: 12px 12px 0 0;">
|
||||||
|
<h6 class="mb-0 fw-bold text-success">Ask AI Assistant</h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body d-flex flex-column p-2" style="min-height: 0;">
|
||||||
|
<div id="ai-response" class="p-2 rounded mb-2 flex-grow-1" style="overflow-y: auto; font-size: 0.9rem; background: rgba(255,255,255,0.6); box-shadow: inset 0 2px 4px rgba(0,0,0,0.02);">
|
||||||
|
<span class="text-muted">Hello! I'm your AI business strategist. Ask me to expand on a node, brainstorm new ideas, or analyze the map. If you ask me to add nodes to the map, I will!</span>
|
||||||
|
</div>
|
||||||
|
<div class="mt-auto flex-shrink-0">
|
||||||
|
<textarea id="ai-input" class="form-control form-control-sm mb-2" rows="2" style="resize: none; border-radius: 8px; background: rgba(255,255,255,0.9);" placeholder="e.g. Add 3 more nodes related to Marketing..."></textarea>
|
||||||
|
<button id="ai-submit" class="btn btn-primary btn-sm w-100 rounded-pill shadow-sm">Send to AI</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- AI Chat Panel -->
|
|
||||||
<div class="card shadow-sm" style="flex: 1;">
|
|
||||||
<div class="card-header bg-white">
|
|
||||||
<h5 class="mb-0">Ask AI Assistant</h5>
|
|
||||||
</div>
|
|
||||||
<div class="card-body d-flex flex-column">
|
|
||||||
<div id="ai-response" class="bg-light p-3 rounded mb-3 flex-grow-1" style="min-height: 100px; max-height: 200px; overflow-y: auto;">
|
|
||||||
<span class="text-muted">Hello! I'm your AI business strategist. Ask me to expand on a node, brainstorm new ideas, or analyze the map. If you ask me to add nodes to the map, I will!</span>
|
|
||||||
</div>
|
|
||||||
<div class="mt-auto">
|
|
||||||
<textarea id="ai-input" class="form-control mb-2" rows="2" placeholder="e.g. Add 3 more nodes related to Marketing..."></textarea>
|
|
||||||
<button id="ai-submit" class="btn btn-primary w-100">Send to AI</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -83,16 +85,33 @@
|
|||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
.vis-tooltip {
|
.vis-tooltip {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
background-color: white;
|
background-color: rgba(255, 255, 255, 0.95);
|
||||||
padding: 10px;
|
padding: 12px;
|
||||||
border: 1px solid #ccc;
|
border: none;
|
||||||
border-radius: 5px;
|
border-radius: 8px;
|
||||||
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
box-shadow: 0 10px 25px rgba(0,0,0,0.1);
|
||||||
font-family: Arial, sans-serif;
|
font-family: inherit;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
max-width: 300px;
|
max-width: 320px;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
-webkit-backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Optional: custom scrollbar for details and chat */
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(0,0,0,0.15);
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: rgba(0,0,0,0.25);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
@ -203,9 +222,9 @@
|
|||||||
const nodeId = params.nodes[0];
|
const nodeId = params.nodes[0];
|
||||||
const node = data.nodes.get(nodeId);
|
const node = data.nodes.get(nodeId);
|
||||||
detailsPanel.innerHTML = `
|
detailsPanel.innerHTML = `
|
||||||
<h4 class="mb-2">${node.label}</h4>
|
<h6 class="mb-2 fw-bold text-dark">${node.label}</h6>
|
||||||
<span class="badge bg-secondary mb-3">${node.category}</span>
|
<span class="badge bg-secondary mb-2 bg-opacity-75">${node.category}</span>
|
||||||
<p><strong>Summary:</strong><br>${node.summary}</p>
|
<p class="small mb-0 text-dark"><strong>Summary:</strong><br>${node.summary}</p>
|
||||||
`;
|
`;
|
||||||
} else if (params.edges.length > 0) {
|
} else if (params.edges.length > 0) {
|
||||||
// Edge clicked
|
// Edge clicked
|
||||||
@ -214,16 +233,17 @@
|
|||||||
const fromNode = data.nodes.get(edge.from);
|
const fromNode = data.nodes.get(edge.from);
|
||||||
const toNode = data.nodes.get(edge.to);
|
const toNode = data.nodes.get(edge.to);
|
||||||
detailsPanel.innerHTML = `
|
detailsPanel.innerHTML = `
|
||||||
<h5 class="mb-3">Connection Details</h5>
|
<h6 class="mb-2 fw-bold text-dark">Connection Details</h6>
|
||||||
<p><strong>From:</strong> ${fromNode.label}</p>
|
<p class="small mb-1 text-dark"><strong>From:</strong> ${fromNode.label}</p>
|
||||||
<p><strong>To:</strong> ${toNode.label}</p>
|
<p class="small mb-2 text-dark"><strong>To:</strong> ${toNode.label}</p>
|
||||||
<hr>
|
<div class="small text-dark border-top pt-2">
|
||||||
<p><strong>How:</strong><br>${edge.how || 'N/A'}</p>
|
<p class="mb-1"><strong>How:</strong> ${edge.how || 'N/A'}</p>
|
||||||
<p><strong>Why:</strong><br>${edge.why || 'N/A'}</p>
|
<p class="mb-0"><strong>Why:</strong> ${edge.why || 'N/A'}</p>
|
||||||
|
</div>
|
||||||
`;
|
`;
|
||||||
} else {
|
} else {
|
||||||
// Clicked empty space
|
// Clicked empty space
|
||||||
detailsPanel.innerHTML = '<p class="text-muted">Click on a node or edge in the map to see its details here.</p>';
|
detailsPanel.innerHTML = '<p class="text-muted small mb-0">Click on a node or edge in the map to see its details here.</p>';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -236,12 +256,12 @@
|
|||||||
const responseDiv = document.getElementById('ai-response');
|
const responseDiv = document.getElementById('ai-response');
|
||||||
|
|
||||||
// Append user message
|
// Append user message
|
||||||
responseDiv.innerHTML += `<div class="mb-2 text-end"><strong>You:</strong> ${message}</div>`;
|
responseDiv.innerHTML += `<div class="mb-2 text-end"><span class="badge bg-primary rounded-pill px-3 py-2 text-wrap text-start d-inline-block shadow-sm" style="max-width: 85%; font-weight: normal; font-size: 0.9rem;">${message}</span></div>`;
|
||||||
inputField.value = '';
|
inputField.value = '';
|
||||||
|
|
||||||
// Show loading state
|
// Show loading state
|
||||||
const loadingId = 'loading-' + Date.now();
|
const loadingId = 'loading-' + Date.now();
|
||||||
responseDiv.innerHTML += `<div id="${loadingId}" class="mb-2 text-primary"><em>AI is thinking...</em></div>`;
|
responseDiv.innerHTML += `<div id="${loadingId}" class="mb-2 text-start"><span class="badge bg-white text-primary border border-primary border-opacity-25 rounded-pill px-3 py-2 shadow-sm d-inline-block" style="font-weight: normal; font-size: 0.9rem;"><em><span class="spinner-border spinner-border-sm me-1" role="status" aria-hidden="true"></span> Thinking...</em></span></div>`;
|
||||||
responseDiv.scrollTop = responseDiv.scrollHeight;
|
responseDiv.scrollTop = responseDiv.scrollHeight;
|
||||||
|
|
||||||
fetch("{% url 'ai_chat' project.pk %}", {
|
fetch("{% url 'ai_chat' project.pk %}", {
|
||||||
@ -258,7 +278,7 @@
|
|||||||
|
|
||||||
// Handle chat response
|
// Handle chat response
|
||||||
if (apiData.response) {
|
if (apiData.response) {
|
||||||
responseDiv.innerHTML += `<div class="mb-2"><strong>AI:</strong> ${apiData.response}</div>`;
|
responseDiv.innerHTML += `<div class="mb-2 text-start"><span class="badge bg-white text-dark rounded-4 px-3 py-2 text-wrap text-start d-inline-block shadow-sm" style="max-width: 90%; font-weight: normal; font-size: 0.9rem; border: 1px solid rgba(0,0,0,0.05);">${apiData.response}</span></div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for new nodes and add them to the map
|
// Check for new nodes and add them to the map
|
||||||
@ -301,16 +321,24 @@
|
|||||||
// Stabilize and adjust view
|
// Stabilize and adjust view
|
||||||
network.stabilize();
|
network.stabilize();
|
||||||
setTimeout(fitNetwork, 500); // Fit network smoothly after a slight delay
|
setTimeout(fitNetwork, 500); // Fit network smoothly after a slight delay
|
||||||
responseDiv.innerHTML += `<div class="mb-2 text-success small"><em><i class="bi bi-check-circle"></i> Successfully added new items to the map!</em></div>`;
|
responseDiv.innerHTML += `<div class="mb-2 text-center"><small class="text-success"><i class="bi bi-check-circle-fill"></i> New items added to map!</small></div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
responseDiv.scrollTop = responseDiv.scrollHeight;
|
responseDiv.scrollTop = responseDiv.scrollHeight;
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
document.getElementById(loadingId).remove();
|
document.getElementById(loadingId).remove();
|
||||||
responseDiv.innerHTML += `<div class="mb-2 text-danger"><strong>Error:</strong> Failed to get response from AI.</div>`;
|
responseDiv.innerHTML += `<div class="mb-2 text-start"><span class="badge bg-danger text-white rounded-pill px-3 py-2 shadow-sm d-inline-block" style="font-weight: normal;">Error: Failed to get response.</span></div>`;
|
||||||
console.error(err);
|
console.error(err);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Allow submitting AI input with Enter key
|
||||||
|
document.getElementById('ai-input').addEventListener('keypress', function (e) {
|
||||||
|
if (e.key === 'Enter' && !e.shiftKey) {
|
||||||
|
e.preventDefault();
|
||||||
|
document.getElementById('ai-submit').click();
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
Loading…
x
Reference in New Issue
Block a user