Improvements on the chat UI, prepare for supporting multiple models, and adding feedback to interactions.

This commit is contained in:
Josako
2024-06-14 15:05:46 +02:00
parent b77e1ab321
commit c5370c8026
8 changed files with 301 additions and 95 deletions

View File

@@ -1,17 +1,74 @@
/* eveai_chat.css */
:root {
--user-message-bg: #d1e7dd; /* Default user message background color */
--bot-message-bg: #ffffff; /* Default bot message background color */
--chat-bg: #f8f9fa; /* Default chat background color */
--algorithm-color-default: #ccc; /* Default algorithm indicator color */
--algorithm-color-alg1: #f00; /* Algorithm 1 color */
--algorithm-color-alg2: #0f0; /* Algorithm 2 color */
--algorithm-color-alg3: #00f; /* Algorithm 3 color */
--status-line-color: #6c757d; /* Color for the status line text */
--status-line-bg: #e9ecef; /* Background color for the status line */
--user-message-bg: #292929; /* Default user message background color */
--bot-message-bg: #1e1e1e; /* Default bot message background color */
--chat-bg: #1e1e1e; /* Default chat background color */
--status-line-color: #e9e9e9; /* Color for the status line text */
--status-line-bg: #1e1e1e; /* Background color for the status line */
--status-line-height: 30px; /* Fixed height for the status line */
--algorithm-color-rag-tenant: #0f0; /* Green for RAG_TENANT */
--algorithm-color-rag-wikipedia: #00f; /* Blue for RAG_WIKIPEDIA */
--algorithm-color-rag-google: #ff0; /* Yellow for RAG_GOOGLE */
--algorithm-color-rag-llm: #800080; /* Purple for RAG_LLM */
/*--font-family: 'Arial, sans-serif'; !* Default font family *!*/
--font-family: 'ui-sans-serif, -apple-system, system-ui, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans, sans-serif, Helvetica, Apple Color Emoji, Arial, Segoe UI Emoji, Segoe UI Symbol';
--font-color: #e9e9e9; /* Default font color */
--user-message-font-color: #e9e9e9; /* User message font color */
--bot-message-font-color: #e9e9e9; /* Bot message font color */
--input-bg: #292929; /* Input background color */
--input-border: #ccc; /* Input border color */
--input-text-color: #e9e9e9; /* Input text color */
--button-color: #007bff; /* Button text color */
/* Variables for hyperlink backgrounds */
--link-bg: #1e1e1e; /* Default background color for hyperlinks */
--link-hover-bg: #1e1e1e; /* Background color on hover for hyperlinks */
--link-color: #dec981; /* Default text color for hyperlinks */
--link-hover-color: #D68F53; /* Text color on hover for hyperlinks */
/* New scrollbar variables */
--scrollbar-bg: #292929; /* Background color for the scrollbar track */
--scrollbar-thumb: #4b4b4b; /* Color for the scrollbar thumb */
--scrollbar-thumb-hover: #dec981; /* Color for the thumb on hover */
--scrollbar-thumb-active: #D68F53; /* Color for the thumb when active (dragged) */
/* Thumb colors */
--thumb-icon-outlined: #4b4b4b;
--thumb-icon-filled: #e9e9e9;
}
/* Custom scrollbar styles */
.messages-area::-webkit-scrollbar {
width: 12px; /* Width of the scrollbar */
}
.messages-area::-webkit-scrollbar-track {
background: var(--scrollbar-bg); /* Background color for the track */
}
.messages-area::-webkit-scrollbar-thumb {
background-color: var(--scrollbar-thumb); /* Color of the thumb */
border-radius: 10px; /* Rounded corners for the thumb */
border: 3px solid var(--scrollbar-bg); /* Space around the thumb */
}
.messages-area::-webkit-scrollbar-thumb:hover {
background-color: var(--scrollbar-thumb-hover); /* Color when hovering over the thumb */
}
.messages-area::-webkit-scrollbar-thumb:active {
background-color: var(--scrollbar-thumb-active); /* Color when active (dragging) */
}
/* For Firefox */
.messages-area {
scrollbar-width: thin; /* Make scrollbar thinner */
scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-bg); /* Thumb and track colors */
}
/* General Styles */
.chat-container {
display: flex;
flex-direction: column;
@@ -23,6 +80,8 @@
border-radius: 8px;
overflow: hidden;
background-color: var(--chat-bg);
font-family: var(--font-family); /* Apply the default font family */
color: var(--font-color); /* Apply the default font color */
}
.messages-area {
@@ -42,10 +101,12 @@
.message.user {
margin-left: auto;
background-color: var(--user-message-bg);
color: var(--user-message-font-color); /* Apply user message font color */
}
.message.bot {
background-color: var(--bot-message-bg);
color: var(--bot-message-font-color); /* Apply bot message font color */
}
.message-icons {
@@ -53,8 +114,19 @@
align-items: center;
}
.message-icons i {
margin-left: 5px;
/* Scoped styles for thumb icons */
.thumb-icon.outlined {
color: var(--thumb-icon-outlined); /* Color for outlined state */
}
.thumb-icon.filled {
color: var(--thumb-icon-filled); /* Color for filled state */
}
/* Default styles for material icons */
.material-icons {
font-size: 24px;
vertical-align: middle;
cursor: pointer;
}
@@ -71,13 +143,27 @@
padding: 10px;
border-radius: 15px;
margin-right: 10px;
background-color: var(--input-bg); /* Apply input background color */
border: 1px solid var(--input-border); /* Apply input border color */
color: var(--input-text-color); /* Apply input text color */
}
.question-area button {
background: none;
border: none;
cursor: pointer;
color: #007bff;
color: var(--button-color);
}
/* Styles for the send icon */
.send-icon {
font-size: 24px; /* Size of the icon */
color: var(--button-color); /* Color of the icon */
}
.send-icon.disabled {
color: grey; /* Color for the disabled state */
cursor: not-allowed; /* Change cursor to indicate disabled state */
}
/* New CSS for the status-line */
@@ -94,6 +180,38 @@
justify-content: center;
}
/* Algorithm-specific colors for fingerprint icon */
.fingerprint-rag-tenant {
color: var(--algorithm-color-rag-tenant);
}
.fingerprint-rag-wikipedia {
color: var(--algorithm-color-rag-wikipedia);
}
.fingerprint-rag-google {
color: var(--algorithm-color-rag-google);
}
.fingerprint-rag-llm {
color: var(--algorithm-color-rag-llm);
}
/* Styling for citation links */
.citations a {
background-color: var(--link-bg); /* Apply default background color */
color: var(--link-color); /* Apply default link color */
padding: 2px 4px; /* Add padding for better appearance */
border-radius: 3px; /* Add slight rounding for a modern look */
text-decoration: none; /* Remove default underline */
transition: background-color 0.3s, color 0.3s; /* Smooth transition for hover effects */
}
.citations a:hover {
background-color: var(--link-hover-bg); /* Background color on hover */
color: var(--link-hover-color); /* Text color on hover */
}
/* Media queries for responsiveness */
@media (max-width: 768px) {
.chat-container {
@@ -114,3 +232,6 @@
font-size: 0.8rem; /* Adjust status line font size for smaller screens */
}
}

