198 lines
7.0 KiB
HTML

{% extends 'base.html' %}
{% load i18n static %}
{% block title %}{% trans "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 %}