188 lines
6.5 KiB
HTML
188 lines
6.5 KiB
HTML
{% extends 'base.html' %}
|
|
{% load i18n static %}
|
|
|
|
{% block title %}Haikal Bot{% endblock %}
|
|
|
|
{% block content %}
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/PapaParse/5.3.2/papaparse.min.js"></script>
|
|
|
|
<div class="container mt-5">
|
|
<div class="card shadow-sm">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<h5 class="mb-0"><i class="fas fa-robot me-2"></i>{% trans "HaikalBot" %}</h5>
|
|
<div>
|
|
<button id="export-btn" class="btn btn-sm btn-phoenix-secondary" style="display:none;">
|
|
{% trans "Export CSV" %}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="card-body" style="max-height: 60vh; overflow-y: auto;" id="chat-history"></div>
|
|
<div class="card-footer bg-white border-top">
|
|
<form id="chat-form" class="d-flex align-items-center gap-2">
|
|
<button type="button" class="btn btn-light" id="mic-btn"><i class="fas fa-microphone"></i></button>
|
|
<input type="text" class="form-control" id="chat-input" placeholder="{% trans 'Type your question...' %}" required />
|
|
<button type="submit" class="btn btn-phoenix-primary"><i class="fas fa-paper-plane"></i></button>
|
|
</form>
|
|
</div>
|
|
<div id="chart-container" style="display:none;" class="p-4 border-top">
|
|
<canvas id="chart-canvas" height="200px"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
const chatHistory = document.getElementById('chat-history');
|
|
const chartContainer = document.getElementById('chart-container');
|
|
const chartCanvas = document.getElementById('chart-canvas');
|
|
const exportBtn = document.getElementById('export-btn');
|
|
let chartInstance = null;
|
|
let latestDataTable = null;
|
|
|
|
function getCookie(name) {
|
|
let cookieValue = null;
|
|
if (document.cookie && document.cookie !== "") {
|
|
const cookies = document.cookie.split(";");
|
|
for (let cookie of cookies) {
|
|
cookie = cookie.trim();
|
|
if (cookie.substring(0, name.length + 1) === name + "=") {
|
|
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return cookieValue;
|
|
}
|
|
|
|
function speak(text) {
|
|
const utterance = new SpeechSynthesisUtterance(text);
|
|
utterance.lang = document.documentElement.lang || "en";
|
|
window.speechSynthesis.speak(utterance);
|
|
}
|
|
|
|
function renderTable(data) {
|
|
latestDataTable = data;
|
|
exportBtn.style.display = 'inline-block';
|
|
const headers = Object.keys(data[0]);
|
|
let html = '<div class="table-responsive"><table class="table table-bordered table-striped"><thead><tr>';
|
|
headers.forEach(h => html += `<th>${h}</th>`);
|
|
html += '</tr></thead><tbody>';
|
|
data.forEach(row => {
|
|
html += '<tr>' + headers.map(h => `<td>${row[h]}</td>`).join('') + '</tr>';
|
|
});
|
|
html += '</tbody></table></div>';
|
|
return html;
|
|
}
|
|
|
|
function appendMessage(role, htmlContent) {
|
|
const align = role === 'AI' ? 'bg-secondary-light' : 'bg-primary-light';
|
|
chatHistory.innerHTML += `
|
|
<div class="mb-3 p-3 rounded ${align}">
|
|
<strong>${role}:</strong><br>${htmlContent}
|
|
</div>
|
|
`;
|
|
chatHistory.scrollTop = chatHistory.scrollHeight;
|
|
}
|
|
|
|
document.getElementById('chat-form').addEventListener('submit', async function(e) {
|
|
e.preventDefault();
|
|
const input = document.getElementById('chat-input');
|
|
const prompt = input.value.trim();
|
|
const csrfToken = getCookie("csrftoken");
|
|
|
|
if (!prompt) return;
|
|
|
|
appendMessage('You', prompt);
|
|
input.value = "";
|
|
chartContainer.style.display = 'none';
|
|
exportBtn.style.display = 'none';
|
|
if (chartInstance) {
|
|
chartInstance.destroy();
|
|
chartInstance = null;
|
|
}
|
|
|
|
const response = await fetch("{% url 'haikalbot' %}", {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
"X-CSRFToken": csrfToken
|
|
},
|
|
body: new URLSearchParams({ prompt })
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
// Show chart if available
|
|
if (result.chart && result.chart.type && result.chart.labels && result.chart.data) {
|
|
chartInstance = new Chart(chartCanvas, {
|
|
type: result.chart.type,
|
|
data: {
|
|
labels: result.chart.labels,
|
|
datasets: [{
|
|
label: result.chart.labels.join(", "),
|
|
data: result.chart.data,
|
|
backgroundColor: result.chart.backgroundColor || []
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
plugins: {
|
|
title: {
|
|
display: true,
|
|
text: result.chart.type.toUpperCase()
|
|
}
|
|
}
|
|
}
|
|
});
|
|
chartContainer.style.display = 'block';
|
|
appendMessage('AI', `{% trans "Chart displayed below." %}`);
|
|
|
|
return;
|
|
}
|
|
|
|
// Table if list of objects
|
|
if (Array.isArray(result.data) && result.data.length && typeof result.data[0] === 'object') {
|
|
const tableHTML = renderTable(result.data);
|
|
appendMessage('AI', tableHTML);
|
|
|
|
} else {
|
|
const content = typeof result.data === 'object'
|
|
? `<pre>${JSON.stringify(result.data, null, 2)}</pre>`
|
|
: `<p>${result.data}</p>`;
|
|
appendMessage('AI', content);
|
|
}
|
|
});
|
|
|
|
document.getElementById('export-btn').addEventListener('click', () => {
|
|
if (!latestDataTable) return;
|
|
const csv = Papa.unparse(latestDataTable);
|
|
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
|
|
const url = URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = 'haikal_data.csv';
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
document.body.removeChild(a);
|
|
});
|
|
|
|
// Voice input (speech-to-text)
|
|
document.getElementById('mic-btn').addEventListener('click', () => {
|
|
const recognition = new (window.SpeechRecognition || window.webkitSpeechRecognition)();
|
|
recognition.lang = document.documentElement.lang || "en";
|
|
recognition.interimResults = false;
|
|
recognition.maxAlternatives = 1;
|
|
|
|
recognition.onresult = (event) => {
|
|
const speech = event.results[0][0].transcript;
|
|
document.getElementById('chat-input').value = speech;
|
|
};
|
|
|
|
recognition.onerror = (e) => {
|
|
console.error('Speech recognition error', e);
|
|
};
|
|
|
|
recognition.start();
|
|
});
|
|
</script>
|
|
{% endblock %} |