View File

@@ -16,9 +16,10 @@ class EveAIChatWidget extends HTMLElement {
this.innerHTML = this.getTemplate();
this.messagesArea = this.querySelector('.messages-area');
this.questionInput = this.querySelector('.question-area input');
this.sendButton = this.querySelector('.send-icon');
this.statusLine = this.querySelector('.status-line');
this.querySelector('.question-area button').addEventListener('click', () => this.handleSendMessage());
this.sendButton.addEventListener('click', () => this.handleSendMessage());
this.questionInput.addEventListener('keydown', (event) => {
if (event.key === 'Enter') {
this.handleSendMessage();
@@ -34,12 +35,6 @@ class EveAIChatWidget extends HTMLElement {
attributeChangedCallback(name, oldValue, newValue) {
console.log(`attributeChangedCallback called: ${name} changed from ${oldValue} to ${newValue}`);
this.updateAttributes();
console.log('Current attributes:', {
tenantId: this.getAttribute('tenant-id'),
apiKey: this.getAttribute('api-key'),
domain: this.getAttribute('domain'),
language: this.getAttribute('language')
});
if (this.areAllAttributesSet() && !this.socket) {
console.log('All attributes set in attributeChangedCallback, initializing socket');
@@ -49,7 +44,6 @@ class EveAIChatWidget extends HTMLElement {
}
updateAttributes() {
console.log('Updating attributes:');
this.tenantId = this.getAttribute('tenant-id');
this.apiKey = this.getAttribute('api-key');
this.domain = this.getAttribute('domain');
@@ -73,7 +67,7 @@ class EveAIChatWidget extends HTMLElement {
domain,
language
});
return tenantId && apiKey && domain;
return tenantId && apiKey && domain && language;
}
initializeSocket() {
@@ -83,7 +77,7 @@ class EveAIChatWidget extends HTMLElement {
}
if (!this.domain || this.domain === 'null') {
console.error('Domain attribute is missing or invalid');
this.setStatusMessage('EveAI Chat Widget needs further configuration by site administrator.')
this.setStatusMessage('EveAI Chat Widget needs further configuration by site administrator.');
return;
}
console.log(`Initializing socket connection to ${this.domain}`);
@@ -101,14 +95,16 @@ class EveAIChatWidget extends HTMLElement {
}
});
console.log(`Finished initializing socket connection to ${this.domain}`);
this.socket.on('connect', (data) => {
console.log('Socket connected');
this.setStatusMessage('Connected to EveAI.')
console.log('Socket connected OK');
this.setStatusMessage('Connected to EveAI.');
});
this.socket.on('authenticated', (data) => {
console.log('Authenticated event received: ', data);
this.setStatusMessage('Authenticated.')
this.setStatusMessage('Authenticated.');
if (data.token) {
this.jwtToken = data.token; // Store the JWT token received from the server
}
@@ -116,31 +112,31 @@ class EveAIChatWidget extends HTMLElement {
this.socket.on('connect_error', (err) => {
console.error('Socket connection error:', err);
this.setStatusMessage('EveAI Chat Widget needs further configuration by site administrator.')
this.setStatusMessage('EveAI Chat Widget needs further configuration by site administrator.');
});
this.socket.on('connect_timeout', () => {
console.error('Socket connection timeout');
this.setStatusMessage('EveAI Chat Widget needs further configuration by site administrator.')
this.setStatusMessage('EveAI Chat Widget needs further configuration by site administrator.');
});
this.socket.on('disconnect', () => {
console.log('Socket disconnected');
this.setStatusMessage('Disconnected from EveAI. Please refresh the page for further interaction.')
this.setStatusMessage('Disconnected from EveAI. Please refresh the page for further interaction.');
});
this.socket.on('bot_response', (data) => {
if (data.tenantId === this.tenantId) {
console.log('Initial response received:', data)
console.log('Task ID received:', data.taskId)
this.checkTaskStatus(data.taskId)
this.setStatusMessage('Processing...')
console.log('Initial response received:', data);
console.log('Task ID received:', data.taskId);
this.checkTaskStatus(data.taskId);
this.setStatusMessage('Processing...');
}
});
this.socket.on('task_status', (data) => {
console.log('Task status received:', data.status)
console.log('Task ID received:', data.taskId)
console.log('Task status received:', data.status);
console.log('Task ID received:', data.taskId);
console.log('Citations type:', typeof data.citations, 'Citations:', data.citations);
if (data.status === 'pending') {
@@ -169,6 +165,7 @@ class EveAIChatWidget extends HTMLElement {
clearProgress() {
this.statusLine.textContent = '';
this.toggleSendButton(false); // Re-enable and revert send button to outlined version
}
checkTaskStatus(taskId) {
@@ -182,7 +179,7 @@ class EveAIChatWidget extends HTMLElement {
<div class="messages-area"></div>
<div class="question-area">
<input type="text" placeholder="Type your message here..." />
<button>Send</button>
<i class="material-icons send-icon outlined">send</i>
</div>
<div class="status-line"></div>
</div>
@@ -197,31 +194,89 @@ class EveAIChatWidget extends HTMLElement {
this.messagesArea.scrollTop = this.messagesArea.scrollHeight;
}
handleFeedback(feedback, interactionId) {
// Send feedback to the backend
console.log('handleFeedback called');
if (!this.socket) {
console.error('Socket is not initialized');
return;
}
if (!this.jwtToken) {
console.error('JWT token is not available');
return;
}
console.log('Sending message to backend');
console.log(`Feedback for ${interactionId}: ${feedback}`);
this.socket.emit('feedback', { tenantId: this.tenantId, token: this.jwtToken, feedback, interactionId });
this.setStatusMessage('Feedback sent.');
}
addBotMessage(text, interactionId, algorithm = 'default', citations = []) {
const message = document.createElement('div');
message.classList.add('message', 'bot');
let content = marked.parse(text); // Use marked to convert markdown to HTML
// Ensure citations is an array
if (!Array.isArray(citations)) {
console.error('Expected citations to be an array, but got:', citations);
citations = []; // Default to an empty array
}
let content = marked.parse(text);
let citationsHtml = citations.map(url => `<a href="${url}" target="_blank">${url}</a>`).join('<br>');
message.innerHTML = `
<p>${content}</p>
${citationsHtml ? `<p>${citationsHtml}</p>` : ''}
<div class="message-icons">
<span class="algorithm-indicator" style="background-color: var(--algorithm-color-${algorithm});">${algorithm}</span>
<i class="material-icons" onclick="handleFeedback('up', '${interactionId}')">thumb_up</i>
<i class="material-icons" onclick="handleFeedback('down', '${interactionId}')">thumb_down</i>
</div>
`;
this.messagesArea.appendChild(message);
let algorithmClass;
switch (algorithm) {
case 'RAG_TENANT':
algorithmClass = 'fingerprint-rag-tenant';
break;
case 'RAG_WIKIPEDIA':
algorithmClass = 'fingerprint-rag-wikipedia';
break;
case 'RAG_GOOGLE':
algorithmClass = 'fingerprint-rag-google';
break;
case 'RAG_LLM':
algorithmClass = 'fingerprint-rag-llm';
break;
default:
algorithmClass = '';
}
message.innerHTML = `
<p>${content}</p>
${citationsHtml ? `<p class="citations">${citationsHtml}</p>` : ''}
<div class="message-icons">
<i class="material-icons ${algorithmClass}">fingerprint</i>
<i class="material-icons thumb-icon outlined" data-feedback="up" data-interaction-id="${interactionId}">thumb_up_off_alt</i>
<i class="material-icons thumb-icon outlined" data-feedback="down" data-interaction-id="${interactionId}">thumb_down_off_alt</i>
</div>
`;
this.messagesArea.appendChild(message);
// Add event listeners for feedback buttons
const thumbsUp = message.querySelector('i[data-feedback="up"]');
const thumbsDown = message.querySelector('i[data-feedback="down"]');
thumbsUp.addEventListener('click', () => this.toggleFeedback(thumbsUp, thumbsDown, 'up', interactionId));
thumbsDown.addEventListener('click', () => this.toggleFeedback(thumbsUp, thumbsDown, 'down', interactionId));
this.messagesArea.scrollTop = this.messagesArea.scrollHeight;
}
toggleFeedback(thumbsUp, thumbsDown, feedback, interactionId) {
if (feedback === 'up') {
thumbsUp.textContent = 'thumb_up'; // Change to filled icon
thumbsUp.classList.remove('outlined');
thumbsUp.classList.add('filled');
thumbsDown.textContent = 'thumb_down_off_alt'; // Keep the other icon outlined
thumbsDown.classList.add('outlined');
thumbsDown.classList.remove('filled');
} else {
thumbsDown.textContent = 'thumb_down'; // Change to filled icon
thumbsDown.classList.remove('outlined');
thumbsDown.classList.add('filled');
thumbsUp.textContent = 'thumb_up_off_alt'; // Keep the other icon outlined
thumbsUp.classList.add('outlined');
thumbsUp.classList.remove('filled');
}
// Send feedback to the backend
this.handleFeedback(feedback, interactionId);
}
handleSendMessage() {
console.log('handleSendMessage called');
const message = this.questionInput.value.trim();
@@ -229,6 +284,7 @@ class EveAIChatWidget extends HTMLElement {
this.addUserMessage(message);
this.questionInput.value = '';
this.sendMessageToBackend(message);
this.toggleSendButton(true); // Disable and change send button to filled version
}
}
@@ -246,12 +302,23 @@ class EveAIChatWidget extends HTMLElement {
this.socket.emit('user_message', { tenantId: this.tenantId, token: this.jwtToken, message, language: this.language });
this.setStatusMessage('Processing started ...')
}
toggleSendButton(isProcessing) {
if (isProcessing) {
this.sendButton.textContent = 'send'; // Filled send icon
this.sendButton.classList.remove('outlined');
this.sendButton.classList.add('filled');
this.sendButton.classList.add('disabled'); // Add disabled class for styling
this.sendButton.style.pointerEvents = 'none'; // Disable click events
} else {
this.sendButton.textContent = 'send'; // Outlined send icon
this.sendButton.classList.add('outlined');
this.sendButton.classList.remove('filled');
this.sendButton.classList.remove('disabled'); // Remove disabled class
this.sendButton.style.pointerEvents = 'auto'; // Re-enable click events
}
}
}
customElements.define('eveai-chat-widget', EveAIChatWidget);
function handleFeedback(feedback, interactionId) {
// Send feedback to the backend
console.log(`Feedback for ${interactionId}: ${feedback}`);
this.socket.emit('feedback', { feedback, interaction_id: interactionId });
}

View File

@@ -19,12 +19,6 @@ class EveAI {
chatWidget.setAttribute('api-key', this.apiKey);
chatWidget.setAttribute('domain', this.domain);
chatWidget.setAttribute('language', this.language);
console.log('Attributes set in chat widget:', {
tenantId: chatWidget.getAttribute('tenant-id'),
apiKey: chatWidget.getAttribute('api-key'),
domain: chatWidget.getAttribute('domain'),
language: chatWidget.getAttribute('language')
});
});
} else {
console.error('Container not found');