shop-agent / static /index.html
eshan13's picture
Update static/index.html
23eccbb verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Shopping Assistant</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
}
.container {
display: flex;
width: 100%;
max-width: 1200px;
height: 90vh;
background: white;
border-radius: 16px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
overflow: hidden;
}
.chat-section {
flex: 1;
display: flex;
flex-direction: column;
border-right: 1px solid #e0e0e0;
}
.header {
padding: 24px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-bottom: none;
}
.header h1 {
font-size: 24px;
margin-bottom: 4px;
}
.header p {
opacity: 0.9;
font-size: 14px;
}
.messages-container {
flex: 1;
overflow-y: auto;
padding: 24px;
background: #f9f9f9;
}
.message {
margin-bottom: 16px;
animation: slideIn 0.3s ease-out;
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.message.user {
display: flex;
justify-content: flex-end;
}
.message.assistant {
display: flex;
justify-content: flex-start;
}
.message-content {
max-width: 70%;
padding: 12px 16px;
border-radius: 12px;
word-wrap: break-word;
line-height: 1.4;
}
.user .message-content {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.assistant .message-content {
background: #e8e8e8;
color: #333;
}
.input-area {
padding: 16px 24px;
border-top: 1px solid #e0e0e0;
display: flex;
gap: 12px;
}
.input-wrapper {
flex: 1;
display: flex;
gap: 8px;
}
input[type="text"] {
flex: 1;
padding: 12px 16px;
border: 1px solid #e0e0e0;
border-radius: 24px;
font-size: 14px;
font-family: inherit;
transition: all 0.3s ease;
}
input[type="text"]:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
button {
padding: 10px 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
border-radius: 24px;
cursor: pointer;
font-size: 14px;
font-weight: 600;
transition: all 0.3s ease;
}
button:hover {
transform: translateY(-2px);
box-shadow: 0 8px 16px rgba(102, 126, 234, 0.3);
}
button:active {
transform: translateY(0);
}
button:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.products-section {
flex: 1;
overflow-y: auto;
padding: 24px;
background: white;
}
.products-title {
font-size: 16px;
font-weight: 600;
margin-bottom: 16px;
color: #333;
}
.products-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
gap: 16px;
}
.product-card {
border: 1px solid #e6e6e6;
border-radius: 12px;
overflow: hidden;
transition: all 0.3s ease;
display: flex;
flex-direction: column;
height: 100%;
}
.product-card:hover {
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
transform: translateY(-4px);
}
.product-image {
width: 100%;
height: 120px;
object-fit: contain;
background: #f5f5f5;
padding: 8px;
}
.product-info {
padding: 12px;
flex: 1;
display: flex;
flex-direction: column;
}
.product-name {
font-size: 12px;
font-weight: 600;
line-height: 1.2;
margin-bottom: 6px;
color: #333;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.product-price {
font-size: 14px;
font-weight: 700;
color: #667eea;
margin-bottom: 4px;
}
.product-rating {
font-size: 12px;
color: #666;
margin-bottom: 8px;
}
.product-link {
margin-top: auto;
padding: 8px 12px;
background: #f0f0f0;
color: #667eea;
text-decoration: none;
border-radius: 6px;
text-align: center;
font-size: 12px;
font-weight: 600;
transition: all 0.3s ease;
}
.product-link:hover {
background: #667eea;
color: white;
}
.prompt-modal {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
justify-content: center;
align-items: center;
z-index: 1000;
}
.prompt-modal.active {
display: flex;
}
.prompt-content {
background: white;
padding: 32px;
border-radius: 16px;
max-width: 500px;
width: 90%;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
animation: slideIn 0.3s ease-out;
}
.prompt-content h2 {
font-size: 18px;
margin-bottom: 16px;
color: #333;
}
.prompt-content input {
width: 100%;
padding: 12px;
margin-bottom: 16px;
border: 1px solid #e0e0e0;
border-radius: 8px;
font-size: 14px;
}
.prompt-buttons {
display: flex;
gap: 12px;
justify-content: flex-end;
}
.prompt-buttons button {
flex: 1;
padding: 12px;
}
.prompt-buttons .cancel {
background: #e0e0e0;
color: #333;
}
.loading {
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
background: #667eea;
margin: 0 2px;
animation: pulse 1.5s infinite;
}
.loading:nth-child(2) {
animation-delay: 0.2s;
}
.loading:nth-child(3) {
animation-delay: 0.4s;
}
@keyframes pulse {
0%, 100% {
opacity: 0.3;
}
50% {
opacity: 1;
}
}
@media (max-width: 768px) {
.container {
flex-direction: column;
}
.chat-section {
border-right: none;
border-bottom: 1px solid #e0e0e0;
}
.message-content {
max-width: 85%;
}
.products-grid {
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
}
}
</style>
</head>
<body>
<div class="container">
<div class="chat-section">
<div class="header">
<h1>💬 Shopping Assistant</h1>
<p>What would you like to buy today?</p>
</div>
<div class="messages-container" id="messagesContainer"></div>
<div class="input-area">
<div class="input-wrapper">
<input
type="text"
id="userInput"
placeholder="Type your message..."
autocomplete="off"
/>
</div>
<button id="sendBtn" onclick="sendMessage()">Send</button>
</div>
</div>
<div class="products-section">
<h2 class="products-title">Results</h2>
<div class="products-grid" id="productsGrid"></div>
</div>
</div>
<!-- <div class="prompt-modal" id="promptModal">
<div class="prompt-content">
<h2 id="promptText"></h2>
<input type="text" id="promptInput" placeholder="Your response..." />
<div class="prompt-buttons">
<button class="cancel" onclick="cancelPrompt()">Cancel</button>
<button onclick="submitPrompt()">Submit</button>
</div>
</div>
</div> -->
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<script>
const ws = new WebSocket(`wss://${window.location.host}/ws/chat`);
const messagesContainer = document.getElementById('messagesContainer');
const userInput = document.getElementById('userInput');
const sendBtn = document.getElementById('sendBtn');
const productsGrid = document.getElementById('productsGrid');
// const promptModal = document.getElementById('promptModal');
window.isUserPrompt = false;
window.isIntro = false;
ws.onopen = () => {
console.log('WebSocket connected');
addSystemMessage('Connected to shopping assistant.');
addSystemMessage('**What do you want to shop today?**');
window.isIntro = true;
};
ws.onmessage = (event) => {
console.log(`Message from backend: ${event}`)
const message = JSON.parse(event.data);
console.log('Parsed message:', message);
if (message.type === 'response') {
addAssistantMessage(message.message);
if (message.products && message.products.length > 0) {
if (message.recc){
displayProducts(message.recc, message.products);
}
else{
displayProducts(null, message.products);
}
}
window.isIntro = false
} else if (message.type === 'prompt') {
window._current_prompt_id = message.prompt_id;
window.isUserPrompt = true
addAssistantMessage(message.question);
// showPrompt(message.message);
} else if (message.type === 'error') {
addSystemMessage(`Error: ${message.message}`);
} else if (message.type === 'ackChat'){
window.isUserPrompt = false;
if (window.isIntro != true){
addSystemMessage('**What do you want to shop today?**');
}
}
};
ws.onerror = () => {
addSystemMessage('Connection error. Please refresh the page.');
};
ws.onclose = () => {
addSystemMessage('Connection closed.');
};
function sendMessage() {
const text = userInput.value.trim();
// if (text === '') return;
console.log(`Message sent: ${text}`)
addUserMessage(text);
if (window.isUserPrompt == true){
ws.send(JSON.stringify({
type: 'prompt_response',
content: text,
prompt_id: window._current_prompt_id
}))
}
else{
ws.send(JSON.stringify({
type: 'chat',
content: text
}));
}
userInput.value = '';
userInput.focus();
sendBtn.disabled = true;
sendBtn.textContent = 'Sending...';
}
function addUserMessage(text) {
const div = document.createElement('div');
div.className = 'message user';
div.innerHTML = `<div class="message-content">${escapeHtml(text)}</div>`;
messagesContainer.appendChild(div);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
}
function addAssistantMessage(text) {
const div = document.createElement('div');
div.className = 'message assistant';
// div.innerHTML = `<div class="message-content">${escapeHtml(text)}</div>`;
div.innerHTML = `<div class="message-content">${marked.parse(text, {breaks: true})}</div>`;
messagesContainer.appendChild(div);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
sendBtn.disabled = false;
sendBtn.textContent = 'Send';
}
function addSystemMessage(text) {
const div = document.createElement('div');
div.className = 'message assistant';
// div.innerHTML = `<div class="message-content"><em>${escapeHtml(text)}</em></div>`;
div.innerHTML = `<div class="message-content">${marked.parse(text, {breaks: true})}</div>`;
messagesContainer.appendChild(div);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
}
function displayProducts(recc, products) {
try {
console.log('displayProducts called with:', { recc, products });
productsGrid.innerHTML = '';
if (recc != null){
const reccCard = document.createElement('a');
reccCard.href = recc.url || '#';
reccCard.target = '_blank';
reccCard.className = 'product-card';
const price = typeof recc.price === 'number'
? `₹${recc.price.toLocaleString()}`
: recc.price;
const rating = recc.review
? `⭐ ${recc.review.ratings} (${recc.review.num_ratings})`
: '';
reccCard.innerHTML = `
<img class="product-image" src="${escapeHtml(recc.image || 'https://via.placeholder.com/160x120?text=No+Image')}" alt="${escapeHtml(recc.name)}" />
<div class="product-info">
<div class="product-name">${escapeHtml(recc.name)}</div>
<div class="product-price">${escapeHtml(price)}</div>
<div class="product-rating">${escapeHtml(rating)}</div>
<div class="product-link"><button class="view-btn">🌟 Recommended Product</button></div>
</div>
`;
productsGrid.appendChild(reccCard);
}
products.forEach(product => {
if (product.url != recc.url){
const card = document.createElement('a');
card.href = product.url || '#';
card.target = '_blank';
card.className = 'product-card';
const price = typeof product.price === 'number'
? `₹${product.price.toLocaleString()}`
: product.price;
const rating = product.review
? `⭐ ${product.review.ratings} (${product.review.num_ratings})`
: '';
card.innerHTML = `
<img class="product-image" src="${escapeHtml(product.image || 'https://via.placeholder.com/160x120?text=No+Image')}" alt="${escapeHtml(product.name)}" />
<div class="product-info">
<div class="product-name">${escapeHtml(product.name)}</div>
<div class="product-price">${escapeHtml(price)}</div>
<div class="product-rating">${escapeHtml(rating)}</div>
<div class="product-link">View Product</div>
</div>
`;
productsGrid.appendChild(card);
}
});
console.log('Products rendered successfully');
} catch (err) {
console.error('Error rendering products:', err);
}
}
// function showPrompt(question) {
// document.getElementById('promptText').textContent = question;
// document.getElementById('promptInput').value = '';
// promptModal.classList.add('active');
// document.getElementById('promptInput').focus();
// }
// function submitPrompt() {
// const response = document.getElementById('promptInput').value.trim();
// if (response === '') return;
// ws.send(JSON.stringify({
// type: 'prompt_response',
// content: response,
// prompt_id: window._current_prompt_id
// }));
// promptModal.classList.remove('active');
// window._current_prompt_id = null;
// }
// function cancelPrompt() {
// promptModal.classList.remove('active');
// }
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
userInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
sendMessage();
}
});
// Re-enable send button when input is not empty
userInput.addEventListener('input', () => {
sendBtn.disabled = userInput.value.trim() === '';
});
</script>
</body>
</html>