besper-frontend-site-dev-0935
Version:
Professional B-esper Frontend Site - Site-wide integration toolkit for full website bot deployment
921 lines (808 loc) • 27.2 kB
JavaScript
// src/components/demo-interface.js - Full Demo Interface
// Extracted from tobedeveloped_demo.html
/**
* BesperDemoInterface - Complete demo interface with chat and knowledge management
* This replicates the functionality from tobedeveloped_demo.html
*/
export class BesperDemoInterface {
constructor(botId, options = {}) {
this.botId = botId;
this.options = {
environment: 'dev',
container: 'besperDemoContainer',
...options,
};
this.state = {
threadId: null,
isProcessing: false,
connectedWebsite: null,
currentKnowledgeData: [],
currentPage: 1,
itemsPerPage: 10,
knowledgePollingInterval: null,
isInitialListKnowledge: true,
robotTxtMessageSent: false,
hasShownInitialKnowledgeQuestions: false,
dataPolicyUrl: null,
messages: [],
};
this.els = {};
this.translations = this.getTranslations();
}
/**
* Initialize the demo interface
*/
async init() {
if (this.isMobileDevice()) {
console.log('📱 Mobile device detected - demo interface hidden');
return null;
}
// Create container if it doesn't exist
this.createContainer();
// Inject CSS
this.injectCSS();
// Create HTML structure
this.createHTML();
// Get element references
this.getElements();
// Set up event listeners
this.setupEventListeners();
// Initialize the demo
await this.initializeDemo();
return this;
}
/**
* Check if device is mobile
*/
isMobileDevice() {
return (
window.innerWidth <= 768 ||
/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
navigator.userAgent
)
);
}
/**
* Create container element if it doesn't exist
*/
createContainer() {
let container = document.getElementById(this.options.container);
if (!container) {
container = document.createElement('div');
container.id = this.options.container;
document.body.appendChild(container);
}
this.container = container;
}
/**
* Get translations for the interface
*/
getTranslations() {
return {
title: 'B-esper AI Assistant Demo',
subtitle: 'Interactive Knowledge Integration',
connecting: 'Connecting...',
online: 'Online',
enterMessage: 'Type your message...',
connect: 'Connect',
enterValidUrl: 'Please enter a valid URL',
disconnectFirst: 'Please disconnect the current website first',
connectionFailed: 'Failed to connect to website',
websiteConnected: 'Website connected',
websiteDisconnected: 'Website disconnected',
downloaded: 'Conversation downloaded',
downloadFailed: 'Download failed',
issueError: 'Sorry, I encountered an issue. Please try again.',
robotTxtDetected: 'What can you tell me about this website?',
demoQuestion1: 'What services do you offer?',
demoQuestion2: 'How can I get started?',
demoQuestion3: 'What are your business hours?',
pages: 'pages',
words: 'words',
pageName: 'Page Name',
source: 'Source',
connectedWebsiteTitle: 'Connected Website',
analyzingWebsite: 'Analyzing website content',
connectingWebsite: 'This may take a few moments...',
dataPolicy: 'Data Policy',
};
}
/**
* Inject CSS styles for the demo interface
*/
injectCSS() {
if (document.getElementById('besper-demo-styles')) return;
const style = document.createElement('style');
style.id = 'besper-demo-styles';
style.textContent = `
/* Demo Interface Styles - Extracted from tobedeveloped_demo.html */
#${this.options.container} .besper-demo-wrapper {
width: 100%;
height: 900px;
max-height: 90vh;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Helvetica', 'Arial', sans-serif;
background: #ffffff;
overflow: hidden;
display: flex;
flex-direction: column;
position: relative;
border-radius: 16px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.06), 0 2px 8px rgba(0, 0, 0, 0.04);
}
#${this.options.container} {
width: 100%;
max-width: 100%;
margin: 0 auto;
}
#${this.options.container} .besper-demo-wrapper * {
margin: 0;
padding: 0;
box-sizing: border-box;
}
#${this.options.container} .besper-demo-header {
background: linear-gradient(135deg, #ffffff 0%, #f8fafe 100%);
padding: 20px 24px;
border-bottom: 1px solid rgba(88, 151, 222, 0.1);
text-align: center;
flex-shrink: 0;
position: relative;
overflow: hidden;
}
#${this.options.container} .besper-header-title {
font-size: 18px;
font-weight: 600;
color: #022d54;
margin-bottom: 4px;
position: relative;
display: inline-block;
}
#${this.options.container} .besper-header-subtitle {
font-size: 13px;
color: #6b7684;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
position: relative;
z-index: 1;
}
#${this.options.container} .besper-live-indicator {
display: inline-flex;
align-items: center;
gap: 6px;
background: rgba(16, 185, 129, 0.1);
padding: 4px 10px;
border-radius: 20px;
font-size: 11px;
font-weight: 600;
color: #10b981;
}
#${this.options.container} .besper-live-dot {
width: 6px;
height: 6px;
background: #10b981;
border-radius: 50%;
animation: pulse 2s ease-in-out infinite;
}
pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
#${this.options.container} .besper-demo-main {
flex: 1;
display: flex;
background: #fafbfc;
overflow: hidden;
min-height: 0;
}
#${this.options.container} .besper-chat-section {
background: #ffffff;
display: flex;
flex-direction: column;
border-right: 1px solid #e1e7ef;
min-height: 0;
flex: 1;
transition: flex 0.4s cubic-bezier(0.4, 0, 0.2, 1);
max-width: 50%;
overflow: hidden;
}
#${this.options.container} .besper-chat-header {
padding: 16px 24px;
border-bottom: 1px solid rgba(229, 231, 235, 0.8);
display: flex;
justify-content: space-between;
align-items: center;
flex-shrink: 0;
}
#${this.options.container} .besper-assistant-info {
display: flex;
align-items: center;
gap: 12px;
}
#${this.options.container} .besper-logo-avatar {
width: 40px;
height: 40px;
background-color: #003057;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
color: white;
font-size: 24px;
font-weight: bold;
font-family: Arial, sans-serif;
}
#${this.options.container} .besper-assistant-details h3 {
font-size: 14px;
font-weight: 600;
color: #1a1f2c;
margin-bottom: 2px;
}
#${this.options.container} .besper-assistant-status {
font-size: 12px;
transition: color 0.3s ease;
}
#${this.options.container} .besper-assistant-status.connecting {
color: #f59e0b;
}
#${this.options.container} .besper-assistant-status.online {
color: #10b981;
}
#${this.options.container} .besper-chat-actions {
display: flex;
gap: 8px;
}
#${this.options.container} .besper-icon-btn {
width: 36px;
height: 36px;
background: #ffffff;
border: 1px solid #e1e7ef;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
color: #6b7684;
transition: all 0.2s ease;
position: relative;
overflow: hidden;
}
#${this.options.container} .besper-icon-btn:hover {
background: #f0f4f8;
border-color: #5897de;
color: #5897de;
transform: translateY(-1px);
}
#${this.options.container} .besper-messages-container {
flex: 1;
padding: 20px;
overflow-y: auto;
overflow-x: hidden;
display: flex;
flex-direction: column;
gap: 16px;
min-height: 0;
scroll-behavior: smooth;
}
#${this.options.container} .besper-message {
display: flex;
gap: 12px;
align-items: flex-start;
animation: messageSlide 0.3s ease-out;
}
messageSlide {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
#${this.options.container} .besper-message.user {
flex-direction: row-reverse;
}
#${this.options.container} .besper-avatar {
width: 36px;
height: 36px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
flex-shrink: 0;
}
#${this.options.container} .besper-avatar.ai {
background-color: #003057;
color: white;
font-size: 20px;
font-weight: bold;
font-family: Arial, sans-serif;
}
#${this.options.container} .besper-avatar.user {
background: #f0f4f8;
color: #022d54;
}
#${this.options.container} .besper-message-bubble {
max-width: 70%;
padding: 12px 16px;
border-radius: 16px;
font-size: 14px;
line-height: 1.5;
word-wrap: break-word;
color: #1a1f2c;
overflow: hidden;
}
#${this.options.container} .besper-message.ai .besper-message-bubble {
background: #f0f4f8;
color: #1a1f2c;
border-bottom-left-radius: 4px;
}
#${this.options.container} .besper-message.user .besper-message-bubble {
background: linear-gradient(135deg, #5897de 0%, #4a85c9 100%);
color: white;
border-bottom-right-radius: 4px;
}
#${this.options.container} .besper-input-area {
padding: 16px 20px;
border-top: 1px solid rgba(229, 231, 235, 0.8);
background: #ffffff;
flex-shrink: 0;
}
#${this.options.container} .besper-input-wrapper {
display: flex;
gap: 12px;
align-items: center;
background: #ffffff;
border: 1px solid #e1e7ef;
border-radius: 24px;
padding: 4px;
transition: border-color 0.2s ease;
}
#${this.options.container} .besper-input-wrapper:focus-within {
border-color: #5897de;
box-shadow: 0 0 0 3px rgba(88, 151, 222, 0.1);
}
#${this.options.container} .besper-input-field {
flex: 1;
padding: 10px 16px;
border: none;
font-size: 14px;
outline: none;
background: transparent;
border-radius: 20px;
}
#${this.options.container} .besper-send-btn {
width: 36px;
height: 36px;
background: linear-gradient(135deg, #5897de 0%, #4a85c9 100%);
border: none;
border-radius: 18px;
color: white;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
}
#${this.options.container} .besper-send-btn:hover:not(:disabled) {
transform: scale(1.05);
box-shadow: 0 4px 12px rgba(88, 151, 222, 0.3);
}
#${this.options.container} .besper-send-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
#${this.options.container} .besper-knowledge-panel {
background: #ffffff;
display: flex;
flex-direction: column;
min-height: 0;
padding: 24px;
overflow-y: auto;
gap: 20px;
flex: 1;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
max-width: 50%;
}
#${this.options.container} .besper-connection-card {
background: #f8f9fb;
border-radius: 12px;
padding: 20px;
border: 1px solid #e1e7ef;
transition: all 0.2s ease;
}
#${this.options.container} .besper-url-input-wrapper {
display: flex;
gap: 8px;
align-items: center;
background: #ffffff;
border: 1px solid #e1e7ef;
border-radius: 24px;
padding: 4px;
transition: all 0.2s ease;
}
#${this.options.container} .besper-url-input {
flex: 1;
padding: 10px 16px;
border: none;
font-size: 13px;
outline: none;
background: transparent;
border-radius: 20px;
}
#${this.options.container} .besper-connect-btn {
padding: 8px 20px;
background: linear-gradient(135deg, #5897de 0%, #4a85c9 100%);
color: white;
border: none;
border-radius: 20px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
white-space: nowrap;
}
#${this.options.container} .besper-connect-btn:hover:not(:disabled) {
transform: scale(1.05);
box-shadow: 0 4px 12px rgba(88, 151, 222, 0.3);
}
#${this.options.container} .besper-connect-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
#${this.options.container} .besper-typing-indicator {
display: flex;
gap: 10px;
align-items: center;
}
#${this.options.container} .besper-typing-bubble {
background: #f0f4f8;
padding: 12px 16px;
border-radius: 16px;
border-bottom-left-radius: 4px;
display: flex;
gap: 4px;
}
#${this.options.container} .besper-typing-dot {
width: 6px;
height: 6px;
background: #9ca3af;
border-radius: 50%;
animation: typing 1.4s ease-in-out infinite;
}
#${this.options.container} .besper-typing-dot:nth-child(2) { animation-delay: 0.2s; }
#${this.options.container} .besper-typing-dot:nth-child(3) { animation-delay: 0.4s; }
typing {
0%, 60%, 100% { transform: translateY(0); opacity: 0.5; }
30% { transform: translateY(-10px); opacity: 1; }
}
/* Hide on mobile devices */
(max-width: 768px) {
#${this.options.container} {
display: none !important;
}
}
`;
document.head.appendChild(style);
}
/**
* Create the HTML structure for the demo interface
*/
createHTML() {
this.container.innerHTML = `
<div class="besper-demo-wrapper" id="besperDemoWrapper">
<div class="besper-demo-header">
<div class="besper-header-title">${this.translations.title}</div>
<div class="besper-header-subtitle">
${this.translations.subtitle}
<div class="besper-live-indicator">
<div class="besper-live-dot"></div>
Live Demo
</div>
</div>
</div>
<div class="besper-demo-main">
<div class="besper-chat-section">
<div class="besper-chat-header">
<div class="besper-assistant-info">
<div class="besper-logo-avatar">B</div>
<div class="besper-assistant-details">
<h3>B-esper Assistant</h3>
<div class="besper-assistant-status connecting" id="besperStatus">${this.translations.connecting}</div>
</div>
</div>
<div class="besper-chat-actions">
<button class="besper-icon-btn" id="besperDownloadBtn" title="Download conversation">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M7 10l5 5 5-5M12 15V3"/>
</svg>
</button>
<button class="besper-icon-btn" id="besperRestartBtn" title="Restart conversation">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M23 4v6h-6M1 20v-6h6M20.49 9A9 9 0 005.64 5.64L1 10m22 4a9 9 0 01-14.85 3.36L23 14"/>
</svg>
</button>
<button class="besper-icon-btn" id="besperDeleteBtn" title="Delete conversation">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M3 6h18M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6M8 6V4a2 2 0 012-2h4a2 2 0 012 2v2"/>
</svg>
</button>
</div>
</div>
<div class="besper-messages-container" id="besperMessages">
<!-- Messages will be added here -->
</div>
<div class="besper-input-area">
<div class="besper-input-wrapper">
<input type="text" class="besper-input-field" id="besperInput" placeholder="${this.translations.enterMessage}">
<button class="besper-send-btn" id="besperSendBtn">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="22" y1="2" x2="11" y2="13"></line>
<polygon points="22,2 15,22 11,13 2,9 22,2"></polygon>
</svg>
</button>
</div>
</div>
</div>
<div class="besper-knowledge-panel">
<div class="besper-connection-card" id="besperConnectionArea">
<h3 style="font-size: 16px; font-weight: 600; color: #1a1f2c; margin-bottom: 12px;">Connect Website</h3>
<p style="font-size: 13px; color: #6b7684; margin-bottom: 16px;">Connect a website to enable AI analysis and knowledge extraction</p>
<div class="besper-url-input-wrapper">
<input type="url" class="besper-url-input" id="besperUrlInput" placeholder="Enter website URL...">
<button class="besper-connect-btn" id="besperConnectBtn">${this.translations.connect}</button>
</div>
</div>
<div id="besperKnowledgeList" style="display: none;">
<!-- Knowledge content will be added here -->
</div>
</div>
</div>
</div>
`;
}
/**
* Get references to DOM elements
*/
getElements() {
this.els = {
status: document.getElementById('besperStatus'),
messages: document.getElementById('besperMessages'),
input: document.getElementById('besperInput'),
sendBtn: document.getElementById('besperSendBtn'),
downloadBtn: document.getElementById('besperDownloadBtn'),
restartBtn: document.getElementById('besperRestartBtn'),
deleteBtn: document.getElementById('besperDeleteBtn'),
connectionArea: document.getElementById('besperConnectionArea'),
urlInput: document.getElementById('besperUrlInput'),
connectBtn: document.getElementById('besperConnectBtn'),
knowledgeList: document.getElementById('besperKnowledgeList'),
};
}
/**
* Set up event listeners
*/
setupEventListeners() {
// Send message
this.els.sendBtn.addEventListener('click', () => this.sendMessage());
this.els.input.addEventListener('keypress', e => {
if (e.key === 'Enter') this.sendMessage();
});
// Connect website
this.els.connectBtn.addEventListener('click', () => this.connectWebsite());
// Download conversation
this.els.downloadBtn.addEventListener('click', () =>
this.downloadConversation()
);
// Restart conversation
this.els.restartBtn.addEventListener('click', () =>
this.restartConversation()
);
// Delete conversation
this.els.deleteBtn.addEventListener('click', () =>
this.deleteConversation()
);
}
/**
* Initialize the demo with API connection
*/
async initializeDemo() {
try {
// Generate a unique thread ID
this.state.threadId =
'demo-' + Date.now() + '-' + Math.random().toString(36).substr(2, 9);
// Update status to online
this.els.status.textContent = this.translations.online;
this.els.status.classList.remove('connecting');
this.els.status.classList.add('online');
console.log('[SUCCESS] Demo interface initialized successfully');
} catch (error) {
console.error('[ERROR] Failed to initialize demo:', error);
}
}
/**
* Add a message to the chat
*/
addMessage(content, sender = 'ai') {
const messageDiv = document.createElement('div');
messageDiv.className = `besper-message ${sender}`;
const messageId =
'msg-' + Date.now() + '-' + Math.random().toString(36).substr(2, 9);
messageDiv.id = `message-${messageId}`;
messageDiv.innerHTML = `
<div class="besper-avatar ${sender}">${sender === 'ai' ? 'B' : 'U'}</div>
<div class="besper-message-bubble">
${this.parseMarkdown(content)}
</div>
`;
this.els.messages.appendChild(messageDiv);
// Auto-scroll to bottom
setTimeout(() => {
this.els.messages.scrollTop = this.els.messages.scrollHeight;
}, 50);
}
/**
* Simple markdown parser
*/
parseMarkdown(text) {
if (!text) return '';
// Basic markdown parsing
return text
.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
.replace(/\*(.+?)\*/g, '<em>$1</em>')
.replace(/`(.+?)`/g, '<code>$1</code>')
.replace(/\n/g, '<br>');
}
/**
* Send a message
*/
async sendMessage() {
const message = this.els.input.value.trim();
if (!message || this.state.isProcessing) return;
this.addMessage(message, 'user');
this.els.input.value = '';
this.state.isProcessing = true;
this.els.input.disabled = true;
this.els.sendBtn.disabled = true;
this.showTypingIndicator();
try {
// Simple demo response - in real implementation this would call the API
await new Promise(resolve =>
setTimeout(resolve, 1000 + Math.random() * 2000)
);
const responses = [
"I'm a demo AI assistant. I can help you with various questions about your website and business.",
'This is a demonstration of the B-esper AI interface. You can connect a website to enable knowledge extraction.',
"I'm designed to provide intelligent responses based on your connected website content and general knowledge.",
'Feel free to ask me anything! In a real deployment, I would be connected to your specific knowledge base.',
'This demo showcases the chat interface. Connect a website above to see knowledge management in action.',
];
const response = responses[Math.floor(Math.random() * responses.length)];
this.removeTypingIndicator();
this.addMessage(response, 'ai');
} catch (error) {
this.removeTypingIndicator();
console.error('Send message error:', error);
this.addMessage(this.translations.issueError, 'ai');
} finally {
this.state.isProcessing = false;
this.els.input.disabled = false;
this.els.sendBtn.disabled = false;
this.els.input.focus();
}
}
/**
* Show typing indicator
*/
showTypingIndicator() {
const typingDiv = document.createElement('div');
typingDiv.className = 'besper-typing-indicator';
typingDiv.id = 'besperTypingIndicator';
typingDiv.innerHTML = `
<div class="besper-avatar ai">B</div>
<div class="besper-typing-bubble">
<div class="besper-typing-dot"></div>
<div class="besper-typing-dot"></div>
<div class="besper-typing-dot"></div>
</div>
`;
this.els.messages.appendChild(typingDiv);
setTimeout(() => {
this.els.messages.scrollTop = this.els.messages.scrollHeight;
}, 50);
}
/**
* Remove typing indicator
*/
removeTypingIndicator() {
const indicator = document.getElementById('besperTypingIndicator');
if (indicator) indicator.remove();
}
/**
* Connect website (demo version)
*/
async connectWebsite() {
const url = this.els.urlInput.value.trim();
if (!url) return;
try {
new URL(url);
} catch (e) {
alert(this.translations.enterValidUrl);
return;
}
this.els.connectBtn.disabled = true;
this.els.connectBtn.textContent = this.translations.connecting;
// Demo connection
setTimeout(() => {
this.addMessage(
`Connected to ${new URL(url).hostname}! This is a demo - in production, I would analyze the website content.`,
'ai'
);
this.els.connectionArea.style.display = 'none';
this.els.knowledgeList.style.display = 'block';
this.els.knowledgeList.innerHTML = `
<div style="padding: 20px; text-align: center; color: #6b7684;">
<h3 style="margin-bottom: 10px; color: #1a1f2c;">Demo Mode</h3>
<p>Website analysis would appear here in the full version.</p>
</div>
`;
this.els.connectBtn.disabled = false;
this.els.connectBtn.textContent = this.translations.connect;
}, 2000);
}
/**
* Download conversation
*/
downloadConversation() {
const messages = Array.from(this.els.messages.children)
.filter(el => el.classList.contains('besper-message'))
.map(el => {
const sender = el.classList.contains('user') ? 'User' : 'Assistant';
const content = el.querySelector('.besper-message-bubble').textContent;
return `${sender}: ${content}`;
})
.join('\n\n');
const blob = new Blob([messages], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `besper-demo-chat-${new Date().toISOString().split('T')[0]}.txt`;
a.click();
URL.revokeObjectURL(url);
}
/**
* Restart conversation
*/
restartConversation() {
this.els.messages.innerHTML = '';
this.state.messages = [];
this.addMessage(
"Hello! I'm your B-esper AI assistant. How can I help you today?",
'ai'
);
}
/**
* Delete conversation
*/
deleteConversation() {
this.els.messages.innerHTML = '';
this.state.messages = [];
}
/**
* Destroy the demo interface
*/
destroy() {
if (this.container) {
this.container.innerHTML = '';
}
// Remove CSS
const style = document.getElementById('besper-demo-styles');
if (style) {
style.remove();
}
console.log('[SUCCESS] Demo interface destroyed');
}
}