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)
|
answer = db.Column(db.Text, nullable=True)
|
||||||
algorithm_used = db.Column(db.String(20), nullable=True)
|
algorithm_used = db.Column(db.String(20), nullable=True)
|
||||||
language = db.Column(db.String(2), nullable=False)
|
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
|
# Timing information
|
||||||
question_at = db.Column(db.DateTime, nullable=False)
|
question_at = db.Column(db.DateTime, nullable=False)
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ def create_app(config_file=None):
|
|||||||
init_celery(app.celery, app)
|
init_celery(app.celery, app)
|
||||||
|
|
||||||
# Register Blueprints
|
# Register Blueprints
|
||||||
register_blueprints(app)
|
# register_blueprints(app)
|
||||||
|
|
||||||
@app.route('/ping')
|
@app.route('/ping')
|
||||||
def ping():
|
def ping():
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ from common.extensions import socketio, kms_client, db
|
|||||||
from common.models.user import Tenant
|
from common.models.user import Tenant
|
||||||
from common.models.interaction import Interaction
|
from common.models.interaction import Interaction
|
||||||
from common.utils.celery_utils import current_celery
|
from common.utils.celery_utils import current_celery
|
||||||
|
from common.utils.database import Database
|
||||||
|
|
||||||
|
|
||||||
@socketio.on('connect')
|
@socketio.on('connect')
|
||||||
@@ -35,7 +36,9 @@ def handle_connect():
|
|||||||
session['session_id'] = str(uuid.uuid4())
|
session['session_id'] = str(uuid.uuid4())
|
||||||
|
|
||||||
# Communicate connection to client
|
# 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})
|
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
|
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}')
|
current_app.logger.debug(f'SocketIO: Connection handling sent token to client for tenant {tenant_id}')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -55,25 +58,8 @@ def handle_message(data):
|
|||||||
try:
|
try:
|
||||||
current_app.logger.debug(f"SocketIO: Message handling received message from tenant {data['tenantId']}: "
|
current_app.logger.debug(f"SocketIO: Message handling received message from tenant {data['tenantId']}: "
|
||||||
f"{data['message']} with token {data['token']}")
|
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 "
|
current_tenant_id = validate_incoming_data(data)
|
||||||
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")
|
|
||||||
|
|
||||||
# Offload actual processing of question
|
# Offload actual processing of question
|
||||||
task = current_celery.send_task('ask_question', queue='llm_interactions', args=[
|
task = current_celery.send_task('ask_question', queue='llm_interactions', args=[
|
||||||
@@ -128,19 +114,30 @@ def check_task_status(data):
|
|||||||
|
|
||||||
@socketio.on('feedback')
|
@socketio.on('feedback')
|
||||||
def handle_feedback(data):
|
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:
|
try:
|
||||||
db.session.commit()
|
current_app.logger.debug(f'SocketIO: Feedback handling received feedback with data: {data}')
|
||||||
emit('feedback_received', {'status': 'success', 'interaction_id': interaction_id})
|
|
||||||
except SQLAlchemyError as e:
|
current_tenant_id = validate_incoming_data(data)
|
||||||
current_app.logger.error(f'SocketIO: Feedback handling failed: {e}')
|
|
||||||
db.session.rollback()
|
interaction_id = data.get('interactionId')
|
||||||
emit('feedback_received', {'status': 'Could not register feedback', 'interaction_id': interaction_id})
|
feedback = data.get('feedback') # 'up' or 'down'
|
||||||
raise e
|
|
||||||
|
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):
|
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)
|
decrypted_api_key = kms_client.decrypt_api_key(tenant.encrypted_chat_api_key)
|
||||||
|
|
||||||
return decrypted_api_key == 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 = Interaction()
|
||||||
new_interaction.question = question
|
new_interaction.question = question
|
||||||
new_interaction.language = language
|
new_interaction.language = language
|
||||||
|
new_interaction.appreciation = None
|
||||||
new_interaction.chat_session_id = chat_session.id
|
new_interaction.chat_session_id = chat_session.id
|
||||||
new_interaction.question_at = dt.now(tz.utc)
|
new_interaction.question_at = dt.now(tz.utc)
|
||||||
new_interaction.algorithm_used = current_app.config['INTERACTION_ALGORITHMS']['RAG_TENANT']['name']
|
new_interaction.algorithm_used = current_app.config['INTERACTION_ALGORITHMS']['RAG_TENANT']['name']
|
||||||
|
|||||||
@@ -4,12 +4,12 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Chat Client AE</title>
|
<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">
|
<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.socket.io/4.0.1/socket.io.min.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.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-sdk.js" defer></script>
|
||||||
<script src="/static/js/eveai-chat-widget.js" defer></script>
|
<script src="/static/js/eveai-chat-widget.js" defer></script>
|
||||||
|
<link rel="stylesheet" href="/static/css/eveai-chat-style.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="chat-container"></div>
|
<div id="chat-container"></div>
|
||||||
|
|||||||
@@ -1,17 +1,74 @@
|
|||||||
/* eveai_chat.css */
|
/* eveai_chat.css */
|
||||||
:root {
|
:root {
|
||||||
--user-message-bg: #d1e7dd; /* Default user message background color */
|
--user-message-bg: #292929; /* Default user message background color */
|
||||||
--bot-message-bg: #ffffff; /* Default bot message background color */
|
--bot-message-bg: #1e1e1e; /* Default bot message background color */
|
||||||
--chat-bg: #f8f9fa; /* Default chat background color */
|
--chat-bg: #1e1e1e; /* Default chat background color */
|
||||||
--algorithm-color-default: #ccc; /* Default algorithm indicator color */
|
--status-line-color: #e9e9e9; /* Color for the status line text */
|
||||||
--algorithm-color-alg1: #f00; /* Algorithm 1 color */
|
--status-line-bg: #1e1e1e; /* Background color for the status line */
|
||||||
--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 */
|
|
||||||
--status-line-height: 30px; /* Fixed height 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 {
|
.chat-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -23,6 +80,8 @@
|
|||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background-color: var(--chat-bg);
|
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 {
|
.messages-area {
|
||||||
@@ -42,10 +101,12 @@
|
|||||||
.message.user {
|
.message.user {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
background-color: var(--user-message-bg);
|
background-color: var(--user-message-bg);
|
||||||
|
color: var(--user-message-font-color); /* Apply user message font color */
|
||||||
}
|
}
|
||||||
|
|
||||||
.message.bot {
|
.message.bot {
|
||||||
background-color: var(--bot-message-bg);
|
background-color: var(--bot-message-bg);
|
||||||
|
color: var(--bot-message-font-color); /* Apply bot message font color */
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-icons {
|
.message-icons {
|
||||||
@@ -53,8 +114,19 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-icons i {
|
/* Scoped styles for thumb icons */
|
||||||
margin-left: 5px;
|
.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;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,13 +143,27 @@
|
|||||||
padding: 10px;
|
padding: 10px;
|
||||||
border-radius: 15px;
|
border-radius: 15px;
|
||||||
margin-right: 10px;
|
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 {
|
.question-area button {
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
cursor: pointer;
|
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 */
|
/* New CSS for the status-line */
|
||||||
@@ -94,6 +180,38 @@
|
|||||||
justify-content: center;
|
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 queries for responsiveness */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.chat-container {
|
.chat-container {
|
||||||
@@ -114,3 +232,6 @@
|
|||||||
font-size: 0.8rem; /* Adjust status line font size for smaller screens */
|
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.innerHTML = this.getTemplate();
|
||||||
this.messagesArea = this.querySelector('.messages-area');
|
this.messagesArea = this.querySelector('.messages-area');
|
||||||
this.questionInput = this.querySelector('.question-area input');
|
this.questionInput = this.querySelector('.question-area input');
|
||||||
|
this.sendButton = this.querySelector('.send-icon');
|
||||||
this.statusLine = this.querySelector('.status-line');
|
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) => {
|
this.questionInput.addEventListener('keydown', (event) => {
|
||||||
if (event.key === 'Enter') {
|
if (event.key === 'Enter') {
|
||||||
this.handleSendMessage();
|
this.handleSendMessage();
|
||||||
@@ -34,12 +35,6 @@ class EveAIChatWidget extends HTMLElement {
|
|||||||
attributeChangedCallback(name, oldValue, newValue) {
|
attributeChangedCallback(name, oldValue, newValue) {
|
||||||
console.log(`attributeChangedCallback called: ${name} changed from ${oldValue} to ${newValue}`);
|
console.log(`attributeChangedCallback called: ${name} changed from ${oldValue} to ${newValue}`);
|
||||||
this.updateAttributes();
|
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) {
|
if (this.areAllAttributesSet() && !this.socket) {
|
||||||
console.log('All attributes set in attributeChangedCallback, initializing socket');
|
console.log('All attributes set in attributeChangedCallback, initializing socket');
|
||||||
@@ -49,7 +44,6 @@ class EveAIChatWidget extends HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateAttributes() {
|
updateAttributes() {
|
||||||
console.log('Updating attributes:');
|
|
||||||
this.tenantId = this.getAttribute('tenant-id');
|
this.tenantId = this.getAttribute('tenant-id');
|
||||||
this.apiKey = this.getAttribute('api-key');
|
this.apiKey = this.getAttribute('api-key');
|
||||||
this.domain = this.getAttribute('domain');
|
this.domain = this.getAttribute('domain');
|
||||||
@@ -73,7 +67,7 @@ class EveAIChatWidget extends HTMLElement {
|
|||||||
domain,
|
domain,
|
||||||
language
|
language
|
||||||
});
|
});
|
||||||
return tenantId && apiKey && domain;
|
return tenantId && apiKey && domain && language;
|
||||||
}
|
}
|
||||||
|
|
||||||
initializeSocket() {
|
initializeSocket() {
|
||||||
@@ -83,7 +77,7 @@ class EveAIChatWidget extends HTMLElement {
|
|||||||
}
|
}
|
||||||
if (!this.domain || this.domain === 'null') {
|
if (!this.domain || this.domain === 'null') {
|
||||||
console.error('Domain attribute is missing or invalid');
|
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;
|
return;
|
||||||
}
|
}
|
||||||
console.log(`Initializing socket connection to ${this.domain}`);
|
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) => {
|
this.socket.on('connect', (data) => {
|
||||||
console.log('Socket connected');
|
console.log('Socket connected OK');
|
||||||
this.setStatusMessage('Connected to EveAI.')
|
this.setStatusMessage('Connected to EveAI.');
|
||||||
});
|
});
|
||||||
|
|
||||||
this.socket.on('authenticated', (data) => {
|
this.socket.on('authenticated', (data) => {
|
||||||
console.log('Authenticated event received: ', data);
|
console.log('Authenticated event received: ', data);
|
||||||
this.setStatusMessage('Authenticated.')
|
this.setStatusMessage('Authenticated.');
|
||||||
if (data.token) {
|
if (data.token) {
|
||||||
this.jwtToken = data.token; // Store the JWT token received from the server
|
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) => {
|
this.socket.on('connect_error', (err) => {
|
||||||
console.error('Socket connection 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', () => {
|
this.socket.on('connect_timeout', () => {
|
||||||
console.error('Socket connection 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', () => {
|
this.socket.on('disconnect', () => {
|
||||||
console.log('Socket disconnected');
|
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) => {
|
this.socket.on('bot_response', (data) => {
|
||||||
if (data.tenantId === this.tenantId) {
|
if (data.tenantId === this.tenantId) {
|
||||||
console.log('Initial response received:', data)
|
console.log('Initial response received:', data);
|
||||||
console.log('Task ID received:', data.taskId)
|
console.log('Task ID received:', data.taskId);
|
||||||
this.checkTaskStatus(data.taskId)
|
this.checkTaskStatus(data.taskId);
|
||||||
this.setStatusMessage('Processing...')
|
this.setStatusMessage('Processing...');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.socket.on('task_status', (data) => {
|
this.socket.on('task_status', (data) => {
|
||||||
console.log('Task status received:', data.status)
|
console.log('Task status received:', data.status);
|
||||||
console.log('Task ID received:', data.taskId)
|
console.log('Task ID received:', data.taskId);
|
||||||
console.log('Citations type:', typeof data.citations, 'Citations:', data.citations);
|
console.log('Citations type:', typeof data.citations, 'Citations:', data.citations);
|
||||||
|
|
||||||
if (data.status === 'pending') {
|
if (data.status === 'pending') {
|
||||||
@@ -169,6 +165,7 @@ class EveAIChatWidget extends HTMLElement {
|
|||||||
|
|
||||||
clearProgress() {
|
clearProgress() {
|
||||||
this.statusLine.textContent = '';
|
this.statusLine.textContent = '';
|
||||||
|
this.toggleSendButton(false); // Re-enable and revert send button to outlined version
|
||||||
}
|
}
|
||||||
|
|
||||||
checkTaskStatus(taskId) {
|
checkTaskStatus(taskId) {
|
||||||
@@ -182,7 +179,7 @@ class EveAIChatWidget extends HTMLElement {
|
|||||||
<div class="messages-area"></div>
|
<div class="messages-area"></div>
|
||||||
<div class="question-area">
|
<div class="question-area">
|
||||||
<input type="text" placeholder="Type your message here..." />
|
<input type="text" placeholder="Type your message here..." />
|
||||||
<button>Send</button>
|
<i class="material-icons send-icon outlined">send</i>
|
||||||
</div>
|
</div>
|
||||||
<div class="status-line"></div>
|
<div class="status-line"></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -197,31 +194,89 @@ class EveAIChatWidget extends HTMLElement {
|
|||||||
this.messagesArea.scrollTop = this.messagesArea.scrollHeight;
|
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 = []) {
|
addBotMessage(text, interactionId, algorithm = 'default', citations = []) {
|
||||||
const message = document.createElement('div');
|
const message = document.createElement('div');
|
||||||
message.classList.add('message', 'bot');
|
message.classList.add('message', 'bot');
|
||||||
|
|
||||||
let content = marked.parse(text); // Use marked to convert markdown to HTML
|
let content = marked.parse(text);
|
||||||
// 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 citationsHtml = citations.map(url => `<a href="${url}" target="_blank">${url}</a>`).join('<br>');
|
let citationsHtml = citations.map(url => `<a href="${url}" target="_blank">${url}</a>`).join('<br>');
|
||||||
|
|
||||||
message.innerHTML = `
|
let algorithmClass;
|
||||||
<p>${content}</p>
|
switch (algorithm) {
|
||||||
${citationsHtml ? `<p>${citationsHtml}</p>` : ''}
|
case 'RAG_TENANT':
|
||||||
<div class="message-icons">
|
algorithmClass = 'fingerprint-rag-tenant';
|
||||||
<span class="algorithm-indicator" style="background-color: var(--algorithm-color-${algorithm});">${algorithm}</span>
|
break;
|
||||||
<i class="material-icons" onclick="handleFeedback('up', '${interactionId}')">thumb_up</i>
|
case 'RAG_WIKIPEDIA':
|
||||||
<i class="material-icons" onclick="handleFeedback('down', '${interactionId}')">thumb_down</i>
|
algorithmClass = 'fingerprint-rag-wikipedia';
|
||||||
</div>
|
break;
|
||||||
`;
|
case 'RAG_GOOGLE':
|
||||||
this.messagesArea.appendChild(message);
|
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;
|
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() {
|
handleSendMessage() {
|
||||||
console.log('handleSendMessage called');
|
console.log('handleSendMessage called');
|
||||||
const message = this.questionInput.value.trim();
|
const message = this.questionInput.value.trim();
|
||||||
@@ -229,6 +284,7 @@ class EveAIChatWidget extends HTMLElement {
|
|||||||
this.addUserMessage(message);
|
this.addUserMessage(message);
|
||||||
this.questionInput.value = '';
|
this.questionInput.value = '';
|
||||||
this.sendMessageToBackend(message);
|
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.socket.emit('user_message', { tenantId: this.tenantId, token: this.jwtToken, message, language: this.language });
|
||||||
this.setStatusMessage('Processing started ...')
|
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);
|
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('api-key', this.apiKey);
|
||||||
chatWidget.setAttribute('domain', this.domain);
|
chatWidget.setAttribute('domain', this.domain);
|
||||||
chatWidget.setAttribute('language', this.language);
|
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 {
|
} else {
|
||||||
console.error('Container not found');
|
console.error('Container not found');
|
||||||
|
|||||||
Reference in New Issue
Block a user