<!DOCTYPE html>
<html>
<head>
<title>Context Memory Chat</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
background: #f5f5f5;
}
.container {
background: white;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 15px;
}
.header-left {
flex: 1;
}
.header-right {
display: flex;
gap: 10px; /* space between buttons */
}
.header h1 {
margin: 0;
}
.stats-btn {
background: #28a745;
padding: 10px 15px;
}
.stats-btn:hover {
background: #218838;
}
.chat-box {
height: 400px;
overflow-y: auto;
border: 1px solid #ddd;
padding: 10px;
margin-bottom: 10px;
background: #fafafa;
border-radius: 4px;
}
.message {
margin: 10px 0;
padding: 10px;
border-radius: 4px;
white-space: normal;
word-wrap: break-word;
}
.message h1, .message h2, .message h3 {
margin: 10px 0 5px 0;
}
.message p {
margin: 5px 0;
}
.message code {
background: rgba(0,0,0,0.1);
padding: 2px 6px;
border-radius: 3px;
font-family: monospace;
}
.message pre {
background: rgba(0,0,0,0.1);
padding: 10px;
border-radius: 4px;
overflow-x: auto;
}
.message ul, .message ol {
margin: 5px 0 5px 20px;
}
.user-message {
background: #007bff;
color: white;
text-align: right;
}
.assistant-message {
background: #e9ecef;
color: black;
}
.system-message {
background: #d4edda;
color: #155724;
text-align: center;
font-size: 12px;
}
.error-message {
background: #f8d7da;
color: #721c24;
}
.input-group {
display: flex;
gap: 10px;
margin-bottom: 10px;
}
input[type="text"], input[type="password"] {
flex: 1;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
}
button {
padding: 10px 20px;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background: #0056b3;
}
.clear-btn {
background: #dc3545;
}
.clear-btn:hover {
background: #c82333;
}
.user-input-group {
display: flex;
gap: 10px;
margin-bottom: 10px;
}
.user-input-group input {
flex: 1;
}
/* Modal Styles */
.modal {
display: none;
position: fixed;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.5);
animation: fadeIn 0.3s ease-in;
}
.modal.show {
display: flex;
justify-content: center;
align-items: center;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.modal-content {
background-color: white;
padding: 30px;
border-radius: 8px;
width: 90%;
max-width: 600px;
max-height: 80vh;
overflow-y: auto;
box-shadow: 0 4px 20px rgba(0,0,0,0.3);
animation: slideIn 0.3s ease-out;
}
@keyframes slideIn {
from {
transform: translateY(-50px);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
border-bottom: 2px solid #007bff;
padding-bottom: 10px;
}
.modal-header h2 {
margin: 0;
color: #333;
}
.close-btn {
background: none;
border: none;
font-size: 28px;
cursor: pointer;
color: #999;
padding: 0;
width: auto;
}
.close-btn:hover {
color: #333;
background: none;
}
/* Analytics Styles */
.analytics-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin-bottom: 20px;
}
.stat-card {
background: #f8f9fa;
padding: 15px;
border-radius: 6px;
border-left: 4px solid #007bff;
}
.stat-card h3 {
margin: 0 0 10px 0;
color: #007bff;
font-size: 14px;
text-transform: uppercase;
}
.stat-value {
font-size: 28px;
font-weight: bold;
color: #333;
}
.stat-label {
font-size: 12px;
color: #666;
margin-top: 5px;
}
.chart-container {
margin: 20px 0;
padding: 15px;
background: #f8f9fa;
border-radius: 6px;
}
.chart-container h3 {
margin-top: 0;
color: #333;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 10px;
}
table th, table td {
padding: 10px;
text-align: left;
border-bottom: 1px solid #ddd;
}
table th {
background: #007bff;
color: white;
}
table tr:hover {
background: #f5f5f5;
}
.loading {
text-align: center;
padding: 20px;
color: #666;
}
.error {
background: #f8d7da;
color: #721c24;
padding: 15px;
border-radius: 4px;
margin-bottom: 10px;
}
</style>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
</head>
<body>
<div class="container">
<!-- LOGIN PANEL -->
<div id="authPanel">
<h2>🔐 Login / Register</h2>
<input id="authUser" type="text" placeholder="Username">
<input id="authPass" type="password" placeholder="Password">
<button onclick="login()">Login</button>
<button class="stats-btn" onclick="registerUser()">Register</button>
<p id="authError" style="color:red; margin-top: 10px;"></p>
</div>
<!-- CHAT PANEL -->
<div id="chatPanel" style="display:none;">
<div class="header">
<div class="header-left">
<h1>💬 Travel Memory Chat</h1>
</div>
<div class="header-right">
<button class="stats-btn" onclick="openAnalyticsModal()">📊 Statistics</button>
<button class="clear-btn" onclick="logout()">Switch User</button>
</div>
</div>
<p><strong>User:</strong> <span id="displayUser"></span></p>
<div class="chat-box" id="chatBox"></div>
<div class="input-group">
<input
type="text"
id="messageInput"
placeholder="Type your message..."
onkeypress="handleKeyPress(event)"
>
<button onclick="sendMessage()">Send</button>
<button class="clear-btn" onclick="clearHistory()">Clear</button>
</div>
<p id="status" style="color: #666; font-size: 12px;"></p>
</div>
</div>
<!-- <div class="container">
<div class="header">
<h1>💬 Travel Memory Chat - Your Trip Helper</h1>
<button class="stats-btn" onclick="openAnalyticsModal()">📊 Statistics</button>
</div>
<div class="user-input-group">
<input
type="text"
id="userIdInput"
placeholder="Enter user ID"
value="user123"
>
<button onclick="setUserId()">Set User</button>
</div>
<div class="chat-box" id="chatBox"></div>
<div class="input-group">
<input
type="text"
id="messageInput"
placeholder="Type your message..."
onkeypress="handleKeyPress(event)"
>
<button onclick="sendMessage()">Send</button>
<button class="clear-btn" onclick="clearHistory()">Clear</button>
</div>
<p id="status" style="color: #666; font-size: 12px;"></p>
</div>
-->
<!-- Analytics Modal -->
<div id="analyticsModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h2>📊 Analytics Dashboard</h2>
<button class="close-btn" onclick="closeAnalyticsModal()">×</button>
</div>
<div id="analyticsContent">
<div class="loading">Loading analytics...</div>
</div>
</div>
</div>
<script>
const API_URL = "/api";
let token = null;
let currentUserId = null;
let analyticsChart = null;
// ----------------------
// AUTH FUNCTIONS
// ----------------------
function showAuth() {
document.getElementById("authPanel").style.display = "block";
document.getElementById("chatPanel").style.display = "none";
}
function showChat() {
document.getElementById("authPanel").style.display = "none";
document.getElementById("chatPanel").style.display = "block";
}
function loadAuthState() {
token = localStorage.getItem("token");
currentUserId = localStorage.getItem("user_id");
if (token && currentUserId) {
document.getElementById("displayUser").textContent = currentUserId;
showChat();
} else {
showAuth();
}
}
async function login() {
const user_id = document.getElementById("authUser").value.trim();
const password = document.getElementById("authPass").value.trim();
if (!user_id || !password) {
return showAuthError("Missing user_id or password.");
}
try {
const res = await fetch(`${API_URL}/login`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ user_id, password })
});
const data = await res.json();
if (data.status === "error") {
return showAuthError(data.message);
}
token = data.token;
currentUserId = data.user_id;
localStorage.setItem("token", token);
localStorage.setItem("user_id", currentUserId);
document.getElementById("displayUser").textContent = currentUserId;
showChat();
} catch (err) {
showAuthError("Network error. Please try again.");
}
}
async function registerUser() {
const user_id = document.getElementById("authUser").value.trim();
const password = document.getElementById("authPass").value.trim();
if (!user_id || !password) {
return showAuthError("Missing user_id or password.");
}
try {
const res = await fetch(`${API_URL}/register`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ user_id, password })
});
const data = await res.json();
if (data.status === "error") {
return showAuthError(data.message);
}
token = data.token;
currentUserId = data.user_id;
localStorage.setItem("token", token);
localStorage.setItem("user_id", currentUserId);
document.getElementById("displayUser").textContent = currentUserId;
showChat();
} catch (err) {
showAuthError("Network error. Please try again.");
}
}
function logout() {
localStorage.removeItem("token");
localStorage.removeItem("user_id");
token = null;
currentUserId = null;
showAuth();
}
function showAuthError(msg) {
document.getElementById("authError").textContent = msg;
}
function authHeader() {
return {
"Content-Type": "application/json",
"Authorization": `Bearer ${token}`
};
}
// ----------------------
// CHAT FUNCTIONS
// ----------------------
async function sendMessage() {
const input = document.getElementById("messageInput");
const message = input.value.trim();
if (!message) return;
addMessage(message, "user");
input.value = "";
try {
updateStatus("Sending...");
const response = await fetch(`${API_URL}/chat`, {
method: "POST",
headers: authHeader(),
body: JSON.stringify({ message })
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const data = await response.json();
addMessage(data.response, "assistant");
updateStatus("Connected ✓");
} catch (error) {
addMessage(`Error: ${error.message}`, "error");
updateStatus("Error ✗");
}
}
async function clearHistory() {
if (!confirm("Clear history?")) return;
try {
updateStatus("Clearing...");
const res = await fetch(`${API_URL}/clear`, {
method: "POST",
headers: authHeader(),
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
document.getElementById("chatBox").innerHTML = "";
addMessage("History cleared.", "system");
updateStatus("Done ✓");
} catch (err) {
addMessage(`Error: ${err.message}`, "error");
updateStatus("Error ✗");
}
}
function openAnalyticsModal() {
document.getElementById("analyticsModal").classList.add("show");
fetchAnalytics();
}
function closeAnalyticsModal() {
document.getElementById("analyticsModal").classList.remove("show");
if (analyticsChart) {
analyticsChart.destroy();
analyticsChart = null;
}
}
async function fetchAnalytics() {
const content = document.getElementById("analyticsContent");
content.innerHTML = '<div class="loading">⏳ Refreshing analytics...</div>';
try {
const response = await fetch(`${API_URL}/analytics`);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const data = await response.json();
// Minimum 1.5 second loading display
await new Promise(resolve => setTimeout(resolve, 1500));
visualizeAnalytics(data);
} catch (error) {
content.innerHTML =
`<div class="error">Failed to load analytics: ${error.message}</div>`;
}
}
function visualizeAnalytics(data) {
const content = document.getElementById("analyticsContent");
if (!data || Object.keys(data).length === 0) {
content.innerHTML = '<div class="error">No analytics data available</div>';
return;
}
let html = '';
html += `<div class="chart-container">
<h3>Calls Comparison</h3>
<canvas id="analyticsChart"></canvas>
</div>`;
const chartData = [];
// Create a card for each tool/function
for (const [toolName, metrics] of Object.entries(data)) {
html += `
<div class="chart-container">
<h3>${formatKey(toolName)}</h3>
<table>
<thead>
<tr>
<th>Metric</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr>
<td>Calls</td>
<td><strong>${metrics.calls}</strong></td>
</tr>
<tr>
<td>Tokens In</td>
<td>${metrics.tokens_in}</td>
</tr>
<tr>
<td>Tokens Out</td>
<td>${metrics.tokens_out}</td>
</tr>
</tbody>
</table>
</div>
`;
// Collect data for chart
chartData.push({
label: formatKey(toolName),
calls: metrics.calls,
tokensIn: metrics.tokens_in,
tokensOut: metrics.tokens_out
});
}
content.innerHTML = html;
// Initialize chart
initializeCallsChart(chartData);
}
function initializeCallsChart(chartData) {
const ctx = document.getElementById("analyticsChart");
if (!ctx) return;
if (analyticsChart) {
analyticsChart.destroy();
}
analyticsChart = new Chart(ctx, {
type: 'bar',
data: {
labels: chartData.map(d => d.label),
datasets: [
{
label: 'Calls',
data: chartData.map(d => d.calls),
backgroundColor: '#007bff'
},
{
label: 'Tokens In',
data: chartData.map(d => d.tokensIn),
backgroundColor: '#28a745'
},
{
label: 'Tokens Out',
data: chartData.map(d => d.tokensOut),
backgroundColor: '#ffc107'
}
]
},
options: {
responsive: true,
plugins: {
legend: { position: 'top' }
},
scales: {
y: { beginAtZero: true }
}
}
});
}
function formatKey(key) {
return key
.replace(/_/g, ' ')
.replace(/([A-Z])/g, ' $1')
.replace(/^./, str => str.toUpperCase())
.trim();
}
function addMessage(text, sender) {
const chatBox = document.getElementById("chatBox");
const messageDiv = document.createElement("div");
messageDiv.className = `message ${sender}-message`;
messageDiv.innerHTML = marked.parse(text);
chatBox.appendChild(messageDiv);
chatBox.scrollTop = chatBox.scrollHeight;
}
function updateStatus(text) {
document.getElementById("status").textContent = `Status: ${text}`;
}
function handleKeyPress(event) {
if (event.key === "Enter") {
sendMessage();
}
}
// Close modal when clicking outside
window.addEventListener("click", (event) => {
const modal = document.getElementById("analyticsModal");
if (event.target === modal) {
closeAnalyticsModal();
}
});
// Check API health on load
window.addEventListener("load", async () => {
try {
const response = await fetch(`${API_URL}/health`);
if (response.ok) {
updateStatus("Gateway Connected ✓");
}
} catch {
updateStatus("Gateway unavailable ✗");
}
});
// check possible login
document.addEventListener("DOMContentLoaded", () => {
const savedToken = localStorage.getItem("token");
const savedUserId = localStorage.getItem("user_id");
if (savedToken && savedUserId) {
// Optionally verify token with backend:
autoLogin(savedToken, savedUserId);
}
});
async function autoLogin(token, user_id) {
try {
const res = await fetch(`${API_URL}/auth-check`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${token}`
}
});
const data = await res.json();
if (res.ok && data.status === "ok") {
// Valid session → show chat UI
currentUserId = user_id;
showChat();
document.getElementById("displayUser").textContent = currentUserId;
} else {
// Token invalid → clear and show login form
localStorage.removeItem("token");
localStorage.removeItem("user_id");
showAuth();
}
} catch (err) {
console.error("Auto-login failed:", err);
showAuth();
}
}
</script>
</body>
</html>