- FastAPI backend with WebSocket chat - SQLite database for products - Z.AI (GLM-4.7) integration for AI responses - Docker deployment ready - Caddy proxy configuration
218 lines
7.9 KiB
HTML
218 lines
7.9 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="es">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Demo Librería - PymesBot</title>
|
|
<style>
|
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
background: #f5f5f5;
|
|
height: 100vh;
|
|
}
|
|
.container {
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
padding: 20px;
|
|
height: 100%;
|
|
}
|
|
.app {
|
|
display: grid;
|
|
grid-template-columns: 1fr 300px;
|
|
gap: 20px;
|
|
height: calc(100vh - 40px);
|
|
}
|
|
.chat-box {
|
|
background: white;
|
|
border-radius: 12px;
|
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
|
display: flex;
|
|
flex-direction: column;
|
|
overflow: hidden;
|
|
}
|
|
.chat-header {
|
|
padding: 16px 20px;
|
|
border-bottom: 1px solid #eee;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
}
|
|
.chat-header h1 { font-size: 18px; color: #333; }
|
|
.chat-messages {
|
|
flex: 1;
|
|
overflow-y: auto;
|
|
padding: 20px;
|
|
}
|
|
.message { margin-bottom: 16px; max-width: 80%; }
|
|
.message.bot { margin-right: auto; }
|
|
.message.user { margin-left: auto; text-align: right; }
|
|
.message .bubble {
|
|
display: inline-block;
|
|
padding: 12px 16px;
|
|
border-radius: 18px;
|
|
white-space: pre-wrap;
|
|
}
|
|
.message.bot .bubble { background: #f0f0f0; color: #333; }
|
|
.message.user .bubble { background: #007bff; color: white; }
|
|
.typing { color: #888; font-style: italic; padding: 8px; }
|
|
.chat-input {
|
|
padding: 16px 20px;
|
|
border-top: 1px solid #eee;
|
|
display: flex;
|
|
gap: 12px;
|
|
}
|
|
.chat-input input {
|
|
flex: 1;
|
|
padding: 12px 16px;
|
|
border: 1px solid #ddd;
|
|
border-radius: 24px;
|
|
outline: none;
|
|
font-size: 16px;
|
|
}
|
|
.chat-input input:focus { border-color: #007bff; }
|
|
.chat-input button {
|
|
padding: 12px 24px;
|
|
background: #007bff;
|
|
color: white;
|
|
border: none;
|
|
border-radius: 24px;
|
|
cursor: pointer;
|
|
font-size: 16px;
|
|
}
|
|
.chat-input button:hover { background: #0056b3; }
|
|
.sidebar { display: flex; flex-direction: column; gap: 20px; }
|
|
.sidebar-box {
|
|
background: white;
|
|
border-radius: 12px;
|
|
padding: 20px;
|
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
|
}
|
|
.sidebar-box h2 { font-size: 16px; margin-bottom: 12px; color: #333; }
|
|
.stat { display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid #eee; }
|
|
.stat-value { font-weight: 600; color: #007bff; }
|
|
.product-list { max-height: 200px; overflow-y: auto; }
|
|
.product-item { padding: 8px 0; border-bottom: 1px solid #eee; font-size: 14px; }
|
|
.product-item .name { font-weight: 500; }
|
|
.product-item .price { color: #007bff; }
|
|
.product-item .stock { color: #28a745; font-size: 12px; }
|
|
.product-item .no-stock { color: #dc3545; font-size: 12px; }
|
|
@media (max-width: 768px) {
|
|
.app { grid-template-columns: 1fr; }
|
|
.sidebar { display: none; }
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<div class="app">
|
|
<div class="chat-box">
|
|
<div class="chat-header">
|
|
<span style="font-size: 24px;">🛒</span>
|
|
<h1>Demo Librería</h1>
|
|
</div>
|
|
<div class="chat-messages" id="messages">
|
|
<div class="message bot">
|
|
<div class="bubble">¡Hola! Soy el asistente de ventas de Demo Librería. ¿Qué estás buscando?</div>
|
|
</div>
|
|
</div>
|
|
<div id="typing-indicator" class="typing" style="display: none;">
|
|
Escribiendo...
|
|
</div>
|
|
<div class="chat-input">
|
|
<input type="text" id="msgInput" placeholder="Escribí tu consulta..." autocomplete="off">
|
|
<button onclick="sendMessage()">Enviar</button>
|
|
</div>
|
|
</div>
|
|
<div class="sidebar">
|
|
<div class="sidebar-box">
|
|
<h2>📊 Hoy</h2>
|
|
<div class="stat">
|
|
<span>Ventas</span>
|
|
<span class="stat-value" id="ventas-hoy">0</span>
|
|
</div>
|
|
<div class="stat">
|
|
<span>Total</span>
|
|
<span class="stat-value" id="total-hoy">$0</span>
|
|
</div>
|
|
</div>
|
|
<div class="sidebar-box">
|
|
<h2>📦 Productos</h2>
|
|
<div class="product-list" id="product-list">
|
|
<div style="color: #888; font-size: 14px;">Escribí para buscar...</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
let ws = null;
|
|
const sessionId = Math.random().toString(36).substring(7);
|
|
|
|
function connect() {
|
|
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
ws = new WebSocket(`${protocol}//${window.location.host}/chat/ws/${sessionId}`);
|
|
|
|
ws.onopen = () => console.log('Conectado');
|
|
|
|
ws.onmessage = (event) => {
|
|
const data = JSON.parse(event.data);
|
|
|
|
if (data.typing !== undefined) {
|
|
document.getElementById('typing-indicator').style.display = data.typing ? 'block' : 'none';
|
|
}
|
|
|
|
if (data.msg) {
|
|
addMessage(data.msg, 'bot');
|
|
}
|
|
};
|
|
|
|
ws.onerror = (e) => console.error('WS Error:', e);
|
|
ws.onclose = () => {
|
|
console.log('Desconectado, reconectando...');
|
|
setTimeout(connect, 3000);
|
|
};
|
|
}
|
|
|
|
function addMessage(text, type) {
|
|
const div = document.createElement('div');
|
|
div.className = `message ${type}`;
|
|
div.innerHTML = `<div class="bubble">${escapeHtml(text)}</div>`;
|
|
document.getElementById('messages').appendChild(div);
|
|
document.getElementById('messages').scrollTop = document.getElementById('messages').scrollHeight;
|
|
}
|
|
|
|
function escapeHtml(text) {
|
|
const div = document.createElement('div');
|
|
div.textContent = text;
|
|
return div.innerHTML;
|
|
}
|
|
|
|
function sendMessage() {
|
|
const input = document.getElementById('msgInput');
|
|
const text = input.value.trim();
|
|
if (!text || !ws || ws.readyState !== WebSocket.OPEN) return;
|
|
|
|
addMessage(text, 'user');
|
|
ws.send(JSON.stringify({ msg: text, session_id: sessionId }));
|
|
input.value = '';
|
|
}
|
|
|
|
document.getElementById('msgInput').addEventListener('keypress', (e) => {
|
|
if (e.key === 'Enter') sendMessage();
|
|
});
|
|
|
|
connect();
|
|
|
|
fetch('/stats/ventas?periodo=hoy')
|
|
.then(r => r.json())
|
|
.then(data => {
|
|
document.getElementById('ventas-hoy').textContent = data.total_ventas || 0;
|
|
document.getElementById('total-hoy').textContent = '$' + (data.total_pesos || 0).toLocaleString();
|
|
})
|
|
.catch(() => {});
|
|
</script>
|
|
</body>
|
|
</html>
|