Auto commit: 2026-03-12T14:15:33.094Z
This commit is contained in:
parent
ffe8cee686
commit
33a9c38573
BIN
ai/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
ai/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ai/__pycache__/local_ai_api.cpython-311.pyc
Normal file
BIN
ai/__pycache__/local_ai_api.cpython-311.pyc
Normal file
Binary file not shown.
BIN
core/__pycache__/ai_helpers.cpython-311.pyc
Normal file
BIN
core/__pycache__/ai_helpers.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
60
core/ai_helpers.py
Normal file
60
core/ai_helpers.py
Normal file
@ -0,0 +1,60 @@
|
||||
from ai.local_ai_api import LocalAIApi
|
||||
from .models import MindMapNode, MindMapConnection
|
||||
import json
|
||||
|
||||
def generate_initial_mindmap(project):
|
||||
prompt = f"""
|
||||
You are an expert business consultant. Create an initial mind map for a new project.
|
||||
Project Title: {project.title}
|
||||
Industry: {project.industry}
|
||||
Goal: {project.goal}
|
||||
|
||||
Respond ONLY with a valid JSON object in the following format:
|
||||
{{
|
||||
"nodes": [
|
||||
{{"id": "node_1", "title": "Main Goal", "summary": "Short description", "category": "Strategy"}},
|
||||
{{"id": "node_2", "title": "Feature X", "summary": "Short description", "category": "Product"}}
|
||||
],
|
||||
"connections": [
|
||||
{{"source_id": "node_1", "target_id": "node_2", "how": "Defines what to build", "why": "Feature X is critical to achieve the Main Goal"}}
|
||||
]
|
||||
}}
|
||||
Create 6-10 interconnected nodes exploring key business areas like Target Audience, Core Features, Marketing Strategy, Revenue Streams, etc. Ensure the IDs in connections match the nodes.
|
||||
"""
|
||||
response = LocalAIApi.create_response({
|
||||
"input": [
|
||||
{"role": "system", "content": "You are a helpful business strategy AI. You must respond in valid JSON matching the exact requested format."},
|
||||
{"role": "user", "content": prompt}
|
||||
],
|
||||
"response_format": {"type": "json_object"}
|
||||
})
|
||||
|
||||
if response.get("success"):
|
||||
data = LocalAIApi.decode_json_from_response(response)
|
||||
if not data:
|
||||
return False
|
||||
|
||||
# Parse and save to DB
|
||||
node_map = {}
|
||||
for n in data.get("nodes", []):
|
||||
node = MindMapNode.objects.create(
|
||||
project=project,
|
||||
title=n.get("title", "Untitled"),
|
||||
summary=n.get("summary", ""),
|
||||
category=n.get("category", "General")
|
||||
)
|
||||
node_map[n.get("id")] = node
|
||||
|
||||
for c in data.get("connections", []):
|
||||
source_id = c.get("source_id")
|
||||
target_id = c.get("target_id")
|
||||
if source_id in node_map and target_id in node_map:
|
||||
MindMapConnection.objects.create(
|
||||
project=project,
|
||||
source=node_map[source_id],
|
||||
target=node_map[target_id],
|
||||
how=c.get("how", ""),
|
||||
why=c.get("why", "")
|
||||
)
|
||||
return True
|
||||
return False
|
||||
143
core/ai_views.py
143
core/ai_views.py
@ -1,18 +1,145 @@
|
||||
from django.http import JsonResponse
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from .models import Project, MindMapNode, MindMapConnection
|
||||
from ai.local_ai_api import LocalAIApi
|
||||
from django.shortcuts import get_object_or_404
|
||||
import json
|
||||
|
||||
@csrf_exempt
|
||||
@login_required
|
||||
def ai_chat(request, pk):
|
||||
if request.method == 'POST':
|
||||
data = json.loads(request.body)
|
||||
user_message = data.get('message')
|
||||
project = get_object_or_404(Project, pk=pk, user=request.user)
|
||||
|
||||
# In a real app, this would call your AI service (e.g., GPT, Gemini)
|
||||
# Here we mock the AI response
|
||||
ai_response = f"AI thinking about: {user_message}. Based on my analysis, you should consider..."
|
||||
|
||||
return JsonResponse({'response': ai_response})
|
||||
return JsonResponse({'error': 'Invalid request'}, status=400)
|
||||
try:
|
||||
data = json.loads(request.body)
|
||||
user_message = data.get('message')
|
||||
|
||||
# Context builder for the project
|
||||
nodes = list(project.nodes.values('id', 'title', 'category'))
|
||||
nodes_context = json.dumps(nodes)
|
||||
|
||||
system_prompt = f"""You are an AI business strategist helping a user build a mind map for their project.
|
||||
Project Title: {project.title}
|
||||
Industry: {project.industry}
|
||||
Goal: {project.goal}
|
||||
|
||||
Current Nodes (with IDs):
|
||||
{nodes_context}
|
||||
|
||||
Respond ONLY with a valid JSON object in the following format:
|
||||
{{
|
||||
"message": "Your text response to the user's prompt goes here.",
|
||||
"new_nodes": [
|
||||
{{"id": "temp_1", "title": "New Idea", "summary": "Short description", "category": "Strategy"}}
|
||||
],
|
||||
"new_connections": [
|
||||
{{"source_id": 1, "target_id": "temp_1", "how": "Relates to existing node", "why": "Important reason"}}
|
||||
]
|
||||
}}
|
||||
Instructions:
|
||||
- If the user just asks a question, put your answer in "message" and leave "new_nodes" and "new_connections" empty.
|
||||
- If the user asks to add nodes, brainstorm nodes, or expand the map, generate them and place them in "new_nodes". Use temporary string IDs for new nodes (e.g., "temp_1").
|
||||
- For connections, "source_id" and "target_id" can be an existing integer ID from the Current Nodes list OR a temporary string ID from "new_nodes".
|
||||
- Keep your answers concise and practical.
|
||||
"""
|
||||
|
||||
response = LocalAIApi.create_response({
|
||||
"input": [
|
||||
{"role": "system", "content": system_prompt},
|
||||
{"role": "user", "content": user_message}
|
||||
],
|
||||
"response_format": {"type": "json_object"}
|
||||
})
|
||||
|
||||
if response.get("success"):
|
||||
ai_data = LocalAIApi.decode_json_from_response(response)
|
||||
|
||||
if not ai_data:
|
||||
# fallback if it's not valid JSON
|
||||
ai_text = LocalAIApi.extract_text(response)
|
||||
return JsonResponse({'response': ai_text})
|
||||
|
||||
# Extract parts
|
||||
ai_message = ai_data.get("message", "I have updated the mind map.")
|
||||
new_nodes_data = ai_data.get("new_nodes", [])
|
||||
new_connections_data = ai_data.get("new_connections", [])
|
||||
|
||||
added_nodes = []
|
||||
added_connections = []
|
||||
|
||||
if new_nodes_data or new_connections_data:
|
||||
# Save nodes
|
||||
temp_to_real_id = {}
|
||||
|
||||
for n in new_nodes_data:
|
||||
node = MindMapNode.objects.create(
|
||||
project=project,
|
||||
title=n.get("title", "Untitled"),
|
||||
summary=n.get("summary", ""),
|
||||
category=n.get("category", "General")
|
||||
)
|
||||
temp_to_real_id[n.get("id")] = node
|
||||
added_nodes.append({
|
||||
"id": node.pk,
|
||||
"title": node.title,
|
||||
"summary": node.summary,
|
||||
"category": node.category
|
||||
})
|
||||
|
||||
# Create dictionary to fetch existing nodes quickly by int ID
|
||||
existing_nodes_map = {n.id: n for n in project.nodes.all()}
|
||||
|
||||
# Save connections
|
||||
for c in new_connections_data:
|
||||
source_ref = c.get("source_id")
|
||||
target_ref = c.get("target_id")
|
||||
|
||||
source_node = None
|
||||
target_node = None
|
||||
|
||||
# Resolve source
|
||||
if isinstance(source_ref, int) and source_ref in existing_nodes_map:
|
||||
source_node = existing_nodes_map[source_ref]
|
||||
elif isinstance(source_ref, str) and str(source_ref).isdigit() and int(source_ref) in existing_nodes_map:
|
||||
source_node = existing_nodes_map[int(source_ref)]
|
||||
elif source_ref in temp_to_real_id:
|
||||
source_node = temp_to_real_id[source_ref]
|
||||
|
||||
# Resolve target
|
||||
if isinstance(target_ref, int) and target_ref in existing_nodes_map:
|
||||
target_node = existing_nodes_map[target_ref]
|
||||
elif isinstance(target_ref, str) and str(target_ref).isdigit() and int(target_ref) in existing_nodes_map:
|
||||
target_node = existing_nodes_map[int(target_ref)]
|
||||
elif target_ref in temp_to_real_id:
|
||||
target_node = temp_to_real_id[target_ref]
|
||||
|
||||
if source_node and target_node:
|
||||
conn = MindMapConnection.objects.create(
|
||||
project=project,
|
||||
source=source_node,
|
||||
target=target_node,
|
||||
how=c.get("how", ""),
|
||||
why=c.get("why", "")
|
||||
)
|
||||
added_connections.append({
|
||||
"id": conn.pk,
|
||||
"source_id": source_node.pk,
|
||||
"target_id": target_node.pk,
|
||||
"how": conn.how,
|
||||
"why": conn.why
|
||||
})
|
||||
|
||||
return JsonResponse({
|
||||
'response': ai_message,
|
||||
'added_nodes': added_nodes,
|
||||
'added_connections': added_connections
|
||||
})
|
||||
else:
|
||||
return JsonResponse({'response': "Sorry, I had trouble processing that request. Please try again."})
|
||||
|
||||
except json.JSONDecodeError:
|
||||
return JsonResponse({'error': 'Invalid request body'}, status=400)
|
||||
|
||||
return JsonResponse({'error': 'Invalid request method'}, status=405)
|
||||
@ -1,22 +1,55 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container mt-4">
|
||||
<h1>Create New Project</h1>
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<div class="mb-3">
|
||||
<label for="title" class="form-label">Title</label>
|
||||
<input type="text" class="form-control" id="title" name="title" required>
|
||||
<div class="container mt-5">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<div class="card shadow">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h3 class="card-title mb-0">Start a New Project</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="post" id="create-project-form">
|
||||
{% csrf_token %}
|
||||
<div class="mb-3">
|
||||
<label for="title" class="form-label">Project Title</label>
|
||||
<input type="text" class="form-control" id="title" name="title" required placeholder="e.g. NextGen CRM">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="industry" class="form-label">Industry</label>
|
||||
<input type="text" class="form-control" id="industry" name="industry" required placeholder="e.g. SaaS">
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label for="goal" class="form-label">Primary Goal</label>
|
||||
<textarea class="form-control" id="goal" name="goal" rows="3" required placeholder="Describe what you want to achieve..."></textarea>
|
||||
</div>
|
||||
<div class="d-grid">
|
||||
<button type="submit" class="btn btn-primary btn-lg" id="submit-btn">
|
||||
Generate Mind Map
|
||||
<span class="spinner-border spinner-border-sm d-none ms-2" id="loading-spinner" role="status" aria-hidden="true"></span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="card-footer text-muted text-center" id="loading-text" style="display: none;">
|
||||
<small>Please wait while our AI analyzes your goal and builds the initial map (this may take up to 20 seconds)...</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="industry" class="form-label">Industry</label>
|
||||
<input type="text" class="form-control" id="industry" name="industry" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="goal" class="form-label">Goal</label>
|
||||
<textarea class="form-control" id="goal" name="goal" rows="3" required></textarea>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Create</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
<script>
|
||||
document.getElementById('create-project-form').addEventListener('submit', function() {
|
||||
// Disable the button to prevent multiple submissions
|
||||
const btn = document.getElementById('submit-btn');
|
||||
setTimeout(() => { btn.disabled = true; }, 0);
|
||||
|
||||
// Show the spinner and loading text
|
||||
document.getElementById('loading-spinner').classList.remove('d-none');
|
||||
document.getElementById('loading-text').style.display = 'block';
|
||||
|
||||
// Let the form submit normally
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@ -1,49 +1,297 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block content %}
|
||||
<div class="container mt-4">
|
||||
<h1>{{ project.title }}</h1>
|
||||
<p>Industry: {{ project.industry }}</p>
|
||||
<p>Goal: {{ project.goal }}</p>
|
||||
|
||||
<h3>Mind Map Nodes</h3>
|
||||
<ul>
|
||||
{% for node in nodes %}
|
||||
<li>{{ node.title }} - {{ node.category }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
<h3>Connections</h3>
|
||||
<ul>
|
||||
{% for conn in connections %}
|
||||
<li>{{ conn.source.title }} -> {{ conn.target.title }} (Why: {{ conn.why }})</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
<h3>Chat with AI</h3>
|
||||
<div id="chat-container" class="mb-3">
|
||||
<textarea id="ai-input" class="form-control" rows="2" placeholder="Ask AI..."></textarea>
|
||||
<button id="ai-submit" class="btn btn-primary mt-2">Send</button>
|
||||
<div class="container-fluid mt-4">
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-8">
|
||||
<h1 class="mb-0">{{ project.title }}</h1>
|
||||
<p class="text-muted mb-0">{{ project.industry }} | <strong>Goal:</strong> {{ project.goal }}</p>
|
||||
</div>
|
||||
<div class="col-md-4 text-end">
|
||||
<a href="{% url 'project_list' %}" class="btn btn-outline-secondary">Back to Projects</a>
|
||||
</div>
|
||||
</div>
|
||||
<div id="ai-response" class="card mt-2">
|
||||
<div class="card-body">Waiting for input...</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- Mind Map Visualization Column -->
|
||||
<div class="col-md-8">
|
||||
<div class="card shadow-sm h-100">
|
||||
<div class="card-header bg-white d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0">Interactive Mind Map</h5>
|
||||
<button class="btn btn-sm btn-outline-primary" onclick="fitNetwork()">Reset View</button>
|
||||
</div>
|
||||
<div class="card-body p-0" style="height: 600px; position: relative;">
|
||||
<!-- The vis-network container -->
|
||||
<div id="mynetwork" style="width: 100%; height: 100%;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sidebar for Node Details & AI Chat -->
|
||||
<div class="col-md-4 d-flex flex-column">
|
||||
<!-- Node Details Panel -->
|
||||
<div class="card shadow-sm mb-3" style="flex: 1;">
|
||||
<div class="card-header bg-white">
|
||||
<h5 class="mb-0">Node Details</h5>
|
||||
</div>
|
||||
<div class="card-body" id="node-details">
|
||||
<p class="text-muted">Click on a node or edge in the map to see its details here.</p>
|
||||
</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>
|
||||
|
||||
<!-- Load Vis Network library -->
|
||||
<script type="text/javascript" src="https://unpkg.com/vis-network/standalone/umd/vis-network.min.js"></script>
|
||||
|
||||
<!-- Add custom styles for vis-network tooltip -->
|
||||
<style type="text/css">
|
||||
.vis-tooltip {
|
||||
position: absolute;
|
||||
background-color: white;
|
||||
padding: 10px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
max-width: 300px;
|
||||
pointer-events: none;
|
||||
z-index: 10;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
document.getElementById('ai-submit').addEventListener('click', function() {
|
||||
const message = document.getElementById('ai-input').value;
|
||||
fetch("{% url 'ai_chat' project.pk %}", {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': '{{ csrf_token }}'
|
||||
// Colors for different categories
|
||||
const categoryColors = {
|
||||
'Strategy': '#e1bee7', // Purple
|
||||
'Product': '#bbdefb', // Blue
|
||||
'Market': '#c8e6c9', // Green
|
||||
'Operations': '#ffcc80', // Orange
|
||||
'Finance': '#ffcdd2', // Red
|
||||
'General': '#f5f5f5' // Grey
|
||||
};
|
||||
|
||||
function getNodeColor(category) {
|
||||
let cat = category || 'General';
|
||||
let color = '#e0e0e0'; // Default light grey
|
||||
for (let key in categoryColors) {
|
||||
if (cat.toLowerCase().includes(key.toLowerCase())) {
|
||||
color = categoryColors[key];
|
||||
break;
|
||||
}
|
||||
}
|
||||
return {
|
||||
background: color,
|
||||
border: '#9e9e9e',
|
||||
highlight: { background: '#ffffff', border: '#42a5f5' }
|
||||
};
|
||||
}
|
||||
|
||||
// Prepare data from Django context
|
||||
const nodesArray = [
|
||||
{% for node in nodes %}
|
||||
{
|
||||
id: {{ node.pk }},
|
||||
label: "{{ node.title|escapejs }}",
|
||||
title: "<strong>{{ node.title|escapejs }}</strong><br>{{ node.summary|escapejs }}<br><em>Category: {{ node.category|escapejs }}</em>",
|
||||
summary: "{{ node.summary|escapejs }}",
|
||||
category: "{{ node.category|escapejs }}",
|
||||
color: getNodeColor("{{ node.category|escapejs }}"),
|
||||
shape: 'box',
|
||||
font: { size: 16, face: 'Arial', color: '#333' },
|
||||
margin: 10
|
||||
},
|
||||
body: JSON.stringify({ message: message })
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
document.getElementById('ai-response').querySelector('.card-body').innerText = data.response;
|
||||
{% endfor %}
|
||||
];
|
||||
|
||||
const edgesArray = [
|
||||
{% for conn in connections %}
|
||||
{
|
||||
from: {{ conn.source.pk }},
|
||||
to: {{ conn.target.pk }},
|
||||
label: "{{ conn.how|escapejs }}",
|
||||
title: "<strong>Why:</strong> {{ conn.why|escapejs }}",
|
||||
arrows: "to",
|
||||
why: "{{ conn.why|escapejs }}",
|
||||
how: "{{ conn.how|escapejs }}"
|
||||
},
|
||||
{% endfor %}
|
||||
];
|
||||
|
||||
const container = document.getElementById("mynetwork");
|
||||
const data = {
|
||||
nodes: new vis.DataSet(nodesArray),
|
||||
edges: new vis.DataSet(edgesArray)
|
||||
};
|
||||
|
||||
const options = {
|
||||
layout: {
|
||||
improvedLayout: true
|
||||
},
|
||||
physics: {
|
||||
solver: 'forceAtlas2Based',
|
||||
forceAtlas2Based: {
|
||||
gravitationalConstant: -100,
|
||||
centralGravity: 0.01,
|
||||
springLength: 150,
|
||||
springConstant: 0.08
|
||||
},
|
||||
maxVelocity: 50,
|
||||
minVelocity: 0.1,
|
||||
timestep: 0.5,
|
||||
stabilization: { iterations: 150 }
|
||||
},
|
||||
interaction: {
|
||||
tooltipDelay: 200,
|
||||
hover: true
|
||||
},
|
||||
edges: {
|
||||
font: { size: 12, align: 'top' },
|
||||
color: { color: '#848484', highlight: '#42a5f5' },
|
||||
smooth: { type: 'continuous' }
|
||||
}
|
||||
};
|
||||
|
||||
const network = new vis.Network(container, data, options);
|
||||
|
||||
function fitNetwork() {
|
||||
network.fit({ animation: { duration: 1000, easingFunction: 'easeInOutQuad' } });
|
||||
}
|
||||
|
||||
// Handle clicks to show details in the sidebar
|
||||
network.on("click", function (params) {
|
||||
const detailsPanel = document.getElementById('node-details');
|
||||
|
||||
if (params.nodes.length > 0) {
|
||||
// Node clicked
|
||||
const nodeId = params.nodes[0];
|
||||
const node = data.nodes.get(nodeId);
|
||||
detailsPanel.innerHTML = `
|
||||
<h4 class="mb-2">${node.label}</h4>
|
||||
<span class="badge bg-secondary mb-3">${node.category}</span>
|
||||
<p><strong>Summary:</strong><br>${node.summary}</p>
|
||||
`;
|
||||
} else if (params.edges.length > 0) {
|
||||
// Edge clicked
|
||||
const edgeId = params.edges[0];
|
||||
const edge = data.edges.get(edgeId);
|
||||
const fromNode = data.nodes.get(edge.from);
|
||||
const toNode = data.nodes.get(edge.to);
|
||||
detailsPanel.innerHTML = `
|
||||
<h5 class="mb-3">Connection Details</h5>
|
||||
<p><strong>From:</strong> ${fromNode.label}</p>
|
||||
<p><strong>To:</strong> ${toNode.label}</p>
|
||||
<hr>
|
||||
<p><strong>How:</strong><br>${edge.how || 'N/A'}</p>
|
||||
<p><strong>Why:</strong><br>${edge.why || 'N/A'}</p>
|
||||
`;
|
||||
} else {
|
||||
// Clicked empty space
|
||||
detailsPanel.innerHTML = '<p class="text-muted">Click on a node or edge in the map to see its details here.</p>';
|
||||
}
|
||||
});
|
||||
|
||||
// AI Chat Handler
|
||||
document.getElementById('ai-submit').addEventListener('click', function() {
|
||||
const inputField = document.getElementById('ai-input');
|
||||
const message = inputField.value.trim();
|
||||
if (!message) return;
|
||||
|
||||
const responseDiv = document.getElementById('ai-response');
|
||||
|
||||
// Append user message
|
||||
responseDiv.innerHTML += `<div class="mb-2 text-end"><strong>You:</strong> ${message}</div>`;
|
||||
inputField.value = '';
|
||||
|
||||
// Show loading state
|
||||
const loadingId = 'loading-' + Date.now();
|
||||
responseDiv.innerHTML += `<div id="${loadingId}" class="mb-2 text-primary"><em>AI is thinking...</em></div>`;
|
||||
responseDiv.scrollTop = responseDiv.scrollHeight;
|
||||
|
||||
fetch("{% url 'ai_chat' project.pk %}", {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': '{{ csrf_token }}'
|
||||
},
|
||||
body: JSON.stringify({ message: message })
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(apiData => {
|
||||
document.getElementById(loadingId).remove();
|
||||
|
||||
// Handle chat response
|
||||
if (apiData.response) {
|
||||
responseDiv.innerHTML += `<div class="mb-2"><strong>AI:</strong> ${apiData.response}</div>`;
|
||||
}
|
||||
|
||||
// Check for new nodes and add them to the map
|
||||
let mapUpdated = false;
|
||||
|
||||
if (apiData.added_nodes && apiData.added_nodes.length > 0) {
|
||||
apiData.added_nodes.forEach(node => {
|
||||
data.nodes.add({
|
||||
id: node.id,
|
||||
label: node.title,
|
||||
title: "<strong>" + node.title + "</strong><br>" + node.summary + "<br><em>Category: " + node.category + "</em>",
|
||||
summary: node.summary,
|
||||
category: node.category,
|
||||
color: getNodeColor(node.category),
|
||||
shape: 'box',
|
||||
font: { size: 16, face: 'Arial', color: '#333' },
|
||||
margin: 10
|
||||
});
|
||||
});
|
||||
mapUpdated = true;
|
||||
}
|
||||
|
||||
if (apiData.added_connections && apiData.added_connections.length > 0) {
|
||||
apiData.added_connections.forEach(conn => {
|
||||
data.edges.add({
|
||||
id: conn.id,
|
||||
from: conn.source_id,
|
||||
to: conn.target_id,
|
||||
label: conn.how,
|
||||
title: "<strong>Why:</strong> " + conn.why,
|
||||
arrows: "to",
|
||||
why: conn.why,
|
||||
how: conn.how
|
||||
});
|
||||
});
|
||||
mapUpdated = true;
|
||||
}
|
||||
|
||||
if (mapUpdated) {
|
||||
// Stabilize and adjust view
|
||||
network.stabilize();
|
||||
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.scrollTop = responseDiv.scrollHeight;
|
||||
})
|
||||
.catch(err => {
|
||||
document.getElementById(loadingId).remove();
|
||||
responseDiv.innerHTML += `<div class="mb-2 text-danger"><strong>Error:</strong> Failed to get response from AI.</div>`;
|
||||
console.error(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
@ -3,6 +3,7 @@ from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth.forms import UserCreationForm
|
||||
from django.contrib.auth import login
|
||||
from .models import Project, MindMapNode, MindMapConnection
|
||||
from .ai_helpers import generate_initial_mindmap
|
||||
|
||||
def signup(request):
|
||||
if request.method == 'POST':
|
||||
@ -20,7 +21,7 @@ def home(request):
|
||||
|
||||
@login_required
|
||||
def project_list(request):
|
||||
projects = Project.objects.filter(user=request.user)
|
||||
projects = Project.objects.filter(user=request.user).order_by('-created_at')
|
||||
return render(request, 'core/project_list.html', {'projects': projects})
|
||||
|
||||
@login_required
|
||||
@ -41,5 +42,7 @@ def create_project(request):
|
||||
industry = request.POST.get('industry')
|
||||
goal = request.POST.get('goal')
|
||||
project = Project.objects.create(user=request.user, title=title, industry=industry, goal=goal)
|
||||
# Automatically generate the first mind map based on the input
|
||||
generate_initial_mindmap(project)
|
||||
return redirect('project_detail', pk=project.pk)
|
||||
return render(request, 'core/create_project.html')
|
||||
return render(request, 'core/create_project.html')
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user