API key working, CORS working, SocketIO working (but no JWT), Chat client v1, Session implemented (server side)
This commit is contained in:
@@ -6,6 +6,8 @@ from flask_mailman import Mail
|
|||||||
from flask_login import LoginManager
|
from flask_login import LoginManager
|
||||||
from flask_cors import CORS
|
from flask_cors import CORS
|
||||||
from flask_socketio import SocketIO
|
from flask_socketio import SocketIO
|
||||||
|
from flask_jwt_extended import JWTManager
|
||||||
|
from flask_session import Session
|
||||||
|
|
||||||
from .utils.key_encryption import JosKMSClient
|
from .utils.key_encryption import JosKMSClient
|
||||||
|
|
||||||
@@ -18,4 +20,7 @@ mail = Mail()
|
|||||||
login_manager = LoginManager()
|
login_manager = LoginManager()
|
||||||
cors = CORS()
|
cors = CORS()
|
||||||
socketio = SocketIO()
|
socketio = SocketIO()
|
||||||
kms_client = JosKMSClient()
|
jwt = JWTManager()
|
||||||
|
session = Session()
|
||||||
|
|
||||||
|
kms_client = JosKMSClient.from_service_account_json('config/gc_sa_eveai.json')
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
from ..extensions import db
|
from ..extensions import db
|
||||||
from .user import User, Tenant
|
from .user import User, Tenant
|
||||||
|
from .document import Embedding
|
||||||
|
|
||||||
|
|
||||||
class ChatSession(db.Model):
|
class ChatSession(db.Model):
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
user_id = db.Column(db.Integer, db.ForeignKey('public.user.id'), nullable=True)
|
user_id = db.Column(db.Integer, db.ForeignKey(User.id), nullable=True)
|
||||||
session_start = db.Column(db.DateTime, nullable=False)
|
session_start = db.Column(db.DateTime, nullable=False)
|
||||||
session_end = db.Column(db.DateTime, nullable=True)
|
session_end = db.Column(db.DateTime, nullable=True)
|
||||||
|
|
||||||
# Relations
|
# Relations
|
||||||
chat_interactions = db.relationship('Interaction', backref='chat_session', lazy=True)
|
interactions = db.relationship('Interaction', backref='chat_session', lazy=True)
|
||||||
user = db.relationship('User', backref='chat_sessions', lazy=True)
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<ChatSession {self.id} by {self.user_id}>"
|
return f"<ChatSession {self.id} by {self.user_id}>"
|
||||||
@@ -18,7 +18,7 @@ class ChatSession(db.Model):
|
|||||||
|
|
||||||
class Interaction(db.Model):
|
class Interaction(db.Model):
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
chat_session_id = db.Column(db.Integer, db.ForeignKey('public.chat_session.id'), nullable=False)
|
chat_session_id = db.Column(db.Integer, db.ForeignKey(ChatSession.id), nullable=False)
|
||||||
question = db.Column(db.Text, nullable=False)
|
question = db.Column(db.Text, nullable=False)
|
||||||
answer = db.Column(db.Text, nullable=True)
|
answer = db.Column(db.Text, nullable=True)
|
||||||
language = db.Column(db.String(2), nullable=False)
|
language = db.Column(db.String(2), nullable=False)
|
||||||
@@ -33,5 +33,5 @@ class Interaction(db.Model):
|
|||||||
|
|
||||||
|
|
||||||
class InteractionEmbedding(db.Model):
|
class InteractionEmbedding(db.Model):
|
||||||
interaction_id = db.Column(db.Integer, db.ForeignKey('interaction.id', ondelete='CASCADE'), primary_key=True)
|
interaction_id = db.Column(db.Integer, db.ForeignKey(Interaction.id, ondelete='CASCADE'), primary_key=True)
|
||||||
embedding_id = db.Column(db.Integer, db.ForeignKey('embedding.id', ondelete='CASCADE'), primary_key=True)
|
embedding_id = db.Column(db.Integer, db.ForeignKey(Embedding.id, ondelete='CASCADE'), primary_key=True)
|
||||||
|
|||||||
75
common/utils/cors_utils.py
Normal file
75
common/utils/cors_utils.py
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
from flask import request, current_app, session
|
||||||
|
from common.models.user import Tenant, TenantDomain
|
||||||
|
|
||||||
|
|
||||||
|
def get_allowed_origins(tenant_id):
|
||||||
|
session_key = f"allowed_origins_{tenant_id}"
|
||||||
|
if session_key in session:
|
||||||
|
current_app.logger.debug(f"Fetching allowed origins for tenant {tenant_id} from session")
|
||||||
|
return session[session_key]
|
||||||
|
|
||||||
|
current_app.logger.debug(f"Fetching allowed origins for tenant {tenant_id} from database")
|
||||||
|
tenant_domains = TenantDomain.query.filter_by(tenant_id=int(tenant_id)).all()
|
||||||
|
allowed_origins = [domain.domain for domain in tenant_domains]
|
||||||
|
|
||||||
|
# Cache the result in the session
|
||||||
|
session[session_key] = allowed_origins
|
||||||
|
return allowed_origins
|
||||||
|
|
||||||
|
|
||||||
|
def cors_after_request(response, prefix):
|
||||||
|
current_app.logger.debug(f'CORS after request: {request.path}, prefix: {prefix}')
|
||||||
|
current_app.logger.debug(f'request.headers: {request.headers}')
|
||||||
|
current_app.logger.debug(f'request.args: {request.args}')
|
||||||
|
current_app.logger.debug(f'request is json?: {request.is_json}')
|
||||||
|
|
||||||
|
tenant_id = None
|
||||||
|
allowed_origins = []
|
||||||
|
|
||||||
|
# Try to get tenant_id from JSON payload
|
||||||
|
json_data = request.get_json(silent=True)
|
||||||
|
current_app.logger.debug(f'request.get_json(silent=True): {json_data}')
|
||||||
|
|
||||||
|
if json_data and 'tenant_id' in json_data:
|
||||||
|
tenant_id = json_data['tenant_id']
|
||||||
|
else:
|
||||||
|
# Fallback to get tenant_id from query parameters or headers if JSON is not available
|
||||||
|
tenant_id = request.args.get('tenant_id') or request.args.get('tenantId') or request.headers.get('X-Tenant-ID')
|
||||||
|
|
||||||
|
current_app.logger.debug(f'Identified tenant_id: {tenant_id}')
|
||||||
|
|
||||||
|
if tenant_id:
|
||||||
|
allowed_origins = get_allowed_origins(tenant_id)
|
||||||
|
current_app.logger.debug(f'Allowed origins for tenant {tenant_id}: {allowed_origins}')
|
||||||
|
else:
|
||||||
|
current_app.logger.warning('tenant_id not found in request')
|
||||||
|
|
||||||
|
origin = request.headers.get('Origin')
|
||||||
|
current_app.logger.debug(f'Origin: {origin}')
|
||||||
|
|
||||||
|
if origin in allowed_origins:
|
||||||
|
response.headers.add('Access-Control-Allow-Origin', origin)
|
||||||
|
response.headers.add('Access-Control-Allow-Headers', 'Content-Type,Authorization')
|
||||||
|
response.headers.add('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE,OPTIONS')
|
||||||
|
response.headers.add('Access-Control-Allow-Credentials', 'true')
|
||||||
|
current_app.logger.debug(f'CORS headers set for origin: {origin}')
|
||||||
|
else:
|
||||||
|
current_app.logger.warning(f'Origin {origin} not allowed')
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
def create_cors_after_request(prefix):
|
||||||
|
def wrapped_cors_after_request(response):
|
||||||
|
return cors_after_request(response, prefix)
|
||||||
|
|
||||||
|
return wrapped_cors_after_request
|
||||||
|
|
||||||
|
|
||||||
|
def create_multiple_cors_after_requests(prefixes):
|
||||||
|
def wrapped_cors_after_requests(response):
|
||||||
|
for prefix, cors_function in prefixes:
|
||||||
|
response = cors_function(response)
|
||||||
|
return response
|
||||||
|
|
||||||
|
return wrapped_cors_after_requests
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
from google.cloud import kms
|
from google.cloud import kms_v1
|
||||||
from base64 import b64encode, b64decode
|
from base64 import b64encode, b64decode
|
||||||
from Crypto.Cipher import AES
|
from Crypto.Cipher import AES
|
||||||
from Crypto.Random import get_random_bytes
|
from Crypto.Random import get_random_bytes
|
||||||
import random
|
import random
|
||||||
|
import time
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
def generate_api_key(prefix="EveAI-Chat"):
|
def generate_api_key(prefix="EveAI-Chat"):
|
||||||
@@ -11,7 +13,7 @@ def generate_api_key(prefix="EveAI-Chat"):
|
|||||||
return f"{prefix}-{'-'.join(parts)}"
|
return f"{prefix}-{'-'.join(parts)}"
|
||||||
|
|
||||||
|
|
||||||
class JosKMSClient(kms.KeyManagementServiceClient):
|
class JosKMSClient(kms_v1.KeyManagementServiceClient):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.key_name = None
|
self.key_name = None
|
||||||
@@ -26,18 +28,36 @@ class JosKMSClient(kms.KeyManagementServiceClient):
|
|||||||
self.key_ring = app.config.get('GC_KEY_RING')
|
self.key_ring = app.config.get('GC_KEY_RING')
|
||||||
self.crypto_key = app.config.get('GC_CRYPTO_KEY')
|
self.crypto_key = app.config.get('GC_CRYPTO_KEY')
|
||||||
self.key_name = self.crypto_key_path(self.project_id, self.location, self.key_ring, self.crypto_key)
|
self.key_name = self.crypto_key_path(self.project_id, self.location, self.key_ring, self.crypto_key)
|
||||||
|
app.logger.info(f'Project ID: {self.project_id}')
|
||||||
|
app.logger.info(f'Location: {self.location}')
|
||||||
|
app.logger.info(f'Key Ring: {self.key_ring}')
|
||||||
|
app.logger.info(f'Crypto Key: {self.crypto_key}')
|
||||||
|
app.logger.info(f'Key Name: {self.key_name}')
|
||||||
|
|
||||||
|
app.logger.info(f'Service Account Key Path: {os.getenv('GOOGLE_APPLICATION_CREDENTIALS')}')
|
||||||
|
|
||||||
|
os.environ["GOOGLE_CLOUD_PROJECT"] = self.project_id
|
||||||
|
|
||||||
def encrypt_api_key(self, api_key):
|
def encrypt_api_key(self, api_key):
|
||||||
"""Encrypts the API key using the latest version of the KEK."""
|
"""Encrypts the API key using the latest version of the KEK."""
|
||||||
dek = get_random_bytes(32) # AES 256-bit key
|
dek = get_random_bytes(32) # AES 256-bit key
|
||||||
cipher = AES.new(dek, AES.MODE_GCM)
|
cipher = AES.new(dek, AES.MODE_GCM)
|
||||||
ciphertext, tag = cipher.encrypt_and_digest(api_key.encode())
|
ciphertext, tag = cipher.encrypt_and_digest(api_key.encode())
|
||||||
|
# print(f'Dek: {dek}')
|
||||||
|
|
||||||
# Encrypt the DEK using the latest version of the Google Cloud KMS key
|
# Encrypt the DEK using the latest version of the Google Cloud KMS key
|
||||||
encrypt_response = self.encrypt(
|
encrypt_response = self.encrypt(
|
||||||
request={'name': self.key_name, 'plaintext': dek}
|
request={'name': self.key_name, 'plaintext': dek}
|
||||||
)
|
)
|
||||||
encrypted_dek = encrypt_response.ciphertext
|
encrypted_dek = encrypt_response.ciphertext
|
||||||
|
# print(f"Encrypted DEK: {encrypted_dek}")
|
||||||
|
#
|
||||||
|
# # Check
|
||||||
|
# decrypt_response = self.decrypt(
|
||||||
|
# request={'name': self.key_name, 'ciphertext': encrypted_dek}
|
||||||
|
# )
|
||||||
|
# decrypted_dek = decrypt_response.plaintext
|
||||||
|
# print(f"Decrypted DEK: {decrypted_dek}")
|
||||||
|
|
||||||
# Store the version of the key used
|
# Store the version of the key used
|
||||||
key_version = encrypt_response.name
|
key_version = encrypt_response.name
|
||||||
@@ -53,17 +73,35 @@ class JosKMSClient(kms.KeyManagementServiceClient):
|
|||||||
def decrypt_api_key(self, encrypted_data):
|
def decrypt_api_key(self, encrypted_data):
|
||||||
"""Decrypts the API key using the specified key version."""
|
"""Decrypts the API key using the specified key version."""
|
||||||
key_version = encrypted_data['key_version']
|
key_version = encrypted_data['key_version']
|
||||||
encrypted_dek = b64decode(encrypted_data['encrypted_dek'])
|
key_name = self.key_name
|
||||||
nonce = b64decode(encrypted_data['nonce'])
|
encrypted_dek = b64decode(encrypted_data['encrypted_dek'].encode('utf-8'))
|
||||||
tag = b64decode(encrypted_data['tag'])
|
nonce = b64decode(encrypted_data['nonce'].encode('utf-8'))
|
||||||
ciphertext = b64decode(encrypted_data['ciphertext'])
|
tag = b64decode(encrypted_data['tag'].encode('utf-8'))
|
||||||
|
ciphertext = b64decode(encrypted_data['ciphertext'].encode('utf-8'))
|
||||||
|
|
||||||
# Decrypt the DEK using the specified version of the Google Cloud KMS key
|
# Decrypt the DEK using the specified version of the Google Cloud KMS key
|
||||||
decrypt_response = self.decrypt(
|
try:
|
||||||
request={'name': key_version, 'ciphertext': encrypted_dek}
|
decrypt_response = self.decrypt(
|
||||||
)
|
request={'name': key_name, 'ciphertext': encrypted_dek}
|
||||||
dek = decrypt_response.plaintext
|
)
|
||||||
|
dek = decrypt_response.plaintext
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Failed to decrypt DEK: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
cipher = AES.new(dek, AES.MODE_GCM, nonce=nonce)
|
cipher = AES.new(dek, AES.MODE_GCM, nonce=nonce)
|
||||||
api_key = cipher.decrypt_and_verify(ciphertext, tag)
|
api_key = cipher.decrypt_and_verify(ciphertext, tag)
|
||||||
return api_key.decode()
|
return api_key.decode()
|
||||||
|
|
||||||
|
def check_kms_access_and_latency(self):
|
||||||
|
# key_name = self.crypto_key_path(self.project_id, self.location, self.key_ring, self.crypto_key)
|
||||||
|
#
|
||||||
|
# start_time = time.time()
|
||||||
|
# try:
|
||||||
|
# response = self.get_crypto_key(name=key_name)
|
||||||
|
# end_time = time.time()
|
||||||
|
# print(f"Response Time: {end_time - start_time} seconds")
|
||||||
|
# print("Access to KMS is successful.")
|
||||||
|
# except Exception as e:
|
||||||
|
# print(f"Failed to access KMS: {e}")
|
||||||
|
pass
|
||||||
|
|||||||
@@ -64,8 +64,15 @@ class Config(object):
|
|||||||
```{text}```"""
|
```{text}```"""
|
||||||
|
|
||||||
# SocketIO settings
|
# SocketIO settings
|
||||||
|
# SOCKETIO_ASYNC_MODE = 'threading'
|
||||||
SOCKETIO_ASYNC_MODE = 'gevent'
|
SOCKETIO_ASYNC_MODE = 'gevent'
|
||||||
|
|
||||||
|
# Session Settings
|
||||||
|
SESSION_TYPE = 'redis'
|
||||||
|
SESSION_PERMANENT = False
|
||||||
|
SESSION_USE_SIGNER = True
|
||||||
|
SESSION_KEY_PREFIX = 'eveai_chat_'
|
||||||
|
|
||||||
|
|
||||||
class DevConfig(Config):
|
class DevConfig(Config):
|
||||||
DEVELOPMENT = True
|
DEVELOPMENT = True
|
||||||
@@ -107,6 +114,16 @@ class DevConfig(Config):
|
|||||||
GC_KEY_RING = 'eveai-chat'
|
GC_KEY_RING = 'eveai-chat'
|
||||||
GC_CRYPTO_KEY = 'envelope-encryption-key'
|
GC_CRYPTO_KEY = 'envelope-encryption-key'
|
||||||
|
|
||||||
|
# JWT settings
|
||||||
|
JWT_SECRET_KEY = 'bsdMkmQ8ObfMD52yAFg4trrvjgjMhuIqg2fjDpD/JqvgY0ccCcmlsEnVFmR79WPiLKEA3i8a5zmejwLZKl4v9Q=='
|
||||||
|
|
||||||
|
# Session settings
|
||||||
|
SESSION_REDIS = {
|
||||||
|
'host': 'localhost', # Redis server hostname or IP address
|
||||||
|
'port': 6379, # Redis server port
|
||||||
|
'db': 2, # Redis database number (optional)
|
||||||
|
'password': None # Redis password (optional)
|
||||||
|
}
|
||||||
|
|
||||||
class ProdConfig(Config):
|
class ProdConfig(Config):
|
||||||
DEVELOPMENT = False
|
DEVELOPMENT = False
|
||||||
|
|||||||
13
config/gc_sa_eveai.json
Normal file
13
config/gc_sa_eveai.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"type": "service_account",
|
||||||
|
"project_id": "eveai-420711",
|
||||||
|
"private_key_id": "e666408e75793321a6134243628346722a71b3a6",
|
||||||
|
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCaGTXCWpq08YD1\nOW4z+gncOlB7T/EIiEwsZgMp6pyUrNioGfiI9YN+uVR0nsUSmFf1YyerRgX7RqD5\nRc7T/OuX8iIvmloK3g7CaFezcVrjnBKcg/QsjDAt/OO3DTk4vykDlh/Kqxx73Jdv\nFH9YSV2H7ToWqIE8CTDnqe8vQS7Bq995c9fPlues31MgndRFg3CFkH0ldfZ4aGm3\n1RnBDyC+9SPQW9e7CJgNN9PWTmOT51Zyy5IRuV5OWePMQaGLVmCo5zNc/EHZEVRu\n1hxJPHL3NNmkYDY8tye8uHgjsAkv8QuwIuUSqnqjoo1/Yg+P0+9GCpePOAJRNxJS\n0YpDFWc5AgMBAAECggEACIU4/hG+bh97BD7JriFhfDDT6bg7g+pCs/hsAlxQ42jv\nOH7pyWuHJXGf5Cwx31usZAq4fcrgYnVpnyl8odIL628y9AjdI66wMuWhZnBFGJgK\nRhHcZWjW8nlXf0lBjwwFe4edzbn1AuWT5fYZ2HWDW2mthY/e8sUwqWPcWsjdifhz\nNR7V+Ia47McKXYgEKjyEObSP1NUOW24zH0DgxS52YPMwa1FoHn6+9Pr8P3TsTSO6\nh6f8tnd81DGl1UH4F5Bj/MHsQXyAMJbu44S4+rZ4Qlk+5xPp9hfCNpxWaHLIkJCg\nYXnC8UAjjyXiqyK0U0RjJf8TS1FxUI4iPepLNqp/pQKBgQDTicZnWFXmCFTnycWp\n66P3Yx0yvlKdUdfnoD/n9NdmUA3TZUlEVfb0IOm7ZFubF/zDTH87XrRiD/NVDbr8\n6bdhA1DXzraxhbfD36Hca6K74Ba4aYJsSWWwI0hL3FDSsv8c7qAIaUF2iwuHb7Y0\nRDcvZqowtQobcQC8cHLc/bI/ZwKBgQC6fMeGaU+lP6jhp9Nb/3Gz5Z1zzCu34IOo\nlgpTNZsowRKYLtjHifrEFi3XRxPKz5thMuJFniof5U4WoMYtRXy+PbgySvBpCia2\nXty05XssnLLMvLpYU5sbQvmOTe20zaIzLohRvvmqrydYIKu62NTubNeuD1L+Zr0q\nz1P5/wUgXwKBgQCW9MrRFQi3j1qHzkVwbOglsmUzwP3TpoQclw8DyIWuTZKQOMeA\nLJh+vr4NLCDzHLsT45MoGv0+vYM4PwQhV+e1I1idqLZXGMV60iv/0A/hYpjUIPch\nr38RoxwEhsRml7XWP7OUTQiaP7+Kdv3fbo6zFOB+wbLkwk90KgrOCX0aIQKBgFeK\n7esmErJjMPdFXk3om0q09nX+mWNHLOb+EDjBiGXYRM9V5oO9PQ/BzaEqh5sEXE+D\noH7H4cR5U3AB5yYnYYi41ngdf7//eO7Rl1AADhOCN9kum1eNX9mrVhU8deMTSRo3\ntNyTBwbeFF0lcRhUY5jNVW4rWW19cz3ed/B6i8CHAoGBAJ/l5rkV74Z5hg6BWNfQ\nYAg/4PLZmjnXIy5QdnWc/PYgbhn5+iVUcL9fSofFzJM1rjFnNcs3S90MGeOmfmo4\nM1WtcQFQbsCGt6+G5uEL/nf74mKUGpOqEM/XSkZ3inweWiDk3LK3iYfXCMBFouIr\n80IlzI1yMf7MVmWn3e1zPjCA\n-----END PRIVATE KEY-----\n",
|
||||||
|
"client_email": "eveai-349@eveai-420711.iam.gserviceaccount.com",
|
||||||
|
"client_id": "109927035346319712442",
|
||||||
|
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
||||||
|
"token_uri": "https://oauth2.googleapis.com/token",
|
||||||
|
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
|
||||||
|
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/eveai-349%40eveai-420711.iam.gserviceaccount.com",
|
||||||
|
"universe_domain": "googleapis.com"
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ import logging.config
|
|||||||
|
|
||||||
from common.extensions import db, migrate, bootstrap, security, mail, login_manager, cors, kms_client
|
from common.extensions import db, migrate, bootstrap, security, mail, login_manager, cors, kms_client
|
||||||
from common.models.user import User, Role, Tenant, TenantDomain
|
from common.models.user import User, Role, Tenant, TenantDomain
|
||||||
|
import common.models.interaction
|
||||||
from config.logging_config import LOGGING
|
from config.logging_config import LOGGING
|
||||||
from common.utils.security import set_tenant_session_data
|
from common.utils.security import set_tenant_session_data
|
||||||
from .errors import register_error_handlers
|
from .errors import register_error_handlers
|
||||||
@@ -29,7 +30,6 @@ def create_app(config_file=None):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
logging.config.dictConfig(LOGGING)
|
logging.config.dictConfig(LOGGING)
|
||||||
print(__name__)
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
logger.info("eveai_app starting up")
|
logger.info("eveai_app starting up")
|
||||||
@@ -38,12 +38,12 @@ def create_app(config_file=None):
|
|||||||
|
|
||||||
register_extensions(app)
|
register_extensions(app)
|
||||||
|
|
||||||
|
# Check GCloud availability
|
||||||
|
kms_client.check_kms_access_and_latency()
|
||||||
|
|
||||||
app.celery = make_celery(app.name, app.config)
|
app.celery = make_celery(app.name, app.config)
|
||||||
init_celery(app.celery, app)
|
init_celery(app.celery, app)
|
||||||
|
|
||||||
print(app.celery.conf.broker_url)
|
|
||||||
print(app.celery.conf.result_backend)
|
|
||||||
|
|
||||||
# Setup Flask-Security-Too
|
# Setup Flask-Security-Too
|
||||||
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
|
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
|
||||||
security.init_app(app, user_datastore)
|
security.init_app(app, user_datastore)
|
||||||
@@ -69,6 +69,8 @@ def create_app(config_file=None):
|
|||||||
# Register API
|
# Register API
|
||||||
register_api(app)
|
register_api(app)
|
||||||
|
|
||||||
|
app.logger.info("EveAI App Server Started Successfully")
|
||||||
|
app.logger.info("-------------------------------------------------------------------------------------------------")
|
||||||
return app
|
return app
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,43 +3,42 @@
|
|||||||
|
|
||||||
{% block title %}Tenant Selection{% endblock %}
|
{% block title %}Tenant Selection{% endblock %}
|
||||||
|
|
||||||
{% block content_title %}Select a Tenant{% endblock %}
|
{% block content_title %}Select a Tenant{% endblock %}
|
||||||
{% block content_description %}Select the active tenant for the current session{% endblock %}
|
{% block content_description %}Select the active tenant for the current session{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<form method="POST" action="{{ url_for('user_bp.handle_tenant_selection') }}">
|
<form method="POST" action="{{ url_for('user_bp.handle_tenant_selection') }}">
|
||||||
{{ render_selectable_table(headers=["Tenant ID", "Tenant Name","Website"], rows=tenants, selectable=True, id="tenantsTable") }}
|
{{ render_selectable_table(headers=["Tenant ID", "Tenant Name", "Website"], rows=tenants, selectable=True, id="tenantsTable") }}
|
||||||
<div class="form-group mt-3">
|
<div class="form-group mt-3">
|
||||||
<button type="submit" name="action" value="select_tenant" class="btn btn-primary">Set Session Tenant</button>
|
<button type="submit" name="action" value="select_tenant" class="btn btn-primary">Set Session Tenant</button>
|
||||||
<button type="submit" name="action" value="view_users" class="btn btn-secondary">View Users</button>
|
<button type="submit" name="action" value="view_users" class="btn btn-secondary">View Users</button>
|
||||||
<button type="submit" name="action" value="edit_tenant" class="btn btn-secondary">Edit Tenant</button>
|
<button type="submit" name="action" value="edit_tenant" class="btn btn-secondary">Edit Tenant</button>
|
||||||
<!-- Add more buttons as needed -->
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
<script>
|
<script>
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
$('#tenantsTable').DataTable({
|
$('#tenantsTable').DataTable({
|
||||||
'columnDefs': [{
|
'columnDefs': [
|
||||||
'targets': 0,
|
{
|
||||||
'searchable': false,
|
'targets': 0,
|
||||||
'orderable': false,
|
'searchable': false,
|
||||||
'className': 'dt-body-center',
|
'orderable': false,
|
||||||
'render': function (data, type, full, meta){
|
'className': 'dt-body-center',
|
||||||
return '<input type="radio" name="user_id" value="' + $('<div/>').text(data).html() + '">';
|
},
|
||||||
|
{
|
||||||
|
'targets': 1,
|
||||||
|
'orderable': true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'targets': 2,
|
||||||
|
'orderable': true
|
||||||
}
|
}
|
||||||
},
|
],
|
||||||
{
|
|
||||||
'targets': 1,
|
|
||||||
'orderable': true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'targets': 2,
|
|
||||||
'orderable': true
|
|
||||||
}],
|
|
||||||
'order': [[1, 'asc']]
|
'order': [[1, 'asc']]
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import logging
|
import logging
|
||||||
import logging.config
|
import logging.config
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
from flask_socketio import emit
|
from redis import Redis
|
||||||
|
|
||||||
from common.extensions import db, socketio
|
from common.extensions import db, socketio, jwt, kms_client, cors, session
|
||||||
from config.logging_config import LOGGING
|
from config.logging_config import LOGGING
|
||||||
|
from eveai_chat.socket_handlers import chat_handler
|
||||||
|
from common.utils.cors_utils import create_cors_after_request
|
||||||
|
|
||||||
|
|
||||||
def create_app(config_file=None):
|
def create_app(config_file=None):
|
||||||
@@ -18,6 +20,15 @@ def create_app(config_file=None):
|
|||||||
logging.config.dictConfig(LOGGING)
|
logging.config.dictConfig(LOGGING)
|
||||||
register_extensions(app)
|
register_extensions(app)
|
||||||
|
|
||||||
|
# Register Blueprints
|
||||||
|
register_blueprints(app)
|
||||||
|
|
||||||
|
@app.route('/ping')
|
||||||
|
def ping():
|
||||||
|
return 'pong'
|
||||||
|
|
||||||
|
app.logger.info("EveAI Chat Server Started Successfully")
|
||||||
|
app.logger.info("-------------------------------------------------------------------------------------------------")
|
||||||
return app
|
return app
|
||||||
|
|
||||||
|
|
||||||
@@ -29,5 +40,25 @@ def register_extensions(app):
|
|||||||
async_mode=app.config.get('SOCKETIO_ASYNC_MODE'),
|
async_mode=app.config.get('SOCKETIO_ASYNC_MODE'),
|
||||||
logger=app.config.get('SOCKETIO_LOGGER'),
|
logger=app.config.get('SOCKETIO_LOGGER'),
|
||||||
engineio_logger=app.config.get('SOCKETIO_ENGINEIO_LOGGER'),
|
engineio_logger=app.config.get('SOCKETIO_ENGINEIO_LOGGER'),
|
||||||
|
path='/socket.io'
|
||||||
)
|
)
|
||||||
|
jwt.init_app(app)
|
||||||
|
kms_client.init_app(app)
|
||||||
|
|
||||||
|
# Cors setup
|
||||||
|
cors.init_app(app, resources={r"/chat/*": {"origins": "*"}})
|
||||||
|
app.after_request(create_cors_after_request('/chat'))
|
||||||
|
|
||||||
|
# Session setup
|
||||||
|
# redis_config = app.config['SESSION_REDIS']
|
||||||
|
# redis_client = Redis(host=redis_config['host'],
|
||||||
|
# port=redis_config['port'],
|
||||||
|
# db=redis_config['db'],
|
||||||
|
# password=redis_config['password']
|
||||||
|
# )
|
||||||
|
session.init_app(app)
|
||||||
|
|
||||||
|
|
||||||
|
def register_blueprints(app):
|
||||||
|
from .views.chat_views import chat_bp
|
||||||
|
app.register_blueprint(chat_bp)
|
||||||
|
|||||||
52
eveai_chat/socket_handlers/chat_handler.py
Normal file
52
eveai_chat/socket_handlers/chat_handler.py
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
from flask_jwt_extended import verify_jwt_in_request, get_jwt_identity, verify_jwt_in_request, decode_token
|
||||||
|
from flask_socketio import emit, disconnect
|
||||||
|
from flask import current_app, request
|
||||||
|
from common.extensions import socketio
|
||||||
|
|
||||||
|
|
||||||
|
@socketio.on('connect')
|
||||||
|
def handle_connect():
|
||||||
|
try:
|
||||||
|
# Extract token from the auth object
|
||||||
|
token = request.args.get('token')
|
||||||
|
if not token:
|
||||||
|
raise Exception("Missing Authorization Token")
|
||||||
|
current_app.logger.debug(f'SocketIO: Received token: {token}')
|
||||||
|
# Verify token
|
||||||
|
decoded_token = decode_token(token.split(" ")[1]) # Split to remove "Bearer " prefix
|
||||||
|
tenant_id = decoded_token["identity"]["tenant_id"]
|
||||||
|
current_app.logger.info(f'SocketIO: Tenant {decoded_token["identity"]["tenant_id"]} connected')
|
||||||
|
# communicate connection to client
|
||||||
|
emit('connect', {'status': 'Connected', 'tenant_id': tenant_id})
|
||||||
|
except Exception as e:
|
||||||
|
current_app.logger.error(f'SocketIO: Connection failed: {e}')
|
||||||
|
# communicate connection problem to client
|
||||||
|
emit('connect', {'status': 'Connection Failed'})
|
||||||
|
disconnect()
|
||||||
|
|
||||||
|
|
||||||
|
@socketio.on('disconnect')
|
||||||
|
def handle_disconnect():
|
||||||
|
current_app.logger.debug('SocketIO: Client disconnected')
|
||||||
|
|
||||||
|
|
||||||
|
@socketio.on('user_message')
|
||||||
|
def handle_message(data):
|
||||||
|
try:
|
||||||
|
current_app.logger.debug(f"SocketIO: Received message from tenant {data['tenantId']}: {data['message']}")
|
||||||
|
verify_jwt_in_request()
|
||||||
|
current_tenant = get_jwt_identity()
|
||||||
|
print(f'Tenant {current_tenant["tenant_id"]} sent a message: {data}')
|
||||||
|
# Store interaction in the database
|
||||||
|
response = {
|
||||||
|
'tenantId': data['tenantId'],
|
||||||
|
'message': 'This is a bot response. Actual implementation still required.',
|
||||||
|
'messageId': 'bot-message-id',
|
||||||
|
'algorithm': 'alg1'
|
||||||
|
}
|
||||||
|
current_app.logger.debug(f"SocketIO: Bot response: {response}")
|
||||||
|
emit('bot_response', response, broadcast=True)
|
||||||
|
except Exception as e:
|
||||||
|
current_app.logger.error(f'SocketIO: Message handling failed: {e}')
|
||||||
|
disconnect()
|
||||||
|
|
||||||
66
eveai_chat/static/eve_ai_chat.html
Normal file
66
eveai_chat/static/eve_ai_chat.html
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Chat Client</title>
|
||||||
|
<script src="https://cdn.socket.io/4.0.0/socket.io.min.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Chat Client</h1>
|
||||||
|
<button onclick="registerClient()">Register Client</button>
|
||||||
|
<div>
|
||||||
|
<input type="text" id="message" placeholder="Enter your message">
|
||||||
|
<button onclick="sendMessage()">Send Message</button>
|
||||||
|
</div>
|
||||||
|
<div id="messages"></div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
let socket;
|
||||||
|
|
||||||
|
async function registerClient() {
|
||||||
|
const tenantId = '1';
|
||||||
|
const apiKey = 'EveAI-CHAT-8553-7987-2800-9115-6454';
|
||||||
|
|
||||||
|
const response = await fetch('http://127.0.0.1:5001/chat/register_client', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ tenant_id: tenantId, api_key: apiKey })
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const data = await response.json();
|
||||||
|
const token = data.token;
|
||||||
|
|
||||||
|
socket = io('http://127.0.0.1:5001/chat', {
|
||||||
|
auth: {
|
||||||
|
token: `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('connect', () => {
|
||||||
|
console.log('Connected to server');
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('response', (msg) => {
|
||||||
|
const messagesDiv = document.getElementById('messages');
|
||||||
|
const messageElement = document.createElement('div');
|
||||||
|
messageElement.innerText = `Response: ${msg.data}`;
|
||||||
|
messagesDiv.appendChild(messageElement);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.error('Registration failed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendMessage() {
|
||||||
|
const message = document.getElementById('message').value;
|
||||||
|
if (socket) {
|
||||||
|
socket.emit('message', { content: message });
|
||||||
|
} else {
|
||||||
|
console.error('Socket not connected');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
20
eveai_chat/static/eveai_chat.html
Normal file
20
eveai_chat/static/eveai_chat.html
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Chat Client</title>
|
||||||
|
<link rel="stylesheet" href="css/eveai-chat-style.css">
|
||||||
|
<script src="js/eveai-sdk.js" defer></script>
|
||||||
|
<script src="js/eveai-chat-widget.js" defer></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="chat-container"></div>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const eveAI = new EveAI('tenant-id', 'EVEAI-CHAT-xxxx-xxxx-xxxx-xxxx-xxxx');
|
||||||
|
eveAI.initializeChat('chat-container');
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -1,34 +1,75 @@
|
|||||||
# from . import user_bp
|
|
||||||
import uuid
|
|
||||||
from datetime import datetime as dt, timezone as tz
|
from datetime import datetime as dt, timezone as tz
|
||||||
from flask import request, redirect, url_for, flash, render_template, Blueprint, session, current_app
|
from flask import request, redirect, url_for, render_template, Blueprint, session, current_app, jsonify
|
||||||
from flask_security import hash_password, roles_required, roles_accepted
|
from flask_security import hash_password, roles_required, roles_accepted
|
||||||
from sqlalchemy.exc import SQLAlchemyError
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
|
from flask_jwt_extended import create_access_token, jwt_required, get_jwt_identity
|
||||||
|
from flask_socketio import emit, join_room, leave_room
|
||||||
|
import ast
|
||||||
|
|
||||||
|
|
||||||
from common.models.user import User, Tenant
|
from common.models.user import User, Tenant
|
||||||
from common.models.interaction import ChatSession, Interaction, InteractionEmbedding
|
from common.models.interaction import ChatSession, Interaction, InteractionEmbedding
|
||||||
from common.models.document import Embedding
|
from common.models.document import Embedding
|
||||||
from common.extensions import db, socketio
|
from common.extensions import db, socketio, kms_client
|
||||||
from common.utils.database import Database
|
from common.utils.database import Database
|
||||||
|
|
||||||
chat_bp = Blueprint('chat_bp', __name__, url_prefix='/chat')
|
chat_bp = Blueprint('chat_bp', __name__, url_prefix='/chat')
|
||||||
|
|
||||||
|
|
||||||
@chat_bp.route('/', methods=['GET', 'POST'])
|
@chat_bp.route('/register_client', methods=['POST'])
|
||||||
def chat():
|
def register_client():
|
||||||
return render_template('chat.html')
|
tenant_id = request.json.get('tenant_id')
|
||||||
|
api_key = request.json.get('api_key')
|
||||||
|
|
||||||
|
# Validate tenant_id and api_key here (e.g., check against the database)
|
||||||
|
if validate_tenant(tenant_id, api_key):
|
||||||
|
access_token = create_access_token(identity={'tenant_id': tenant_id, 'api_key': api_key})
|
||||||
|
return jsonify({'token': access_token}), 200
|
||||||
|
else:
|
||||||
|
return jsonify({'message': 'Invalid credentials'}), 401
|
||||||
|
|
||||||
|
|
||||||
@chat.record_once
|
@socketio.on('connect', namespace='/chat')
|
||||||
def on_register(state):
|
@jwt_required()
|
||||||
# TODO: write initialisation code when the blueprint is registered (only once)
|
def handle_connect():
|
||||||
# socketio.init_app(state.app)
|
current_tenant = get_jwt_identity()
|
||||||
pass
|
print(f'Tenant {current_tenant["tenant_id"]} connected')
|
||||||
|
|
||||||
|
|
||||||
@socketio.on('message', namespace='/chat')
|
@socketio.on('message', namespace='/chat')
|
||||||
def handle_message(message):
|
@jwt_required()
|
||||||
# TODO: write message handling code to actually realise chat
|
def handle_message(data):
|
||||||
# print('Received message:', message)
|
current_tenant = get_jwt_identity()
|
||||||
# socketio.emit('response', {'data': message}, namespace='/chat')
|
print(f'Tenant {current_tenant["tenant_id"]} sent a message: {data}')
|
||||||
pass
|
# Store interaction in the database
|
||||||
|
emit('response', {'data': 'Message received'}, broadcast=True)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_tenant(tenant_id, api_key):
|
||||||
|
tenant = Tenant.query.get_or_404(tenant_id)
|
||||||
|
encrypted_api_key = ast.literal_eval(tenant.encrypted_chat_api_key)
|
||||||
|
|
||||||
|
decrypted_api_key = kms_client.decrypt_api_key(encrypted_api_key)
|
||||||
|
|
||||||
|
return decrypted_api_key == api_key
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# @chat_bp.route('/', methods=['GET', 'POST'])
|
||||||
|
# def chat():
|
||||||
|
# return render_template('chat.html')
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# @chat.record_once
|
||||||
|
# def on_register(state):
|
||||||
|
# # TODO: write initialisation code when the blueprint is registered (only once)
|
||||||
|
# # socketio.init_app(state.app)
|
||||||
|
# pass
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# @socketio.on('message', namespace='/chat')
|
||||||
|
# def handle_message(message):
|
||||||
|
# # TODO: write message handling code to actually realise chat
|
||||||
|
# # print('Received message:', message)
|
||||||
|
# # socketio.emit('response', {'data': message}, namespace='/chat')
|
||||||
|
# pass
|
||||||
|
|||||||
1
external/nginx.conf
vendored
Symbolic link
1
external/nginx.conf
vendored
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
/opt/homebrew/etc/nginx/nginx.conf
|
||||||
25
public/chat.html
Normal file
25
public/chat.html
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Chat Client</title>
|
||||||
|
<link rel="stylesheet" href="/static/css/eveai-chat-style.css">
|
||||||
|
<script src="https://cdn.socket.io/4.0.1/socket.io.min.js"></script>
|
||||||
|
<script src="/static/js/eveai-sdk.js" defer></script>
|
||||||
|
<script src="/static/js/eveai-chat-widget.js" defer></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="chat-container"></div>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const eveAI = new EveAI(
|
||||||
|
'1',
|
||||||
|
'EveAI-CHAT-8553-7987-2800-9115-6454',
|
||||||
|
'http://macstudio.ask-eve-ai-local.com'
|
||||||
|
);
|
||||||
|
eveAI.initializeChat('chat-container');
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
11
public/index.html
Normal file
11
public/index.html
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>EveAI</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1 style="text-align: center;">EveAI</h1>
|
||||||
|
<p style="text-align: center;">Ja ja, ge zijt gearriveerd ;-)</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -1,14 +1,32 @@
|
|||||||
from eveai_chat import create_app
|
import os
|
||||||
from gevent.pywsgi import WSGIServer
|
|
||||||
from geventwebsocket.handler import WebSocketHandler
|
|
||||||
|
|
||||||
|
# Determine if we are in debug mode
|
||||||
|
debug_mode = os.environ.get('CHAT_DEBUG', 'True').lower() == 'true'
|
||||||
|
|
||||||
|
# Only monkey patch if not in debug mode
|
||||||
|
if not debug_mode:
|
||||||
|
from gevent import monkey
|
||||||
|
monkey.patch_all()
|
||||||
|
|
||||||
|
from eveai_chat import create_app
|
||||||
|
from common.extensions import socketio
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
|
|
||||||
app = create_app()
|
app = create_app()
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
print("Server starting on port 5001")
|
if debug_mode:
|
||||||
http_server = WSGIServer(('0.0.0.0', 5001), app, handler_class=WebSocketHandler)
|
logging.info("Starting Flask application in debug mode")
|
||||||
http_server.serve_forever() # Continuously listens for incoming requests
|
app.config['DEBUG'] = True # Enable debug mode in Flask
|
||||||
|
app.config['ENV'] = 'development'
|
||||||
|
socketio.run(app, debug=True, host='0.0.0.0', port=5001, allow_unsafe_werkzeug=True) # Use Flask's built-in server for debugging
|
||||||
|
else:
|
||||||
|
logging.info("Starting Flask application with gevent WSGI server")
|
||||||
|
from gevent.pywsgi import WSGIServer
|
||||||
|
from geventwebsocket.handler import WebSocketHandler
|
||||||
|
http_server = WSGIServer(('0.0.0.0', 5001), app, handler_class=WebSocketHandler)
|
||||||
|
http_server.serve_forever()
|
||||||
|
|
||||||
|
logging.info("Application started")
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ export FLASK_ENV=development # Use 'production' as appropriate
|
|||||||
export FLASK_DEBUG=1 # Use 0 for production
|
export FLASK_DEBUG=1 # Use 0 for production
|
||||||
|
|
||||||
# Start Flask app
|
# Start Flask app
|
||||||
python scripts/run_eveai_chat.py
|
gunicorn --workers 4 --worker-class gevent -b 0.0.0.0:5001 scripts.run_eveai_chat:app &
|
||||||
|
|
||||||
|
wait
|
||||||
|
|
||||||
deactivate
|
deactivate
|
||||||
77
static/css/eveai-chat-style.css
Normal file
77
static/css/eveai-chat-style.css
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
/* eveai_chat.css */
|
||||||
|
:root {
|
||||||
|
--user-message-bg: #d1e7dd; /* Default user message background color */
|
||||||
|
--bot-message-bg: #ffffff; /* Default bot message background color */
|
||||||
|
--chat-bg: #f8f9fa; /* Default chat background color */
|
||||||
|
--algorithm-color-default: #ccc; /* Default algorithm indicator color */
|
||||||
|
--algorithm-color-alg1: #f00; /* Algorithm 1 color */
|
||||||
|
--algorithm-color-alg2: #0f0; /* Algorithm 2 color */
|
||||||
|
--algorithm-color-alg3: #00f; /* Algorithm 3 color */
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100vh;
|
||||||
|
max-width: 600px;
|
||||||
|
margin: auto;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: var(--chat-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message.user {
|
||||||
|
margin-left: auto;
|
||||||
|
background-color: var(--user-message-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.message.bot {
|
||||||
|
background-color: var(--bot-message-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-icons {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-icons i {
|
||||||
|
margin-left: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.question-area {
|
||||||
|
padding: 10px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background-color: var(--user-message-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.question-area input {
|
||||||
|
flex: 1;
|
||||||
|
border: none;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 15px;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.question-area button {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #007bff;
|
||||||
|
}
|
||||||
172
static/js/eveai-chat-widget.js
Normal file
172
static/js/eveai-chat-widget.js
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
// static/js/eveai-chat-widget.js
|
||||||
|
class EveAIChatWidget extends HTMLElement {
|
||||||
|
static get observedAttributes() {
|
||||||
|
return ['tenant-id', 'api-key', 'domain'];
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.socket = null; // Initialize socket to null
|
||||||
|
this.attributesSet = false; // Flag to check if all attributes are set
|
||||||
|
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 input');
|
||||||
|
|
||||||
|
this.querySelector('.question-area button').addEventListener('click', () => this.handleSendMessage());
|
||||||
|
|
||||||
|
if (this.areAllAttributesSet() && !this.socket) {
|
||||||
|
console.log('Attributes already set in connectedCallback, initializing socket');
|
||||||
|
this.initializeSocket();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
attributeChangedCallback(name, oldValue, newValue) {
|
||||||
|
console.log(`attributeChangedCallback called: ${name} changed from ${oldValue} to ${newValue}`);
|
||||||
|
this.updateAttributes();
|
||||||
|
console.log('Current attributes:', {
|
||||||
|
tenantId: this.getAttribute('tenant-id'),
|
||||||
|
apiKey: this.getAttribute('api-key'),
|
||||||
|
domain: this.getAttribute('domain')
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.areAllAttributesSet() && !this.socket) {
|
||||||
|
console.log('All attributes set in attributeChangedCallback, initializing socket');
|
||||||
|
this.attributesSet = true;
|
||||||
|
this.initializeSocket();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateAttributes() {
|
||||||
|
console.log('Updating attributes:');
|
||||||
|
this.tenantId = this.getAttribute('tenant-id');
|
||||||
|
this.apiKey = this.getAttribute('api-key');
|
||||||
|
this.domain = this.getAttribute('domain');
|
||||||
|
console.log('Updated attributes:', {
|
||||||
|
tenantId: this.tenantId,
|
||||||
|
apiKey: this.apiKey,
|
||||||
|
domain: this.domain
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
areAllAttributesSet() {
|
||||||
|
const tenantId = this.getAttribute('tenant-id');
|
||||||
|
const apiKey = this.getAttribute('api-key');
|
||||||
|
const domain = this.getAttribute('domain');
|
||||||
|
console.log('Checking if all attributes are set:', {
|
||||||
|
tenantId,
|
||||||
|
apiKey,
|
||||||
|
domain
|
||||||
|
});
|
||||||
|
return tenantId && apiKey && domain;
|
||||||
|
}
|
||||||
|
|
||||||
|
initializeSocket() {
|
||||||
|
if (this.socket) {
|
||||||
|
console.log('Socket already initialized');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this.domain || this.domain === 'null') {
|
||||||
|
console.error('Domain attribute is missing or invalid');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log(`Initializing socket connection to ${this.domain}`);
|
||||||
|
|
||||||
|
const token = 'Bearer ' + this.apiKey
|
||||||
|
|
||||||
|
// Include tenantId in query parameters
|
||||||
|
this.socket = io(this.domain, {
|
||||||
|
path: '/chat/socket.io/',
|
||||||
|
transports: ['websocket', 'polling'],
|
||||||
|
auth: {
|
||||||
|
token: token // Add the token to the authentication object
|
||||||
|
},
|
||||||
|
query: {
|
||||||
|
tenantId: this.tenantId,
|
||||||
|
// apiKey: this.apiKey
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
this.socket.on('connect', () => {
|
||||||
|
console.log('Socket connected');
|
||||||
|
});
|
||||||
|
|
||||||
|
this.socket.on('connect_error', (err) => {
|
||||||
|
console.error('Socket connection error:', err);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.socket.on('connect_timeout', () => {
|
||||||
|
console.error('Socket connection timeout')
|
||||||
|
});
|
||||||
|
|
||||||
|
this.socket.on('disconnect', () => {
|
||||||
|
console.log('Socket disconnected');
|
||||||
|
});
|
||||||
|
|
||||||
|
this.socket.on('bot_response', (data) => {
|
||||||
|
if (data.tenantId === this.tenantId) {
|
||||||
|
this.addMessage(data.message, 'bot', data.messageId, data.algorithm);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getTemplate() {
|
||||||
|
return `
|
||||||
|
<div class="chat-container">
|
||||||
|
<div class="messages-area"></div>
|
||||||
|
<div class="question-area">
|
||||||
|
<input type="text" placeholder="Type your message here..." />
|
||||||
|
<button>Send</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
addMessage(text, type = 'user', id = null, algorithm = 'default') {
|
||||||
|
const message = document.createElement('div');
|
||||||
|
message.classList.add('message', type);
|
||||||
|
message.innerHTML = `
|
||||||
|
<p>${text}</p>
|
||||||
|
${type === 'bot' ? `
|
||||||
|
<div class="message-icons">
|
||||||
|
<span class="algorithm-indicator" style="background-color: var(--algorithm-color-${algorithm});"></span>
|
||||||
|
<i class="material-icons" onclick="handleFeedback('${id}', 'up')">thumb_up</i>
|
||||||
|
<i class="material-icons" onclick="handleFeedback('${id}', 'down')">thumb_down</i>
|
||||||
|
</div>` : ''}
|
||||||
|
`;
|
||||||
|
this.messagesArea.appendChild(message);
|
||||||
|
this.messagesArea.scrollTop = this.messagesArea.scrollHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSendMessage() {
|
||||||
|
console.log('handleSendMessage called');
|
||||||
|
const message = this.questionInput.value.trim();
|
||||||
|
if (message) {
|
||||||
|
this.addMessage(message, 'user');
|
||||||
|
this.questionInput.value = '';
|
||||||
|
this.sendMessageToBackend(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sendMessageToBackend(message) {
|
||||||
|
console.log('sendMessageToBackend called');
|
||||||
|
if (!this.socket) {
|
||||||
|
console.error('Socket is not initialized');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log('Sending message to backend');
|
||||||
|
this.socket.emit('user_message', { tenantId: this.tenantId, apiKey: this.apiKey, message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define('eveai-chat-widget', EveAIChatWidget);
|
||||||
|
|
||||||
|
function handleFeedback(messageId, feedback) {
|
||||||
|
// Send feedback to the backend
|
||||||
|
console.log(`Feedback for ${messageId}: ${feedback}`);
|
||||||
|
// Implement the actual feedback mechanism
|
||||||
|
}
|
||||||
29
static/js/eveai-sdk.js
Normal file
29
static/js/eveai-sdk.js
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
// static/js/eveai-sdk.js
|
||||||
|
class EveAI {
|
||||||
|
constructor(tenantId, apiKey, domain) {
|
||||||
|
this.tenantId = tenantId;
|
||||||
|
this.apiKey = apiKey;
|
||||||
|
this.domain = domain;
|
||||||
|
console.log('EveAI constructor:', { tenantId, apiKey, domain });
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
console.log('Attributes set in chat widget:', {
|
||||||
|
tenantId: chatWidget.getAttribute('tenant-id'),
|
||||||
|
apiKey: chatWidget.getAttribute('api-key'),
|
||||||
|
domain: chatWidget.getAttribute('domain')
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.error('Container not found');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user