- Allow the chat-widget to connect to multiple servers (e.g. development and production)
- Created a full session overview
This commit is contained in:
@@ -57,6 +57,9 @@ services:
|
|||||||
- ../nginx/sites-enabled:/etc/nginx/sites-enabled
|
- ../nginx/sites-enabled:/etc/nginx/sites-enabled
|
||||||
- ../nginx/static:/etc/nginx/static
|
- ../nginx/static:/etc/nginx/static
|
||||||
- ../nginx/public:/etc/nginx/public
|
- ../nginx/public:/etc/nginx/public
|
||||||
|
- ../integrations/Wordpress/eveai-chat-widget/css/eveai-chat-style.css:/etc/nginx/static/css/eveai-chat-style.css
|
||||||
|
- ../integrations/Wordpress/eveai-chat-widget/js/eveai-chat-widget.js:/etc/nginx/static/js/eveai-chat-widget.js
|
||||||
|
- ../integrations/Wordpress/eveai-chat-widget/js/eveai-sdk.js:/etc/nginx/static/js/eveai-sdk.js
|
||||||
- ./logs/nginx:/var/log/nginx
|
- ./logs/nginx:/var/log/nginx
|
||||||
depends_on:
|
depends_on:
|
||||||
- eveai_app
|
- eveai_app
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ COPY ../../nginx/mime.types /etc/nginx/mime.types
|
|||||||
# Copy static & public files
|
# Copy static & public files
|
||||||
RUN mkdir -p /etc/nginx/static /etc/nginx/public
|
RUN mkdir -p /etc/nginx/static /etc/nginx/public
|
||||||
COPY ../../nginx/static /etc/nginx/static
|
COPY ../../nginx/static /etc/nginx/static
|
||||||
|
COPY ../../integrations/Wordpress/eveai-chat-widget/css/eveai-chat-style.css /etc/nginx/static/css/
|
||||||
|
COPY ../../integrations/Wordpress/eveai-chat-widget/js/eveai-chat-widget.js /etc/nginx/static/js/
|
||||||
|
COPY ../../integrations/Wordpress/eveai-chat-widget/js/eveai-sdk.js /etc/nginx/static/js
|
||||||
COPY ../../nginx/public /etc/nginx/public
|
COPY ../../nginx/public /etc/nginx/public
|
||||||
|
|
||||||
# Copy site-specific configurations
|
# Copy site-specific configurations
|
||||||
|
|||||||
@@ -1,126 +1,80 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
{% from "macros.html" import render_field %}
|
||||||
|
|
||||||
|
{% block title %}Session Overview{% endblock %}
|
||||||
|
|
||||||
|
{% block content_title %}Session Overview{% endblock %}
|
||||||
|
{% block content_description %}An overview of the chat session.{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container mt-5">
|
<div class="container mt-5">
|
||||||
<h2>Chat Session Details</h2>
|
<h2>Chat Session Details</h2>
|
||||||
<!-- Session Information -->
|
|
||||||
<div class="card mb-4">
|
<div class="card mb-4">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h5>Session Information</h5>
|
<h5>Session Information</h5>
|
||||||
<!-- Timezone Toggle Buttons -->
|
|
||||||
<div class="btn-group" role="group">
|
|
||||||
<button type="button" class="btn btn-primary" id="toggle-interaction-timezone">Interaction Timezone</button>
|
|
||||||
<button type="button" class="btn btn-secondary" id="toggle-admin-timezone">Admin Timezone</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<dl class="row">
|
<p><strong>Session ID:</strong> {{ chat_session.session_id }}</p>
|
||||||
<dt class="col-sm-3">Session ID:</dt>
|
<p><strong>User:</strong> {{ chat_session.user.user_name if chat_session.user else 'Anonymous' }}</p>
|
||||||
<dd class="col-sm-9">{{ chat_session.session_id }}</dd>
|
<p><strong>Start:</strong> {{ chat_session.session_start | to_local_time(chat_session.timezone) }}</p>
|
||||||
|
<p><strong>End:</strong> {{ chat_session.session_end | to_local_time(chat_session.timezone) if chat_session.session_end else 'Ongoing' }}</p>
|
||||||
<dt class="col-sm-3">Session Start:</dt>
|
|
||||||
<dd class="col-sm-9">
|
|
||||||
<span class="timezone interaction-timezone">{{ chat_session.session_start | to_local_time(chat_session.timezone) }}</span>
|
|
||||||
<span class="timezone admin-timezone d-none">{{ chat_session.session_start | to_local_time(session['admin_user_timezone']) }}</span>
|
|
||||||
</dd>
|
|
||||||
|
|
||||||
<dt class="col-sm-3">Session End:</dt>
|
|
||||||
<dd class="col-sm-9">
|
|
||||||
{% if chat_session.session_end %}
|
|
||||||
<span class="timezone interaction-timezone">{{ chat_session.session_end | to_local_time(chat_session.timezone) }}</span>
|
|
||||||
<span class="timezone admin-timezone d-none">{{ chat_session.session_end | to_local_time(session['admin_user_timezone']) }}</span>
|
|
||||||
{% else %}
|
|
||||||
Ongoing
|
|
||||||
{% endif %}
|
|
||||||
</dd>
|
|
||||||
</dl>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Interactions List -->
|
<h3>Interactions</h3>
|
||||||
<div class="card mb-4">
|
<div class="accordion" id="interactionsAccordion">
|
||||||
<div class="card-header">
|
|
||||||
<h5>Interactions</h5>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
{% for interaction in interactions %}
|
{% for interaction in interactions %}
|
||||||
<div class="interaction mb-3">
|
<div class="accordion-item">
|
||||||
<div class="card">
|
<h2 class="accordion-header" id="heading{{ loop.index }}">
|
||||||
<div class="card-header d-flex justify-content-between">
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
|
||||||
<span>Question:</span>
|
data-bs-target="#collapse{{ loop.index }}" aria-expanded="false"
|
||||||
<span class="text-muted">
|
aria-controls="collapse{{ loop.index }}">
|
||||||
<span class="timezone interaction-timezone">{{ interaction.question_at | to_local_time(interaction.timezone) }}</span>
|
<div class="d-flex justify-content-between align-items-center w-100">
|
||||||
<span class="timezone admin-timezone d-none">{{ interaction.question_at | to_local_time(session['admin_user_timezone']) }}</span>
|
<span class="interaction-question">{{ interaction.question | truncate(50) }}</span>
|
||||||
-
|
<span class="interaction-icons">
|
||||||
<span class="timezone interaction-timezone">{{ interaction.answer_at | to_local_time(interaction.timezone) }}</span>
|
<i class="material-icons algorithm-icon {{ interaction.algorithm_used | lower }}">fingerprint</i>
|
||||||
<span class="timezone admin-timezone d-none">{{ interaction.answer_at | to_local_time(session['admin_user_timezone']) }}</span>
|
<i class="material-icons thumb-icon {% if interaction.appreciation == 100 %}filled{% else %}outlined{% endif %}">thumb_up</i>
|
||||||
({{ interaction.question_at | time_difference(interaction.answer_at) }})
|
<i class="material-icons thumb-icon {% if interaction.appreciation == 0 %}filled{% else %}outlined{% endif %}">thumb_down</i>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
</button>
|
||||||
<p><strong>Question:</strong> {{ interaction.question }}</p>
|
</h2>
|
||||||
<p><strong>Answer:</strong> {{ interaction.answer }}</p>
|
<div id="collapse{{ loop.index }}" class="accordion-collapse collapse" aria-labelledby="heading{{ loop.index }}"
|
||||||
<p>
|
data-bs-parent="#interactionsAccordion">
|
||||||
<strong>Algorithm Used:</strong>
|
<div class="accordion-body">
|
||||||
<i class="material-icons {{ 'fingerprint-rag-' ~ interaction.algorithm_used.lower() }}">
|
<h6>Detailed Question:</h6>
|
||||||
fingerprint
|
<p>{{ interaction.detailed_question }}</p>
|
||||||
</i> {{ interaction.algorithm_used }}
|
<h6>Answer:</h6>
|
||||||
</p>
|
<div class="markdown-content">{{ interaction.answer | safe }}</div>
|
||||||
<p>
|
{% if embeddings_dict.get(interaction.id) %}
|
||||||
<strong>Appreciation:</strong>
|
<h6>Related Documents:</h6>
|
||||||
<i class="material-icons thumb-icon {{ 'thumb_up' if interaction.appreciation == 1 else 'thumb_down' }}">
|
<ul>
|
||||||
{{ 'thumb_up' if interaction.appreciation == 1 else 'thumb_down' }}
|
{% for embedding in embeddings_dict[interaction.id] %}
|
||||||
</i>
|
<li>
|
||||||
</p>
|
{% if embedding.url %}
|
||||||
<p><strong>Embeddings:</strong>
|
<a href="{{ embedding.url }}" target="_blank">{{ embedding.url }}</a>
|
||||||
{% if interaction.embeddings %}
|
|
||||||
{% for embedding in interaction.embeddings %}
|
|
||||||
<a href="{{ url_for('interaction_bp.view_embedding', embedding_id=embedding.embedding_id) }}" class="badge badge-info">
|
|
||||||
{{ embedding.embedding_id }}
|
|
||||||
</a>
|
|
||||||
{% endfor %}
|
|
||||||
{% else %}
|
{% else %}
|
||||||
None
|
{{ embedding.file_name }}
|
||||||
|
{% endif %}
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
// Elements to toggle
|
var markdownElements = document.querySelectorAll('.markdown-content');
|
||||||
const interactionTimes = document.querySelectorAll('.interaction-timezone');
|
markdownElements.forEach(function(el) {
|
||||||
const adminTimes = document.querySelectorAll('.admin-timezone');
|
el.innerHTML = marked.parse(el.textContent);
|
||||||
|
|
||||||
// Buttons
|
|
||||||
const interactionButton = document.getElementById('toggle-interaction-timezone');
|
|
||||||
const adminButton = document.getElementById('toggle-admin-timezone');
|
|
||||||
|
|
||||||
// Toggle to Interaction Timezone
|
|
||||||
interactionButton.addEventListener('click', function() {
|
|
||||||
interactionTimes.forEach(el => el.classList.remove('d-none'));
|
|
||||||
adminTimes.forEach(el => el.classList.add('d-none'));
|
|
||||||
interactionButton.classList.add('btn-primary');
|
|
||||||
interactionButton.classList.remove('btn-secondary');
|
|
||||||
adminButton.classList.add('btn-secondary');
|
|
||||||
adminButton.classList.remove('btn-primary');
|
|
||||||
});
|
|
||||||
|
|
||||||
// Toggle to Admin Timezone
|
|
||||||
adminButton.addEventListener('click', function() {
|
|
||||||
interactionTimes.forEach(el => el.classList.add('d-none'));
|
|
||||||
adminTimes.forEach(el => el.classList.remove('d-none'));
|
|
||||||
interactionButton.classList.add('btn-secondary');
|
|
||||||
interactionButton.classList.remove('btn-primary');
|
|
||||||
adminButton.classList.add('btn-primary');
|
|
||||||
adminButton.classList.remove('btn-secondary');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ from requests.exceptions import SSLError
|
|||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
import io
|
import io
|
||||||
|
|
||||||
from common.models.interaction import ChatSession, Interaction
|
from common.models.document import Embedding, DocumentVersion
|
||||||
|
from common.models.interaction import ChatSession, Interaction, InteractionEmbedding
|
||||||
from common.extensions import db
|
from common.extensions import db
|
||||||
from .document_forms import AddDocumentForm, AddURLForm, EditDocumentForm, EditDocumentVersionForm
|
from .document_forms import AddDocumentForm, AddURLForm, EditDocumentForm, EditDocumentVersionForm
|
||||||
from common.utils.middleware import mw_before_request
|
from common.utils.middleware import mw_before_request
|
||||||
@@ -80,11 +81,34 @@ def handle_chat_session_selection():
|
|||||||
return redirect(prefixed_url_for('interaction_bp.chat_sessions'))
|
return redirect(prefixed_url_for('interaction_bp.chat_sessions'))
|
||||||
|
|
||||||
|
|
||||||
@interaction_bp.route('/view_chat_session/<chat_session_id>', methods=['GET'])
|
@interaction_bp.route('/view_chat_session/<int:chat_session_id>', methods=['GET'])
|
||||||
@roles_accepted('Super User', 'Tenant Admin')
|
@roles_accepted('Super User', 'Tenant Admin')
|
||||||
def view_chat_session(chat_session_id):
|
def view_chat_session(chat_session_id):
|
||||||
chat_session = ChatSession.query.get_or_404(chat_session_id)
|
chat_session = ChatSession.query.get_or_404(chat_session_id)
|
||||||
show_chat_session(chat_session)
|
interactions = (Interaction.query
|
||||||
|
.filter_by(chat_session_id=chat_session.id)
|
||||||
|
.order_by(Interaction.question_at)
|
||||||
|
.all())
|
||||||
|
|
||||||
|
# Fetch all related embeddings for the interactions in this session
|
||||||
|
embedding_query = (db.session.query(InteractionEmbedding.interaction_id,
|
||||||
|
DocumentVersion.url,
|
||||||
|
DocumentVersion.file_name)
|
||||||
|
.join(Embedding, InteractionEmbedding.embedding_id == Embedding.id)
|
||||||
|
.join(DocumentVersion, Embedding.doc_vers_id == DocumentVersion.id)
|
||||||
|
.filter(InteractionEmbedding.interaction_id.in_([i.id for i in interactions])))
|
||||||
|
|
||||||
|
# Create a dictionary to store embeddings for each interaction
|
||||||
|
embeddings_dict = {}
|
||||||
|
for interaction_id, url, file_name in embedding_query:
|
||||||
|
if interaction_id not in embeddings_dict:
|
||||||
|
embeddings_dict[interaction_id] = []
|
||||||
|
embeddings_dict[interaction_id].append({'url': url, 'file_name': file_name})
|
||||||
|
|
||||||
|
return render_template('interaction/view_chat_session.html',
|
||||||
|
chat_session=chat_session,
|
||||||
|
interactions=interactions,
|
||||||
|
embeddings_dict=embeddings_dict)
|
||||||
|
|
||||||
|
|
||||||
@interaction_bp.route('/view_chat_session_by_session_id/<session_id>', methods=['GET'])
|
@interaction_bp.route('/view_chat_session_by_session_id/<session_id>', methods=['GET'])
|
||||||
|
|||||||
Binary file not shown.
@@ -3,7 +3,7 @@
|
|||||||
Plugin Name: EveAI Chat Widget
|
Plugin Name: EveAI Chat Widget
|
||||||
Plugin URI: https://askeveai.com/
|
Plugin URI: https://askeveai.com/
|
||||||
Description: Integrates the EveAI chat interface into your WordPress site.
|
Description: Integrates the EveAI chat interface into your WordPress site.
|
||||||
Version: 1.4.1
|
Version: 1.5.0
|
||||||
Author: Josako, Pieter Laroy
|
Author: Josako, Pieter Laroy
|
||||||
Author URI: https://askeveai.com/about/
|
Author URI: https://askeveai.com/about/
|
||||||
*/
|
*/
|
||||||
@@ -28,7 +28,8 @@ function eveai_chat_shortcode($atts) {
|
|||||||
'api_key' => '',
|
'api_key' => '',
|
||||||
'domain' => '',
|
'domain' => '',
|
||||||
'language' => 'en',
|
'language' => 'en',
|
||||||
'supported_languages' => 'en,fr,de,es'
|
'supported_languages' => 'en,fr,de,es',
|
||||||
|
'server_url' => 'https://evie.askeveai.com'
|
||||||
);
|
);
|
||||||
|
|
||||||
// Merge provided attributes with defaults
|
// Merge provided attributes with defaults
|
||||||
@@ -40,6 +41,7 @@ function eveai_chat_shortcode($atts) {
|
|||||||
$domain = esc_url_raw($atts['domain']);
|
$domain = esc_url_raw($atts['domain']);
|
||||||
$language = sanitize_text_field($atts['language']);
|
$language = sanitize_text_field($atts['language']);
|
||||||
$supported_languages = sanitize_text_field($atts['supported_languages']);
|
$supported_languages = sanitize_text_field($atts['supported_languages']);
|
||||||
|
$server_url = esc_url_raw($atts['server_url']);
|
||||||
|
|
||||||
// Generate a unique ID for this instance of the chat widget
|
// Generate a unique ID for this instance of the chat widget
|
||||||
$chat_id = 'chat-container-' . uniqid();
|
$chat_id = 'chat-container-' . uniqid();
|
||||||
@@ -52,7 +54,8 @@ function eveai_chat_shortcode($atts) {
|
|||||||
'$api_key',
|
'$api_key',
|
||||||
'$domain',
|
'$domain',
|
||||||
'$language',
|
'$language',
|
||||||
'$supported_languages'
|
'$supported_languages',
|
||||||
|
'$server_url'
|
||||||
);
|
);
|
||||||
eveAI.initializeChat('$chat_id');
|
eveAI.initializeChat('$chat_id');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
class EveAIChatWidget extends HTMLElement {
|
class EveAIChatWidget extends HTMLElement {
|
||||||
static get observedAttributes() {
|
static get observedAttributes() {
|
||||||
return ['tenant-id', 'api-key', 'domain', 'language', 'languages'];
|
return ['tenant-id', 'api-key', 'domain', 'language', 'languages', 'server-url'];
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -87,6 +87,7 @@ class EveAIChatWidget extends HTMLElement {
|
|||||||
this.language = this.getAttribute('language');
|
this.language = this.getAttribute('language');
|
||||||
const languageAttr = this.getAttribute('languages');
|
const languageAttr = this.getAttribute('languages');
|
||||||
this.languages = languageAttr ? languageAttr.split(',') : [];
|
this.languages = languageAttr ? languageAttr.split(',') : [];
|
||||||
|
this.serverUrl = this.getAttribute('server-url');
|
||||||
this.currentLanguage = this.language;
|
this.currentLanguage = this.language;
|
||||||
console.log('Updated attributes:', {
|
console.log('Updated attributes:', {
|
||||||
tenantId: this.tenantId,
|
tenantId: this.tenantId,
|
||||||
@@ -94,7 +95,8 @@ class EveAIChatWidget extends HTMLElement {
|
|||||||
domain: this.domain,
|
domain: this.domain,
|
||||||
language: this.language,
|
language: this.language,
|
||||||
currentLanguage: this.currentLanguage,
|
currentLanguage: this.currentLanguage,
|
||||||
languages: this.languages
|
languages: this.languages,
|
||||||
|
serverUrl: this.serverUrl
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,14 +106,16 @@ class EveAIChatWidget extends HTMLElement {
|
|||||||
const domain = this.getAttribute('domain');
|
const domain = this.getAttribute('domain');
|
||||||
const language = this.getAttribute('language');
|
const language = this.getAttribute('language');
|
||||||
const languages = this.getAttribute('languages');
|
const languages = this.getAttribute('languages');
|
||||||
|
const serverUrl = this.getAttribute('server-url');
|
||||||
console.log('Checking if all attributes are set:', {
|
console.log('Checking if all attributes are set:', {
|
||||||
tenantId,
|
tenantId,
|
||||||
apiKey,
|
apiKey,
|
||||||
domain,
|
domain,
|
||||||
language,
|
language,
|
||||||
languages
|
languages,
|
||||||
|
serverUrl
|
||||||
});
|
});
|
||||||
return tenantId && apiKey && domain && language && languages;
|
return tenantId && apiKey && domain && language && languages && serverUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
createLanguageDropdown() {
|
createLanguageDropdown() {
|
||||||
@@ -142,7 +146,7 @@ class EveAIChatWidget extends HTMLElement {
|
|||||||
console.log(`Initializing socket connection to Evie`);
|
console.log(`Initializing socket connection to Evie`);
|
||||||
|
|
||||||
// Ensure apiKey is passed in the query parameters
|
// Ensure apiKey is passed in the query parameters
|
||||||
this.socket = io('https://evie.askeveai.com', {
|
this.socket = io(this.serverUrl, {
|
||||||
path: '/chat/socket.io/',
|
path: '/chat/socket.io/',
|
||||||
transports: ['websocket', 'polling'],
|
transports: ['websocket', 'polling'],
|
||||||
query: {
|
query: {
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
// static/js/eveai-sdk.js
|
// static/js/eveai-sdk.js
|
||||||
class EveAI {
|
class EveAI {
|
||||||
constructor(tenantId, apiKey, domain, language, languages) {
|
constructor(tenantId, apiKey, domain, language, languages, serverUrl) {
|
||||||
this.tenantId = tenantId;
|
this.tenantId = tenantId;
|
||||||
this.apiKey = apiKey;
|
this.apiKey = apiKey;
|
||||||
this.domain = domain;
|
this.domain = domain;
|
||||||
this.language = language;
|
this.language = language;
|
||||||
this.languages = languages;
|
this.languages = languages;
|
||||||
|
this.serverUrl = serverUrl;
|
||||||
|
|
||||||
console.log('EveAI constructor:', { tenantId, apiKey, domain, language, languages });
|
console.log('EveAI constructor:', { tenantId, apiKey, domain, language, languages, serverUrl });
|
||||||
}
|
}
|
||||||
|
|
||||||
initializeChat(containerId) {
|
initializeChat(containerId) {
|
||||||
@@ -21,6 +22,7 @@ class EveAI {
|
|||||||
chatWidget.setAttribute('domain', this.domain);
|
chatWidget.setAttribute('domain', this.domain);
|
||||||
chatWidget.setAttribute('language', this.language);
|
chatWidget.setAttribute('language', this.language);
|
||||||
chatWidget.setAttribute('languages', this.languages);
|
chatWidget.setAttribute('languages', this.languages);
|
||||||
|
chatWidget.setAttribute('server-url', this.serverUrl);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
console.error('Container not found');
|
console.error('Container not found');
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ Contributors: Josako
|
|||||||
Tags: chat, ai
|
Tags: chat, ai
|
||||||
Requires at least: 5.0
|
Requires at least: 5.0
|
||||||
Tested up to: 5.9
|
Tested up to: 5.9
|
||||||
Stable tag: 1.4.1
|
Stable tag: 1.5.0
|
||||||
License: GPLv2 or later
|
License: GPLv2 or later
|
||||||
License URI: http://www.gnu.org/licenses/gpl-2.0.html
|
License URI: http://www.gnu.org/licenses/gpl-2.0.html
|
||||||
|
|
||||||
@@ -23,10 +23,10 @@ This plugin allows you to easily add the EveAI chat widget to your WordPress sit
|
|||||||
|
|
||||||
To add an EveAI Chat Widget to your page or post, use the following shortcode:
|
To add an EveAI Chat Widget to your page or post, use the following shortcode:
|
||||||
|
|
||||||
[eveai_chat tenant_id="YOUR_TENANT_ID" api_key="YOUR_API_KEY" domain="YOUR_DOMAIN" language="LANGUAGE_CODE" supported_languages="COMMA_SEPARATED_LANGUAGE_CODES"]
|
[eveai_chat tenant_id="YOUR_TENANT_ID" api_key="YOUR_API_KEY" domain="YOUR_DOMAIN" language="LANGUAGE_CODE" supported_languages="COMMA_SEPARATED_LANGUAGE_CODES" server_url="Server URL for Evie"]
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
[eveai_chat tenant_id="123456" api_key="your_api_key_here" domain="https://your-domain.com" language="en" supported_languages="en,fr,de,es"]
|
[eveai_chat tenant_id="123456" api_key="your_api_key_here" domain="https://your-domain.com" language="en" supported_languages="en,fr,de,es" server_url="https://evie.askeveai.com"]
|
||||||
|
|
||||||
You can add multiple chat widgets with different configurations by using the shortcode multiple times with different parameters.
|
You can add multiple chat widgets with different configurations by using the shortcode multiple times with different parameters.
|
||||||
|
|
||||||
@@ -38,6 +38,9 @@ Contact your EveAI service provider to obtain your Tenant ID, API Key, and Domai
|
|||||||
|
|
||||||
== Changelog ==
|
== Changelog ==
|
||||||
|
|
||||||
|
= 1.5.0 =
|
||||||
|
* Allow for multiple servers to serve Evie
|
||||||
|
|
||||||
= 1.4.1 - 1.4...=
|
= 1.4.1 - 1.4...=
|
||||||
* Bug fixes
|
* Bug fixes
|
||||||
|
|
||||||
|
|||||||
30
nginx/public/chat_evie.html
Normal file
30
nginx/public/chat_evie.html
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Chat Client Evie</title>
|
||||||
|
<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>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const eveAI = new EveAI(
|
||||||
|
'2',
|
||||||
|
'EveAI-CHAT-9079-8604-7441-6496-7604',
|
||||||
|
'http://macstudio.ask-eve-ai-local.com',
|
||||||
|
'en',
|
||||||
|
'en,fr,nl',
|
||||||
|
'http://macstudio.ask-eve-ai-local.com:8080/'
|
||||||
|
);
|
||||||
|
eveAI.initializeChat('chat-container');
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -421,3 +421,90 @@ input[type="radio"] {
|
|||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Custom styles for chat session view */
|
||||||
|
.accordion-button:not(.collapsed) {
|
||||||
|
background-color: var(--bs-primary);
|
||||||
|
color: var(--bs-white);
|
||||||
|
}
|
||||||
|
|
||||||
|
.accordion-button:focus {
|
||||||
|
box-shadow: 0 0 0 0.25rem rgba(118, 89, 154, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.interaction-question {
|
||||||
|
font-size: 1rem; /* Normal text size */
|
||||||
|
}
|
||||||
|
|
||||||
|
.interaction-icons {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.interaction-icons .material-icons {
|
||||||
|
font-size: 24px;
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumb-icon.filled {
|
||||||
|
color: var(--bs-success);
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumb-icon.outlined {
|
||||||
|
color: var(--thumb-icon-outlined);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Algorithm icon colors */
|
||||||
|
.algorithm-icon.rag_tenant {
|
||||||
|
color: var(--algorithm-color-rag-tenant);
|
||||||
|
}
|
||||||
|
|
||||||
|
.algorithm-icon.rag_wikipedia {
|
||||||
|
color: var(--algorithm-color-rag-wikipedia);
|
||||||
|
}
|
||||||
|
|
||||||
|
.algorithm-icon.rag_google {
|
||||||
|
color: var(--algorithm-color-rag-google);
|
||||||
|
}
|
||||||
|
|
||||||
|
.algorithm-icon.llm {
|
||||||
|
color: var(--algorithm-color-llm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.accordion-body {
|
||||||
|
background-color: var(--bs-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Markdown content styles */
|
||||||
|
.markdown-content {
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-content p {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-content h1, .markdown-content h2, .markdown-content h3,
|
||||||
|
.markdown-content h4, .markdown-content h5, .markdown-content h6 {
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-content ul, .markdown-content ol {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
padding-left: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-content code {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
padding: 0.2em 0.4em;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-content pre {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 5px;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,294 +0,0 @@
|
|||||||
/* eveai_chat.css */
|
|
||||||
:root {
|
|
||||||
--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-llm: #800080; /* Purple for RAG_LLM */
|
|
||||||
|
|
||||||
/*--font-family: 'Arial, sans-serif'; !* Default font family *!*/
|
|
||||||
--font-family: 'Segoe UI, Roboto, Cantarell, Noto Sans, Apple Color Emoji, 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;
|
|
||||||
|
|
||||||
/* Connection Status colors */
|
|
||||||
--status-connected-color: #28a745; /* Green color for connected status */
|
|
||||||
--status-disconnected-color: #ffc107; /* Orange color for disconnected status */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Connection status styles */
|
|
||||||
.connection-status-icon {
|
|
||||||
vertical-align: middle;
|
|
||||||
font-size: 24px;
|
|
||||||
margin-right: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-connected {
|
|
||||||
color: var(--status-connected-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-disconnected {
|
|
||||||
color: var(--status-disconnected-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 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;
|
|
||||||
height: 75vh;
|
|
||||||
/*max-height: 100vh;*/
|
|
||||||
max-width: 600px;
|
|
||||||
margin: auto;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
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 */
|
|
||||||
}
|
|
||||||
|
|
||||||
.disclaimer {
|
|
||||||
font-size: 0.7em;
|
|
||||||
text-align: right;
|
|
||||||
padding: 5px 20px 5px 5px;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.messages-area {
|
|
||||||
flex: 1;
|
|
||||||
overflow-y: auto;
|
|
||||||
padding: 10px;
|
|
||||||
background-color: var(--bot-message-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.message {
|
|
||||||
max-width: 90%;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
padding: 10px;
|
|
||||||
border-radius: 15px;
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.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 {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
.question-area {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
background-color: var(--user-message-bg);
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.language-select-container {
|
|
||||||
width: 100%;
|
|
||||||
margin-bottom: 10px; /* Spacing between the dropdown and the textarea */
|
|
||||||
}
|
|
||||||
|
|
||||||
.language-select {
|
|
||||||
width: 100%;
|
|
||||||
margin-bottom: 5px; /* Space between the dropdown and the send button */
|
|
||||||
padding: 8px;
|
|
||||||
border-radius: 5px;
|
|
||||||
border: 1px solid var(--input-border);
|
|
||||||
background-color: var(--input-bg);
|
|
||||||
color: var(--input-text-color);
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.question-area textarea {
|
|
||||||
flex: 1;
|
|
||||||
border: none;
|
|
||||||
padding: 10px;
|
|
||||||
border-radius: 15px;
|
|
||||||
background-color: var(--input-bg);
|
|
||||||
border: 1px solid var(--input-border);
|
|
||||||
color: var(--input-text-color);
|
|
||||||
font-family: var(--font-family); /* Apply the default font family */
|
|
||||||
font-size: 1rem;
|
|
||||||
resize: vertical;
|
|
||||||
min-height: 60px;
|
|
||||||
max-height: 150px;
|
|
||||||
overflow-y: auto;
|
|
||||||
margin-right: 10px; /* Space between textarea and right-side container */
|
|
||||||
}
|
|
||||||
|
|
||||||
.right-side {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.question-area button {
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
color: var(--button-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Styles for the send icon */
|
|
||||||
.send-icon {
|
|
||||||
font-size: 24px;
|
|
||||||
color: var(--button-color);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.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 */
|
|
||||||
.status-line {
|
|
||||||
height: var(--status-line-height); /* Fixed height for the status line */
|
|
||||||
padding: 5px 10px;
|
|
||||||
background-color: var(--status-line-bg); /* Background color */
|
|
||||||
color: var(--status-line-color); /* Text color */
|
|
||||||
font-size: 0.9rem; /* Slightly smaller font size */
|
|
||||||
text-align: center; /* Centered text */
|
|
||||||
border-top: 1px solid #ccc; /* Subtle top border */
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 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-llm {
|
|
||||||
color: var(--algorithm-color-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 {
|
|
||||||
max-width: 90%; /* Reduce max width on smaller screens */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
|
||||||
.chat-container {
|
|
||||||
max-width: 95%; /* Further reduce max width on very small screens */
|
|
||||||
}
|
|
||||||
|
|
||||||
.question-area input {
|
|
||||||
font-size: 0.9rem; /* Adjust input font size for smaller screens */
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-line {
|
|
||||||
font-size: 0.8rem; /* Adjust status line font size for smaller screens */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,470 +0,0 @@
|
|||||||
class EveAIChatWidget extends HTMLElement {
|
|
||||||
static get observedAttributes() {
|
|
||||||
return ['tenant-id', 'api-key', 'domain', 'language', 'languages'];
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
this.socket = null; // Initialize socket to null
|
|
||||||
this.attributesSet = false; // Flag to check if all attributes are set
|
|
||||||
this.jwtToken = null; // Initialize jwtToken to null
|
|
||||||
this.userTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone; // Detect user's timezone
|
|
||||||
this.heartbeatInterval = null;
|
|
||||||
this.idleTime = 0; // in milliseconds
|
|
||||||
this.maxConnectionIdleTime = 1 * 60 * 60 * 1000; // 1 hours in milliseconds
|
|
||||||
this.languages = []
|
|
||||||
this.room = null;
|
|
||||||
console.log('EveAIChatWidget constructor called');
|
|
||||||
}
|
|
||||||
|
|
||||||
connectedCallback() {
|
|
||||||
console.log('connectedCallback called');
|
|
||||||
this.innerHTML = this.getTemplate();
|
|
||||||
this.messagesArea = this.querySelector('.messages-area');
|
|
||||||
this.questionInput = this.querySelector('.question-area textarea');
|
|
||||||
this.sendButton = this.querySelector('.send-icon');
|
|
||||||
this.languageSelect = this.querySelector('.language-select');
|
|
||||||
this.statusLine = this.querySelector('.status-line');
|
|
||||||
this.statusMessage = this.querySelector('.status-message');
|
|
||||||
this.connectionStatusIcon = this.querySelector('.connection-status-icon');
|
|
||||||
|
|
||||||
this.sendButton.addEventListener('click', () => this.handleSendMessage());
|
|
||||||
this.questionInput.addEventListener('keydown', (event) => {
|
|
||||||
if (event.key === 'Enter' && !event.shiftKey) {
|
|
||||||
event.preventDefault(); // Prevent adding a new line
|
|
||||||
this.handleSendMessage();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (this.areAllAttributesSet() && !this.socket) {
|
|
||||||
console.log('Attributes already set in connectedCallback, initializing socket');
|
|
||||||
this.initializeSocket();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
populateLanguageDropdown() {
|
|
||||||
// Clear existing options
|
|
||||||
this.languageSelect.innerHTML = '';
|
|
||||||
console.log(`languages for options: ${this.languages}`)
|
|
||||||
|
|
||||||
// Populate with new options
|
|
||||||
this.languages.forEach(lang => {
|
|
||||||
const option = document.createElement('option');
|
|
||||||
option.value = lang;
|
|
||||||
option.textContent = lang.toUpperCase();
|
|
||||||
if (lang === this.currentLanguage) {
|
|
||||||
option.selected = true;
|
|
||||||
}
|
|
||||||
console.log(`Adding option for language: ${lang}`)
|
|
||||||
this.languageSelect.appendChild(option);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add event listener for language change
|
|
||||||
this.languageSelect.addEventListener('change', (e) => {
|
|
||||||
this.currentLanguage = e.target.value;
|
|
||||||
// You might want to emit an event or update the backend about the language change
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
attributeChangedCallback(name, oldValue, newValue) {
|
|
||||||
console.log(`attributeChangedCallback called: ${name} changed from ${oldValue} to ${newValue}`);
|
|
||||||
this.updateAttributes();
|
|
||||||
|
|
||||||
if (this.areAllAttributesSet() && !this.socket) {
|
|
||||||
console.log('All attributes set in attributeChangedCallback, initializing socket');
|
|
||||||
this.attributesSet = true;
|
|
||||||
console.log('All attributes are set, populating language dropdown');
|
|
||||||
this.populateLanguageDropdown();
|
|
||||||
console.log('All attributes are set, initializing socket')
|
|
||||||
this.initializeSocket();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updateAttributes() {
|
|
||||||
this.tenantId = this.getAttribute('tenant-id');
|
|
||||||
this.apiKey = this.getAttribute('api-key');
|
|
||||||
this.domain = this.getAttribute('domain');
|
|
||||||
this.language = this.getAttribute('language');
|
|
||||||
const languageAttr = this.getAttribute('languages');
|
|
||||||
this.languages = languageAttr ? languageAttr.split(',') : [];
|
|
||||||
this.currentLanguage = this.language;
|
|
||||||
console.log('Updated attributes:', {
|
|
||||||
tenantId: this.tenantId,
|
|
||||||
apiKey: this.apiKey,
|
|
||||||
domain: this.domain,
|
|
||||||
language: this.language,
|
|
||||||
currentLanguage: this.currentLanguage,
|
|
||||||
languages: this.languages
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
areAllAttributesSet() {
|
|
||||||
const tenantId = this.getAttribute('tenant-id');
|
|
||||||
const apiKey = this.getAttribute('api-key');
|
|
||||||
const domain = this.getAttribute('domain');
|
|
||||||
const language = this.getAttribute('language');
|
|
||||||
const languages = this.getAttribute('languages');
|
|
||||||
console.log('Checking if all attributes are set:', {
|
|
||||||
tenantId,
|
|
||||||
apiKey,
|
|
||||||
domain,
|
|
||||||
language,
|
|
||||||
languages
|
|
||||||
});
|
|
||||||
return tenantId && apiKey && domain && language && languages;
|
|
||||||
}
|
|
||||||
|
|
||||||
createLanguageDropdown() {
|
|
||||||
const select = document.createElement('select');
|
|
||||||
select.id = 'languageSelect';
|
|
||||||
this.languages.forEach(lang => {
|
|
||||||
const option = document.createElement('option');
|
|
||||||
option.value = lang;
|
|
||||||
option.textContent = lang.toUpperCase();
|
|
||||||
if (lang === this.currentLanguage) {
|
|
||||||
option.selected = true;
|
|
||||||
}
|
|
||||||
select.appendChild(option);
|
|
||||||
});
|
|
||||||
select.addEventListener('change', (e) => {
|
|
||||||
this.currentLanguage = e.target.value;
|
|
||||||
// You might want to emit an event or update the backend about the language change
|
|
||||||
});
|
|
||||||
return select;
|
|
||||||
}
|
|
||||||
|
|
||||||
initializeSocket() {
|
|
||||||
if (this.socket) {
|
|
||||||
console.log('Socket already initialized');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`Initializing socket connection to Evie`);
|
|
||||||
|
|
||||||
// Ensure apiKey is passed in the query parameters
|
|
||||||
this.socket = io('https://evie.askeveai.com', {
|
|
||||||
path: '/chat/socket.io/',
|
|
||||||
transports: ['websocket', 'polling'],
|
|
||||||
query: {
|
|
||||||
tenantId: this.tenantId,
|
|
||||||
apiKey: this.apiKey // Ensure apiKey is included here
|
|
||||||
},
|
|
||||||
auth: {
|
|
||||||
token: 'Bearer ' + this.apiKey // Ensure token is included here
|
|
||||||
},
|
|
||||||
reconnectionAttempts: Infinity, // Infinite reconnection attempts
|
|
||||||
reconnectionDelay: 5000, // Delay between reconnections
|
|
||||||
timeout: 20000 // Connection timeout
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(`Finished initializing socket connection to Evie`);
|
|
||||||
|
|
||||||
this.socket.on('connect', (data) => {
|
|
||||||
console.log('Socket connected OK');
|
|
||||||
this.setStatusMessage('Connected to EveAI.');
|
|
||||||
this.updateConnectionStatus(true);
|
|
||||||
this.startHeartbeat();
|
|
||||||
if (data.room) {
|
|
||||||
this.room = data.room;
|
|
||||||
console.log(`Joined room: ${this.room}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.socket.on('authenticated', (data) => {
|
|
||||||
console.log('Authenticated event received: ', data);
|
|
||||||
this.setStatusMessage('Authenticated.');
|
|
||||||
if (data.token) {
|
|
||||||
this.jwtToken = data.token; // Store the JWT token received from the server
|
|
||||||
}
|
|
||||||
if (data.room) {
|
|
||||||
this.room = data.room;
|
|
||||||
console.log(`Confirmed room: ${this.room}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.socket.on('connect_error', (err) => {
|
|
||||||
console.error('Socket connection error:', err);
|
|
||||||
this.setStatusMessage('EveAI Chat Widget needs further configuration by site administrator.');
|
|
||||||
this.updateConnectionStatus(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.socket.on('connect_timeout', () => {
|
|
||||||
console.error('Socket connection timeout');
|
|
||||||
this.setStatusMessage('EveAI Chat Widget needs further configuration by site administrator.');
|
|
||||||
this.updateConnectionStatus(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.socket.on('disconnect', (reason) => {
|
|
||||||
console.log('Socket disconnected: ', reason);
|
|
||||||
if (reason === 'io server disconnect') {
|
|
||||||
// Server disconnected the socket
|
|
||||||
this.socket.connect(); // Attempt to reconnect
|
|
||||||
}
|
|
||||||
this.setStatusMessage('Disconnected from EveAI. Please refresh the page for further interaction.');
|
|
||||||
this.updateConnectionStatus(false);
|
|
||||||
this.stopHeartbeat();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.socket.on('reconnect_attempt', () => {
|
|
||||||
console.log('Attempting to reconnect to the server...');
|
|
||||||
this.setStatusMessage('Attempting to reconnect...');
|
|
||||||
});
|
|
||||||
|
|
||||||
this.socket.on('reconnect', () => {
|
|
||||||
console.log('Successfully reconnected to the server');
|
|
||||||
this.setStatusMessage('Reconnected to EveAI.');
|
|
||||||
this.updateConnectionStatus(true);
|
|
||||||
this.startHeartbeat();
|
|
||||||
});
|
|
||||||
|
|
||||||
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...');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.socket.on('task_status', (data) => {
|
|
||||||
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') {
|
|
||||||
this.updateProgress();
|
|
||||||
setTimeout(() => this.checkTaskStatus(data.taskId), 1000); // Poll every second
|
|
||||||
} else if (data.status === 'success') {
|
|
||||||
this.addBotMessage(data.answer, data.interaction_id, data.algorithm, data.citations);
|
|
||||||
this.clearProgress(); // Clear progress indicator when done
|
|
||||||
} else {
|
|
||||||
this.setStatusMessage('Failed to process message.');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
setStatusMessage(message) {
|
|
||||||
this.statusMessage.textContent = message;
|
|
||||||
}
|
|
||||||
|
|
||||||
updateConnectionStatus(isConnected) {
|
|
||||||
if (isConnected) {
|
|
||||||
this.connectionStatusIcon.textContent = 'link';
|
|
||||||
this.connectionStatusIcon.classList.remove('status-disconnected');
|
|
||||||
this.connectionStatusIcon.classList.add('status-connected');
|
|
||||||
} else {
|
|
||||||
this.connectionStatusIcon.textContent = 'link_off';
|
|
||||||
this.connectionStatusIcon.classList.remove('status-connected');
|
|
||||||
this.connectionStatusIcon.classList.add('status-disconnected');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
startHeartbeat() {
|
|
||||||
this.stopHeartbeat(); // Clear any existing interval
|
|
||||||
this.heartbeatInterval = setInterval(() => {
|
|
||||||
if (this.socket && this.socket.connected) {
|
|
||||||
this.socket.emit('heartbeat');
|
|
||||||
this.idleTime += 30000;
|
|
||||||
if (this.idleTime >= this.maxConnectionIdleTime) {
|
|
||||||
this.socket.disconnect();
|
|
||||||
this.setStatusMessage('Disconnected due to inactivity.');
|
|
||||||
this.updateConnectionStatus(false);
|
|
||||||
this.stopHeartbeat();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, 30000); // Send a heartbeat every 30 seconds
|
|
||||||
}
|
|
||||||
|
|
||||||
stopHeartbeat() {
|
|
||||||
if (this.heartbeatInterval) {
|
|
||||||
clearInterval(this.heartbeatInterval);
|
|
||||||
this.heartbeatInterval = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updateProgress() {
|
|
||||||
if (!this.statusMessage.textContent) {
|
|
||||||
this.statusMessage.textContent = 'Processing...';
|
|
||||||
} else {
|
|
||||||
this.statusMessage.textContent += '.'; // Append a dot
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
clearProgress() {
|
|
||||||
this.statusMessage.textContent = '';
|
|
||||||
this.toggleSendButton(false); // Re-enable and revert send button to outlined version
|
|
||||||
}
|
|
||||||
|
|
||||||
checkTaskStatus(taskId) {
|
|
||||||
this.updateProgress();
|
|
||||||
this.socket.emit('check_task_status', { task_id: taskId });
|
|
||||||
}
|
|
||||||
|
|
||||||
getTemplate() {
|
|
||||||
return `
|
|
||||||
<div class="chat-container">
|
|
||||||
<div class="messages-area"></div>
|
|
||||||
<div class="disclaimer">Evie can make mistakes. Please double-check responses.</div>
|
|
||||||
<div class="question-area">
|
|
||||||
<textarea placeholder="Type your message here..." rows="3"></textarea>
|
|
||||||
<div class="right-side">
|
|
||||||
<select class="language-select"></select>
|
|
||||||
<i class="material-icons send-icon outlined">send</i>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="status-line">
|
|
||||||
<i class="material-icons connection-status-icon">link_off</i>
|
|
||||||
<span class="status-message"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
addUserMessage(text) {
|
|
||||||
const message = document.createElement('div');
|
|
||||||
message.classList.add('message', 'user');
|
|
||||||
message.innerHTML = `<p>${text}</p>`;
|
|
||||||
this.messagesArea.appendChild(message);
|
|
||||||
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);
|
|
||||||
let citationsHtml = citations.map(url => `<a href="${url}" target="_blank">${url}</a>`).join('<br>');
|
|
||||||
|
|
||||||
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 'LLM':
|
|
||||||
algorithmClass = 'fingerprint-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) {
|
|
||||||
console.log('feedback called');
|
|
||||||
this.idleTime = 0; // Reset idle time
|
|
||||||
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');
|
|
||||||
this.idleTime = 0; // Reset idle time
|
|
||||||
const message = this.questionInput.value.trim();
|
|
||||||
if (message) {
|
|
||||||
this.addUserMessage(message);
|
|
||||||
this.questionInput.value = '';
|
|
||||||
this.sendMessageToBackend(message);
|
|
||||||
this.toggleSendButton(true); // Disable and change send button to filled version
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sendMessageToBackend(message) {
|
|
||||||
console.log('sendMessageToBackend called');
|
|
||||||
if (!this.socket) {
|
|
||||||
console.error('Socket is not initialized');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!this.jwtToken) {
|
|
||||||
console.error('JWT token is not available');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectedLanguage = this.languageSelect.value;
|
|
||||||
|
|
||||||
console.log('Sending message to backend');
|
|
||||||
this.socket.emit('user_message', {
|
|
||||||
tenantId: this.tenantId,
|
|
||||||
token: this.jwtToken,
|
|
||||||
message,
|
|
||||||
language: selectedLanguage,
|
|
||||||
timezone: this.userTimezone
|
|
||||||
});
|
|
||||||
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);
|
|
||||||
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
// static/js/eveai-sdk.js
|
|
||||||
class EveAI {
|
|
||||||
constructor(tenantId, apiKey, domain, language, languages) {
|
|
||||||
this.tenantId = tenantId;
|
|
||||||
this.apiKey = apiKey;
|
|
||||||
this.domain = domain;
|
|
||||||
this.language = language;
|
|
||||||
this.languages = languages;
|
|
||||||
|
|
||||||
console.log('EveAI constructor:', { tenantId, apiKey, domain, language, languages });
|
|
||||||
}
|
|
||||||
|
|
||||||
initializeChat(containerId) {
|
|
||||||
const container = document.getElementById(containerId);
|
|
||||||
if (container) {
|
|
||||||
container.innerHTML = '<eveai-chat-widget></eveai-chat-widget>';
|
|
||||||
customElements.whenDefined('eveai-chat-widget').then(() => {
|
|
||||||
const chatWidget = container.querySelector('eveai-chat-widget');
|
|
||||||
chatWidget.setAttribute('tenant-id', this.tenantId);
|
|
||||||
chatWidget.setAttribute('api-key', this.apiKey);
|
|
||||||
chatWidget.setAttribute('domain', this.domain);
|
|
||||||
chatWidget.setAttribute('language', this.language);
|
|
||||||
chatWidget.setAttribute('languages', this.languages);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
console.error('Container not found');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user