Improvements on the chat UI, prepare for supporting multiple models, and adding feedback to interactions.
This commit is contained in:
@@ -25,7 +25,7 @@ class Interaction(db.Model):
|
||||
answer = db.Column(db.Text, nullable=True)
|
||||
algorithm_used = db.Column(db.String(20), nullable=True)
|
||||
language = db.Column(db.String(2), nullable=False)
|
||||
appreciation = db.Column(db.Integer, nullable=True, default=100)
|
||||
appreciation = db.Column(db.Integer, nullable=True)
|
||||
|
||||
# Timing information
|
||||
question_at = db.Column(db.DateTime, nullable=False)
|
||||
|
||||
@@ -27,7 +27,7 @@ def create_app(config_file=None):
|
||||
init_celery(app.celery, app)
|
||||
|
||||
# Register Blueprints
|
||||
register_blueprints(app)
|
||||
# register_blueprints(app)
|
||||
|
||||
@app.route('/ping')
|
||||
def ping():
|
||||
|
||||
@@ -9,6 +9,7 @@ from common.extensions import socketio, kms_client, db
|
||||
from common.models.user import Tenant
|
||||
from common.models.interaction import Interaction
|
||||
from common.utils.celery_utils import current_celery
|
||||
from common.utils.database import Database
|
||||
|
||||
|
||||
@socketio.on('connect')
|
||||
@@ -35,7 +36,9 @@ def handle_connect():
|
||||
session['session_id'] = str(uuid.uuid4())
|
||||
|
||||
# Communicate connection to client
|
||||
current_app.logger.debug(f'SocketIO: Connection handling sending status to client for tenant {tenant_id}')
|
||||
emit('connect', {'status': 'Connected', 'tenant_id': tenant_id})
|
||||
current_app.logger.debug(f'SocketIO: Connection handling sending authentication token to client')
|
||||
emit('authenticated', {'token': token}) # Emit custom event with the token
|
||||
current_app.logger.debug(f'SocketIO: Connection handling sent token to client for tenant {tenant_id}')
|
||||
except Exception as e:
|
||||
@@ -55,25 +58,8 @@ def handle_message(data):
|
||||
try:
|
||||
current_app.logger.debug(f"SocketIO: Message handling received message from tenant {data['tenantId']}: "
|
||||
f"{data['message']} with token {data['token']}")
|
||||
token = data.get('token')
|
||||
if not token:
|
||||
raise Exception("Missing token")
|
||||
|
||||
# decoded_token = decode_token(token.split(" ")[1]) # remove "Bearer "
|
||||
decoded_token = decode_token(token)
|
||||
if not decoded_token:
|
||||
raise Exception("Invalid token")
|
||||
current_app.logger.debug(f"SocketIO: Message handling decoded token: {decoded_token}")
|
||||
|
||||
token_sub = decoded_token.get('sub')
|
||||
|
||||
current_tenant_id = token_sub.get('tenant_id')
|
||||
if not current_tenant_id:
|
||||
raise Exception("Missing tenant_id")
|
||||
|
||||
current_api_key = token_sub.get('api_key')
|
||||
if not current_api_key:
|
||||
raise Exception("Missing api_key")
|
||||
current_tenant_id = validate_incoming_data(data)
|
||||
|
||||
# Offload actual processing of question
|
||||
task = current_celery.send_task('ask_question', queue='llm_interactions', args=[
|
||||
@@ -128,19 +114,30 @@ def check_task_status(data):
|
||||
|
||||
@socketio.on('feedback')
|
||||
def handle_feedback(data):
|
||||
interaction_id = data.get('interaction_id')
|
||||
feedback = data.get('feedback') # 'up' or 'down'
|
||||
# Store feedback in the database associated with the interaction_id
|
||||
interaction = Interaction.query.get_or_404(interaction_id)
|
||||
interaction.feedback = 0 if feedback == 'down' else 1
|
||||
try:
|
||||
db.session.commit()
|
||||
emit('feedback_received', {'status': 'success', 'interaction_id': interaction_id})
|
||||
except SQLAlchemyError as e:
|
||||
current_app.logger.error(f'SocketIO: Feedback handling failed: {e}')
|
||||
db.session.rollback()
|
||||
emit('feedback_received', {'status': 'Could not register feedback', 'interaction_id': interaction_id})
|
||||
raise e
|
||||
current_app.logger.debug(f'SocketIO: Feedback handling received feedback with data: {data}')
|
||||
|
||||
current_tenant_id = validate_incoming_data(data)
|
||||
|
||||
interaction_id = data.get('interactionId')
|
||||
feedback = data.get('feedback') # 'up' or 'down'
|
||||
|
||||
Database(current_tenant_id).switch_schema()
|
||||
|
||||
interaction = Interaction.query.get_or_404(interaction_id)
|
||||
current_app.logger.debug(f'Processing feedback for interaction: {interaction}')
|
||||
interaction.appreciation = 0 if feedback == 'down' else 100
|
||||
try:
|
||||
db.session.commit()
|
||||
emit('feedback_received', {'status': 'success', 'interaction_id': interaction_id})
|
||||
except SQLAlchemyError as e:
|
||||
current_app.logger.error(f'SocketIO: Feedback handling failed: {e}')
|
||||
db.session.rollback()
|
||||
emit('feedback_received', {'status': 'Could not register feedback', 'interaction_id': interaction_id})
|
||||
raise e
|
||||
except Exception as e:
|
||||
current_app.logger.debug(f'SocketIO: Feedback handling failed: {e}')
|
||||
disconnect()
|
||||
|
||||
|
||||
def validate_api_key(tenant_id, api_key):
|
||||
@@ -148,3 +145,29 @@ def validate_api_key(tenant_id, api_key):
|
||||
decrypted_api_key = kms_client.decrypt_api_key(tenant.encrypted_chat_api_key)
|
||||
|
||||
return decrypted_api_key == api_key
|
||||
|
||||
|
||||
def validate_incoming_data(data):
|
||||
token = data.get('token')
|
||||
if not token:
|
||||
raise Exception("Missing token")
|
||||
|
||||
decoded_token = decode_token(token)
|
||||
if not decoded_token:
|
||||
raise Exception("Invalid token")
|
||||
|
||||
token_sub = decoded_token.get('sub')
|
||||
|
||||
if not token_sub:
|
||||
raise Exception("Missing token subject")
|
||||
tenant_id = token_sub.get('tenant_id')
|
||||
|
||||
current_tenant_id = token_sub.get('tenant_id')
|
||||
if not current_tenant_id:
|
||||
raise Exception("Missing tenant_id")
|
||||
|
||||
current_api_key = token_sub.get('api_key')
|
||||
if not current_api_key:
|
||||
raise Exception("Missing api_key")
|
||||
|
||||
return current_tenant_id
|
||||
|
||||
@@ -84,6 +84,7 @@ def ask_question(tenant_id, question, language, session_id):
|
||||
new_interaction = Interaction()
|
||||
new_interaction.question = question
|
||||
new_interaction.language = language
|
||||
new_interaction.appreciation = None
|
||||
new_interaction.chat_session_id = chat_session.id
|
||||
new_interaction.question_at = dt.now(tz.utc)
|
||||
new_interaction.algorithm_used = current_app.config['INTERACTION_ALGORITHMS']['RAG_TENANT']['name']
|
||||
|
||||
@@ -4,12 +4,12 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Chat Client AE</title>
|
||||
<link rel="stylesheet" href="/static/css/eveai-chat-style.css">
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
<script src="https://cdn.socket.io/4.0.1/socket.io.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||||
<script src="/static/js/eveai-sdk.js" defer></script>
|
||||
<script src="/static/js/eveai-chat-widget.js" defer></script>
|
||||
<link rel="stylesheet" href="/static/css/eveai-chat-style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="chat-container"></div>
|
||||
|
||||
@@ -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 */
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
|
||||
Reference in New Issue
Block a user