36 Commits

Author SHA1 Message Date
Joakim Persson 9203641e6a Inläsning av apiEndpoint och useModel har flyttats hit. Funktioner och variabler från frontend.js exporteras numer via frontendApi 2024-07-31 15:46:18 +02:00
Joakim Persson 14eaf57335 Ökat upp chatfönstrets höjd vid start 2024-07-31 15:44:18 +02:00
Joakim Persson 9ae5840e8b Flyttat inläsning av apiEndpoint och useModel till frontend.js 2024-07-31 15:43:43 +02:00
Joakim Persson 659b0937d3 Changed to phi3:mini 2024-07-31 15:42:32 +02:00
joakimp 7bc8a129c6 Använder nu postMessage för att få över variabler till iframe 2024-07-30 23:47:16 +02:00
joakimp a12c11c058 Arbetar med att göra externt skapade variabler tillgängliga. Safari krävre annnan hantering än övriga webläsare 2024-07-30 23:46:37 +02:00
joakimp 5771e52c49 Händelsestyrd läsning av apiEndpoint och useModel 2024-07-30 23:45:07 +02:00
joakimp d038f1ac24 Lag till hantering av backend API endpoint 2024-07-30 23:42:56 +02:00
joakimp 86374c91fe Delar variabler enhetligt nu genom global_state 2024-07-30 23:41:42 +02:00
joakimp 0adbb9c222 Lagt till svaren till chat-historiken. Hanterar och delar variabler enhetligt med global_state, inte miljövariabler. 2024-07-30 23:33:00 +02:00
joakimp c3d7f3ba4f Test med annan LLM 2024-07-30 23:30:21 +02:00
Joakim Persson d864d7529a Konfigurerar parser. Städat upp från bortkommenterad kod. 2024-07-30 16:42:59 +02:00
Joakim Persson 0b971dffc4 Ändrat på färger i chat-fönstret 2024-07-30 16:42:09 +02:00
Joakim Persson ecf45bd2e7 Importerar plugin till markdown-it för tabeller och annat. 2024-07-30 16:41:37 +02:00
Joakim Persson b88e573761 Lagt till chat-historik 2024-07-30 16:40:47 +02:00
Joakim Persson ecf2ddf46e Formatting output using markdown-it and MathJax 2024-07-30 14:32:00 +02:00
Joakim Persson b4a35bafef Replace Marked with markdown-it. Added some configuration of MathJax 2024-07-30 14:30:13 +02:00
joakimp de3e59afea Rendrering av markdown-formatterade svar. Experimenterar med MathJax. Ändrar färg på knappen när man trycker på Retur-tangenten 2024-07-30 00:16:55 +02:00
joakimp 9f5bdddb8f Förbättrat det visuella genom att ändra färg på knapp och ta bort storleksbegränsning på chatt-rutan 2024-07-30 00:14:54 +02:00
joakimp 854ebbdd94 Läser in Marked och MathJax för rendrering 2024-07-30 00:13:34 +02:00
joakimp 7d954e7380 Ändrade till phi3:mini som standardmodell för dess lilla storlek och snabbhet 2024-07-30 00:12:05 +02:00
Joakim Persson d843a2b74c Lagt till logmeddelande 2024-07-29 13:15:54 +02:00
Joakim Persson ab94c06e75 Skickar med use_model när index.html rendreras 2024-07-29 13:15:02 +02:00
joakimp 1757542077 Läser nu ut Ollama-modell från yaml-fil 2024-07-29 00:48:21 +02:00
joakimp 941b426574 Lagt till vidarebefordran av LLM från backen.py till frontend (index.html, client.html, frontend.js 2024-07-29 00:47:42 +02:00
joakimp 534874e810 Lagt till tillstånd och metoder för vilken llm som ska användas 2024-07-29 00:45:10 +02:00
Joakim Persson 0cccd02050 Förberett för att skicka med modell i anrop till apiEndpoint 2024-07-28 23:26:33 +02:00
Joakim Persson e69a131875 Fixat så att loggningen fungerar med GlobalState 2024-07-28 23:25:42 +02:00
Joakim Persson d797440288 Hämtar url_server och model när frågor skickas till api/chat 2024-07-28 23:24:39 +02:00
Joakim Persson 1b5624018c Lagt till fler metoder. Sett till det är att root-loggaren som skapas vid instasieringen av denna singleton 2024-07-28 23:22:08 +02:00
Joakim Persson 807a766a63 Bytt till klassbaserad lösning för globala varabler 2024-07-27 23:49:23 +02:00
Joakim Persson 5245e53954 Tagit bort kod som inte behövs längre 2024-07-27 22:46:55 +02:00
Joakim Persson d1b0554748 Skickar apiEndpoint till iframe genom att sätta contentWindow.apiEndpoint 2024-07-27 22:46:13 +02:00
Joakim Persson f8c3bcd156 Hämntar ut apiEndpoint från fönstret som koden exekverar i (en iframe) 2024-07-27 22:44:32 +02:00
Joakim Persson 25e5c6acea Testar vidare... 2024-07-25 23:23:57 +02:00
Joakim Persson 20fa94f533 Fortsatta försök att få med api_endpoint genom Flask till frontend.js där den behövs 2024-07-25 22:55:21 +02:00
8 changed files with 466 additions and 165 deletions
+2 -2
View File
@@ -11,12 +11,12 @@ backend:
ollama: ollama:
url: "http://localhost:11434" url: "http://localhost:11434"
api_key: "${OLLAMA_API_KEY}" # Refer to environment variable api_key: "${OLLAMA_API_KEY}" # Refer to environment variable
# model: "phi3:mini" # Select a model supported by the Ollama server model: "phi3:mini" # Select a model supported by the Ollama server
# model: "llama3:70b" # Select a model supported by the Ollama server # model: "llama3:70b" # Select a model supported by the Ollama server
# model: "llama3:latest" # Select a model supported by the Ollama server # model: "llama3:latest" # Select a model supported by the Ollama server
# model: "mannix/llama3-8b-ablitered-v3:latest" # Select a model supported by the Ollama server # model: "mannix/llama3-8b-ablitered-v3:latest" # Select a model supported by the Ollama server
# model: "mistral-nemo:latest" # Select a model supported by the Ollama server # model: "mistral-nemo:latest" # Select a model supported by the Ollama server
model: "gemma2:27b" # model: "gemma2:27b"
# Logging comment out the whole section for default level which is INFO # Logging comment out the whole section for default level which is INFO
logging: logging:
+70 -30
View File
@@ -1,17 +1,19 @@
# Import the necessary functions from ollama, Flask, requests, threading # Import the necessary functions from ollama, Flask, requests, threading
from ollama import Client from ollama import Client
from flask import Flask, request, jsonify, send_from_directory, render_template from flask import Flask, request, jsonify, send_from_directory, render_template, session, make_response
from flask_cors import CORS, cross_origin # CORS stands for Cross-Origin Resource Sharing. This is necessary to allow the frontend to make requests to our backend. from flask_cors import CORS, cross_origin # CORS stands for Cross-Origin Resource Sharing. This is necessary to allow the frontend to make requests to our backend.
import requests import requests
import json import json
import logging import logging
import os import os
# from utils import set_local_logger import utils
from utils import GlobalState
# Create a logger for this module # Create a logger for this module
logger = logging.getLogger(__name__) # This logger will be used to log messages from this module global_state = GlobalState() # Import the singleton that holds global states (e.g., logger)
logger.debug("Logging level of backend logger has been configured") logger = global_state.getLogger(__name__) # Logger for this module, inherit properties of the root logger
# Find out the path to current directory according to the Python interpreter (venv) # Find out the path to current directory according to the Python interpreter (venv)
logger.debug("Current working directory: %s", os.getcwd()) logger.debug("Current working directory: %s", os.getcwd())
@@ -20,6 +22,15 @@ logger.debug("Current working directory: %s", os.getcwd())
app = Flask(__name__) app = Flask(__name__)
app.config['STATIC_FOLDER'] = 'static' # Adjust if needed app.config['STATIC_FOLDER'] = 'static' # Adjust if needed
# Set the secret key for session management
secret_key = os.urandom(24)
app.config['SECRET_KEY'] = secret_key # When do I need this. How is it retained between sessions?
# Optionally set other configuration options
app.config['SESSION_PERMANENT'] = False # Session will expire after each request
app.config['SESSION_TYPE'] = 'filesystem' # Store sessions on the filesystem
logger.debug("flask app template folder: %s", app.template_folder) logger.debug("flask app template folder: %s", app.template_folder)
@app.route('/') @app.route('/')
@@ -28,15 +39,35 @@ def index():
This route serves index.html to connecting clients This route serves index.html to connecting clients
""" """
session['chat_history'] = [] # The session object (actually, a dictonary) holds the chat session
logger.debug("Entering route '/'") logger.debug("Entering route '/'")
api_endpoint = os.environ['BE_API_ENDPOINT'] # Retrieve the environment variable # api_endpoint = os.environ['BE_API_ENDPOINT'] # Retrieve the environment variable
logger.debug("API endpoint: %s", api_endpoint) api_endpoint = global_state.get_backend_api_ep() # Retrieve the environment variable
logger.debug("Backend API endpoint: %s", api_endpoint)
use_model = global_state.get_llm()
with open('smartassist/src/html/client.html', 'r') as f: with open('smartassist/src/html/client.html', 'r') as f:
client_html = f.read() client_html = f.read()
# logger.debug("Client HTML (first few characters): %s", client_html[:50]) # Print to see if it's loading logger.debug("Client HTML (first few characters): %s", client_html[:50]) # Print to see if it's loading
logger.debug("Client HTML (first few characters): %s", client_html) # Print to see if it's loading # logger.debug("Client HTML (all characters): %s", client_html) # Print to see if it's loading
return render_template('index.html', api_endpoint=api_endpoint, client_content=client_html) return render_template('index.html', api_endpoint=api_endpoint, use_model = use_model, client_content=client_html)
@app.route('/set_session')
def set_session():
resp = make_response()
resp.set_cookie('session', 'some-value', samesite='None', secure=True) # Add SameSite attribute here
return resp
@app.route('/profile')
def profile():
# Retrieve data from the session
user_id = session.get('user_id')
if user_id:
return f'User ID: {user_id}'
else:
return 'No user ID found'
@app.route('/<path:filename>') @app.route('/<path:filename>')
def serve_static(filename): def serve_static(filename):
@@ -55,6 +86,7 @@ CORS(app, resources={
"origins": "*" "origins": "*"
} }
}) })
@app.route('/api/chat', methods=['POST']) @app.route('/api/chat', methods=['POST'])
def chat(url_server = "http://localhost:11434/api/generate", model = "phi3:mini"): def chat(url_server = "http://localhost:11434/api/generate", model = "phi3:mini"):
@@ -67,29 +99,38 @@ def chat(url_server = "http://localhost:11434/api/generate", model = "phi3:mini"
# Get the message from the JSON in the request body # Get the message from the JSON in the request body
data = request.get_json() data = request.get_json()
message = data.get('query') message = data.get('query')
url_server = data.get('url_server', url_server) # Use provided URL or default
logger.debug("data = %s\nmessage = %s", str(data), str(message)) model = data.get('model', model) # Use provided model or default
# Get chat history from session storage (e.g., a dictionary)
chat_history = session.get('chat_history', [])
# Add the new message to the chat history
chat_history.append({'role': 'user', 'message': message})
# Update the session with the new chat history
session['chat_history'] = chat_history
# Create the data dictionary with chat history
data_to_send = {
"model": model,
'prompt': '\n'.join([f"{item['role']}: {item['message']}" for item in chat_history]),
"stream": False
}
try: try:
url = url_server url = url_server
model_to_use = model
data = {
"model": model_to_use,
'prompt': message,
"stream": False
}
headers = { headers = {
"Content-Type": "application/json", "Content-Type": "application/json",
} }
# With API key
# headers = {
# "Content-Type": "application/json",
# "Authorization": "Bearer YOUR_API_KEY" # Replace with your API key
# }
response = requests.post(url, response = requests.post(url,
headers=headers, headers=headers,
data=json.dumps(data)) data=json.dumps(data_to_send))
response.raise_for_status() # Raise an exception for bad status codes response.raise_for_status() # Raise an exception for bad status codes
llm_response = response.json()['response'] # Assuming the LLM's response is under 'response' key
chat_history.append({'role': 'assistant', 'message': llm_response}) # Add assistant response to chat history
logger.debug(f"Chat History: {chat_history}")
return response.json() return response.json()
except requests.exceptions.RequestException as e: except requests.exceptions.RequestException as e:
logger.error("Request Exception: %s", str(e)) logger.error("Request Exception: %s", str(e))
@@ -99,6 +140,7 @@ def chat(url_server = "http://localhost:11434/api/generate", model = "phi3:mini"
return jsonify({'error': 'Invalid JSON response from server'}), 500 return jsonify({'error': 'Invalid JSON response from server'}), 500
@app.route('/smartassist', methods=["POST"]) @app.route('/smartassist', methods=["POST"])
def smartassist(): def smartassist():
# Extract the query from the incoming JSON data # Extract the query from the incoming JSON data
@@ -113,12 +155,10 @@ def smartassist():
return jsonify({"response": response}) return jsonify({"response": response})
def get_response(user_query): def get_response(user_query):
# Create a client object for interacting with OLLAMA API client = Client() # Create a client object for interacting with OLLAMA API
client = Client() response = client.generate_response(user_query) # Generate and retrieve the response based on user's query
# Generate and retrieve the response based on user's query return response
response = client.generate_response(user_query)
# Return the generated response
return response
def run_flask(fport=5005): def run_flask(fport=5005):
""" """
+51 -5
View File
@@ -12,15 +12,58 @@
<!-- messages will be rendered here --> <!-- messages will be rendered here -->
</div> </div>
<textarea id="userInput" placeholder="Type your message..." rows="5"></textarea> <textarea id="userInput" placeholder="Type your message..." rows="5"></textarea>
<button onclick="sendMessage()">Send</button> <button id="sendButton" onclick="sendMessage()">Send</button>
<!-- Get the apiEndpoint and the useModel -->
<!-- <script>
const apiEndpoint = window.apiEndpoint;
const useModel = window.useModel;
console.log("client.html - API Endpoint: ", apiEndpoint);
console.log("client.html - use model: ", useModel);
</script> -->
<!-- <script>
let apiEndpoint; // Make variable available outside of the scope of the event listener
let useModel; // Make variable available outside of the scope of the event listener
window.addEventListener('message', function(event) {
if (event.origin === 'http://localhost:5004') { // Make sure this matches your origin
const { apiEndpoint, useModel } = event.data;
console.log("client.html - API Endpoint: ", apiEndpoint);
console.log("client.html - use model: ", useModel);
window.apiEndpoint = apiEndpoint;
window.useModel = useModel;
}
});
</script> -->
<!-- Marked-it for markdown rendering -->
<script src="https://cdn.jsdelivr.net/npm/markdown-it@14.1.0/dist/markdown-it.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/markdown-it@14/dist/markdown-it.min.js"></script>
<!-- Include MathJax library to render mathematical notation -->
<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
<script>
window.MathJax = {
loader: { load: ['input/tex', 'output/chtml'] },
tex: {
packages: ['base', 'ams'],
inlineMath: [['$', '$']]
}
};
</script>
<script src="/js/frontend.js"></script>
<script> <script>
const chatContainer = document.getElementById('chatbox'); const chatContainer = document.getElementById('chatbox');
// Handle resize events // Handle resize events
window.addEventListener('resize', function() { window.addEventListener('resize',
chatContainer.style.height = 'auto'; function() {
chatContainer.style.height = 'auto';
}); });
const userInputElement = document.getElementById('userInput'); const userInputElement = document.getElementById('userInput');
@@ -30,13 +73,16 @@
event.preventDefault(); event.preventDefault();
userInputElement.value += '\n'; userInputElement.value += '\n';
} else if (event.key === 'Enter') { // Enter to send message } else if (event.key === 'Enter') { // Enter to send message
sendMessage(); window.frontendApi.sendMessage();
userInputElement.value = ''; // Clear the input field after sending userInputElement.value = ''; // Clear the input field after sending
event.preventDefault(); event.preventDefault();
} }
}); });
</script> </script>
<!-- Get the javascript handling communication with the backend -->
<script src="/js/frontend.js"></script>
</body> </body>
</html> </html>
+45 -42
View File
@@ -5,13 +5,13 @@ import yaml
import json import json
import socket import socket
import urllib.parse import urllib.parse
from backend import run_flask
import logging import logging
import utils import utils
from utils import configure_logging from utils import GlobalState
from backend import run_flask
configure_logging() # Configure root logger. The level will be adjusted later based on config file global_state = GlobalState() # Configure root logger. The level will be adjusted later based on config file
logger = logging.getLogger(__name__) # Logger for this module, inherit properties of the root logger logger = global_state.getLogger(__name__) # Logger for this module, inherit properties of the root logger
def configure(): def configure():
""" """
@@ -46,55 +46,58 @@ def configure():
# Extract global logging level # Extract global logging level
#################################### ####################################
if isinstance(updated_config.get('logging'), dict): # Look for 'logging' key in config file if isinstance(updated_config.get('logging'), dict): # Look for 'logging' key in config file
# logging.info("found key 'logging' in config file")
logging_config = updated_config['logging'] logging_config = updated_config['logging']
if isinstance(logging_config.get('level'), str): # Set to value of the yaml file if specified if isinstance(logging_config.get('level'), str): # Set to value of the yaml file if specified
# logging.info("found key 'level' in config file") global_state.set_log_level(logging_config['level'])
utils.log_level = logging_config['level'] logger.debug("configure(): This logger now has effective log level %s", logger.getEffectiveLevel())
numeric_log_level = getattr(logging, utils.log_level, None)
rlogger = logging.getLogger() # Get the root logger
rlogger.setLevel(numeric_log_level)
# set_local_logger(logger) # Set log level for logger based on log_level
logger.info("Global variable utils.log_level set to: {}".format(utils.log_level))
#################################### ####################################
# Extract and export API endpoint as # Extract and export backend API
# envrionment variable # endpoint as global state variable
#################################### ####################################
backend_api_ep = 'http://localhost:5005/api/chat' # Default API endpoint if isinstance(updated_config.get('backend'), dict): # Look for 'backend' key
if isinstance(updated_config.get('backend'), dict): # Look for 'backend' key in config file if isinstance(updated_config['backend'].get('url'), str): # Look for 'url' key
if isinstance(updated_config['backend'].get('url'), str): # Look for 'url' key in config file
url = updated_config['backend'].get('url') url = updated_config['backend'].get('url')
if isinstance(updated_config['backend'].get('api'), str): # Look for 'api' key in config file if isinstance(updated_config['backend'].get('api'), str): # Look for 'api' key
api = updated_config['backend'].get('api') api = updated_config['backend'].get('api')
backend_api_ep = url+api # Extract API endpoint if defined # backend_api_ep = url+api # Extract API endpoint if defined
logger.debug("BE_API_ENDPOINT is set to '{}'".format(backend_api_ep)) logger.debug(f"Constructing endpoint address as url+api: {url+api}")
os.environ['BE_API_ENDPOINT'] = backend_api_ep global_state.set_backend_api_ep(url+api) # Extract API endpoint if defined and set in global_state
logger.debug(f"Backend API endpoint is set to {global_state.get_backend_api_ep()}")
# os.environ['BE_API_ENDPOINT'] = backend_api_ep # Look into alternative way to share this with backend.py
####################################
# Extract Ollama parameters (url, api_key, model)
####################################
if isinstance(updated_config.get('ollama'), dict): # Look for 'ollama' key
if isinstance(updated_config['ollama'].get('model'), str): # Look for 'model' key
model_to_use = updated_config['ollama'].get('model')
global_state.set_llm(model_to_use)
logger.debug("configure(): LLM is set to: %s",global_state.get_llm())
return updated_config return updated_config
def start_frontend(config): # def start_frontend(config):
parsed_url = urllib.parse.urlparse(config['frontend']['url']) # parsed_url = urllib.parse.urlparse(config['frontend']['url'])
hostname = parsed_url.netloc.split(':')[0] # Split by ':' and take the first part, i.e., 'localhost', IP, or domain name # hostname = parsed_url.netloc.split(':')[0] # Split by ':' and take the first part, i.e., 'localhost', IP, or domain name
port = parsed_url.port # This is the server port # port = parsed_url.port # This is the server port
# Use the socket module in Python to check whether a port is in use, # # Use the socket module in Python to check whether a port is in use,
# which would indicate that a server is already running on that port. # # which would indicate that a server is already running on that port.
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: # with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
try: # try:
s.bind((hostname, port)) # s.bind((hostname, port))
logger.debug("No server is running on %s -— starting one.", parsed_url.netloc) # logger.debug("No server is running on %s -— starting one.", parsed_url.netloc)
# Start frontend (web server) as a separate process # # Start frontend (web server) as a separate process
subprocess.Popen(["python", "-m", "http.server", str(port)]) # subprocess.Popen(["python", "-m", "http.server", str(port)])
except socket.error as e: # except socket.error as e:
if e.errno == 48: # if e.errno == 48:
logger.debug("A server is already running on %s -— will use this.", parsed_url.netloc) # logger.debug("A server is already running on %s -— will use this.", parsed_url.netloc)
else: # else:
raise # Unexpected error, re-raise it so we can see the traceback # raise # Unexpected error, re-raise it so we can see the traceback
except Exception as e: # except Exception as e:
logger.error("Failed to start frontend: %s", str(e)) # Corresponds to print(f"Failed to start frontend: {e}") # logger.error("Failed to start frontend: %s", str(e)) # Corresponds to print(f"Failed to start frontend: {e}")
@@ -113,6 +116,6 @@ def start_backend(config):
if __name__ == '__main__': if __name__ == '__main__':
conf = configure() # Read config from file and set up config dict conf = configure() # Read config from file and set up config dict
logger.debug('conf dictionary set to {}'.format(json.dumps(conf, indent=4))) logger.debug('conf dictionary set to {}'.format(json.dumps(conf, indent=4)))
# start_frontend(config=conf) # start_frontend(config=conf) # Not needed as we are using Flask for backend now
start_backend(config=conf) start_backend(config=conf)
+20 -4
View File
@@ -14,14 +14,18 @@ h1 {
} }
#chatbox { #chatbox {
width: 80%; width: calc(50% - 60px); /* Adjust width for input and button */
max-width: 500px; /* max-width: 500px; */
background-color: #fff; height: 600px;
/* background-color: #fff8bc; */
background-color: #ffffff;
border-radius: 10px; border-radius: 10px;
padding: 20px; padding: 20px;
box-shadow: 0 4px 8px rgba(0,0,0,0.1); box-shadow: 0 4px 8px rgba(0,0,0,0.1);
overflow: auto; /* Allow horizontal and vertical scrolling of the chatbox */ overflow: auto; /* Allow horizontal and vertical scrolling of the chatbox */
resize: both; /* Allow resizing vertically */ resize: both; /* Allow resizing vertically */
border: 1px solid #ccc; /* Add a thin grey border around chatbox */
margin-bottom: 20px; /* Add some space between chatbox and userInput */
} }
.message { .message {
@@ -36,7 +40,8 @@ h1 {
} }
.ai-response { .ai-response {
background-color: #f0f8ff; /* background-color: #f0f8ff; */
background-color: #f5ecd0;
padding: 10px 15px; padding: 10px 15px;
border-radius: 10px; border-radius: 10px;
text-align: left; /* Align AI responses to the left */ text-align: left; /* Align AI responses to the left */
@@ -48,6 +53,7 @@ h1 {
border: 1px solid #ccc; border: 1px solid #ccc;
border-radius: 5px; border-radius: 5px;
margin-bottom: 10px; margin-bottom: 10px;
font-family: 'Courier New', Courier, monospace; /* Fixed width typeface */
} }
#userInput:focus { #userInput:focus {
@@ -66,4 +72,14 @@ button[onclick="sendMessage()"] {
font-size: 16px; font-size: 16px;
border-radius: 5px; border-radius: 5px;
cursor: pointer; cursor: pointer;
transition: background-color 0.3s; /* Smooth transition effect */
} }
button[onclick="sendMessage()"]:hover {
background-color: #b2b2b2; /* Light Grey on hover */
}
button[onclick="sendMessage()"]:active {
background-color: #6f6f6f; /* Dark Grey when clicked */
}
+177 -45
View File
@@ -1,60 +1,192 @@
// // Get the user input element from the DOM
// const chatbox = document.getElementById('chatbox');
// const userInput = document.getElementById('userInput');
// const parser = window.markdownit({
// linkify: true,
// strikethrough: true,
// });
// parser.enable(['table']);
// // const apiEndpoint = window.apiEndpoint; // Get the API endpoint
// // const useModel = window.useModel; // Get whether to use a model or not
// // console.log("frontend.js - API Endpoint: ", window.apiEndpoint);
// // console.log("frontend.js - Use model: ", window.useModel);
// let apiEndpoint; // Make variable available outside of the scope of the event listener
// let useModel; // Make variable available outside of the scope of the event listener
// window.addEventListener('message', function(event) {
// if (event.origin === 'http://localhost:5004') { // Make sure this matches your origin
// const { apiEndpoint, useModel } = event.data;
// console.log("client.html - API Endpoint: ", apiEndpoint);
// console.log("client.html - use model: ", useModel);
// window.apiEndpoint = apiEndpoint;
// window.useModel = useModel;
// }
// });
// console.log("frontend.js - API Endpoint: ", window.apiEndpoint);
// console.log("frontend.js - Use model: ", window.useModel);
// // Define a function to send the user's message to the AI
// function sendMessage() {
// // Get the user's input message and trim any whitespace
// const query = userInput.value.trim();
// // Check if the message is not empty
// if (query !== '') {
// // fetch(`${apiEndpoint}`, {
// fetch(apiEndpoint, {
// method: 'POST',
// headers: { 'Content-Type': 'application/json' },
// body: JSON.stringify({ query, model: useModel }), // Add these parameters here
// // body: JSON.stringify({ query, url_server: "http://your-custom-url", model: "phi3:mini" }), // Add these parameters here
// })
// .then(response => response.json())
// .then(data => {
// // Get the AI's response from the API data
// const aiResponse = data.response;
// // Render the user's original message in the chatbox
// renderMessage(query, 'user-message');
// // Render the AI's response in the chatbox
// renderMessage(aiResponse, 'ai-response');
// // Clear the user input field for the next message
// userInput.value = '';
// })
// .catch(error => console.error('Error sending message:', error));
// }
// }
// // Define a function to render a message in the chatbox with a specific class name
// function renderMessage(text, className) {
// // Create a new div element to hold the message
// const messageElement = document.createElement('div');
// // Add the specified class name to the element
// messageElement.className = className;
// // // Set the text content of the element to the message text
// // messageElement.textContent = text;
// // Use the markdown-it parser
// const html = parser.render(text);
// messageElement.innerHTML = html;
// // Append the message element to the chatbox immediately
// // chatbox.appendChild(messageElement);
// // Typeset math in the message element
// MathJax.typesetPromise([messageElement]).then(() => {
// // No need to append anything here, it's already appended above
// chatbox.appendChild(messageElement);
// });
// }
// // Make the button toggle colour when user presses Enter on keyboard
// const sendButton = document.getElementById('sendButton');
// document.addEventListener('keydown', function(event) {
// if (event.key === 'Enter') {
// sendButton.style.backgroundColor = '#6f6f6f'; // Dark Grey when Enter is pressed
// }
// });
// document.addEventListener('keyup', function() {
// sendButton.style.backgroundColor = ''; // Restore the original style when any key is released
// });
// Get the user input element from the DOM // Get the user input element from the DOM
const chatbox = document.getElementById('chatbox'); const chatbox = document.getElementById('chatbox');
const userInput = document.getElementById('userInput'); const userInput = document.getElementById('userInput');
const parser = window.markdownit({
linkify: true,
strikethrough: true,
});
parser.enable(['table']);
// Get API endpoint for chat from environment variable let apiEndpoint; // Make variable available outside of the scope of the event listener
const apiEndpoint = process.env.BE_API_ENDPOINT || 'http://localhost:5005/api/chat'; // Default if not found let useModel; // Make variable available outside of the scope of the event listener
console.log("The API Endpoint is:", apiEndpoint);
const frontendApi = {
// Define a function to send the user's message to the AI
// Define a function to send the user's message to the AI sendMessage: function() {
function sendMessage() { if (!window.apiEndpoint || !window.useModel) { // Check if we're ready before proceeding
// Get the user's input message and trim any whitespace console.error("Not ready yet. Please wait for apiEndpoint and useModel to be set.");
return;
}
// Get the user's input message and trim any whitespace
const query = userInput.value.trim(); const query = userInput.value.trim();
// Check if the message is not empty // Check if the message is not empty
if (query !== '') { if (query !== '') {
// Send a POST request to the /api/chat endpoint with the message fetch(window.apiEndpoint, {
// fetch('http://localhost:5005/api/chat', { method: 'POST',
fetch(apiEndpoint, { headers: { 'Content-Type': 'application/json' },
method: 'POST', body: JSON.stringify({ query, model: window.useModel }), // Add these parameters here
headers: { 'Content-Type': 'application/json' }, })
body: JSON.stringify({ query }), .then(response => response.json())
}) .then(data => {
.then(response => response.json()) // Get the AI's response from the API data
.then(data => { const aiResponse = data.response;
// Get the AI's response from the API data // Render the user's original message in the chatbox
const aiResponse = data.response; this.renderMessage(query, 'user-message');
// Render the AI's response in the chatbox
// Render the user's original message in the chatbox this.renderMessage(aiResponse, 'ai-response');
renderMessage(query, 'user-message'); // Clear the user input field for the next message
userInput.value = '';
// Render the AI's response in the chatbox })
renderMessage(aiResponse, 'ai-response'); .catch(error => console.error('Error sending message:', error));
// Clear the user input field for the next message
userInput.value = '';
})
.catch(error => console.error('Error sending message:', error));
} }
} },
// Define a function to render a message in the chatbox with a specific class name
renderMessage: function(text, className) {
// Define a function to render a message in the chatbox with a specific class name
function renderMessage(text, className) {
// Create a new div element to hold the message // Create a new div element to hold the message
const messageElement = document.createElement('div'); const messageElement = document.createElement('div');
// Add the specified class name to the element // Add the specified class name to the element
messageElement.className = className; messageElement.className = className;
// Use the markdown-it parser
// Set the text content of the element to the message text const html = parser.render(text);
messageElement.textContent = text; messageElement.innerHTML = html;
// Append the message element to the chatbox immediately
// Append the message element to the chatbox
chatbox.appendChild(messageElement); chatbox.appendChild(messageElement);
} },
};
// Wait for the event listener to set apiEndpoint and useModel
window.addEventListener('message', function(event) {
if (event.origin === 'http://localhost:5004') { // Make sure this matches your origin
const { apiEndpoint, useModel } = event.data;
console.log("fronend.js - API Endpoint: ", apiEndpoint);
console.log("fronend.js - use model: ", useModel);
window.apiEndpoint = apiEndpoint;
window.useModel = useModel;
}
});
// Wait for the DOM to be fully loaded before making the API available
document.addEventListener('DOMContentLoaded', function() {
window.frontendApi = frontendApi;
});
// Make the button toggle colour when user presses Enter on keyboard
const sendButton = document.getElementById('sendButton');
document.addEventListener('keydown', function(event) {
if (event.key === 'Enter') {
sendButton.style.backgroundColor = '#6f6f6f'; // Dark Grey when Enter is pressed
}
});
document.addEventListener('keyup', function() {
sendButton.style.backgroundColor = ''; // Restore the original style when any key is released
});
+38 -12
View File
@@ -4,25 +4,51 @@
<!-- <title>Frontend</title> --> <!-- <title>Frontend</title> -->
</head> </head>
<body> <body>
<!-- This iframe will hold the content from client.html --> <!-- This iframe will hold the content from client.html -->
<iframe id="client-frame" style="width: 100%; height: 100vh;" srcdoc="{{ client_content }}"></iframe> <!-- Passing the API endpoint as a query parameter to the srcdoc attribute -->
<!-- srcdoc="{{ client_content }}?apiEndpoint={{ api_endpoint }}/"> -->
<iframe id="client-frame"
style="width: 100%; height: 100vh;"
srcdoc="{{ client_content }}">
</iframe>
<!-- <script>
// Extract apiEndpoint for use in your frontend code...
const apiEndpoint = '{{ api_endpoint }}'; // Templating syntax (Jinja2)
const useModel = '{{ use_model }}'; // Templating syntax (Jinja2)
// Tell the iframe about the apiEndpoint
document.getElementById('client-frame').contentWindow.apiEndpoint = apiEndpoint;
document.getElementById('client-frame').contentWindow.useModel = useModel;
console.log("index.html - API Endpoint: ", apiEndpoint);
console.log("index.html - use model: ", useModel);
</script> -->
<script>
// Extract apiEndpoint for use in frontend.js
const apiEndpoint = '{{ api_endpoint }}'; // Templating syntax (Jinja2)
const useModel = '{{ use_model }}'; // Templating syntax (Jinja2)
window.addEventListener('load', function() {
const clientFrame = document.getElementById('client-frame').contentWindow;
clientFrame.postMessage({ apiEndpoint, useModel }, '*'); // Send the data to the iframe
});
console.log("index.html - API Endpoint: ", apiEndpoint);
console.log("index.html - use model: ", useModel);
</script>
<!-- Responsive scaling and some padding --> <!-- Responsive scaling and some padding -->
<script> <script>
const clientFrame = document.getElementById('client-frame'); const clientFrame = document.getElementById('client-frame');
function resizeIframe() { function resizeIframe() {
clientFrame.style.height = window.innerHeight - 50 + 'px'; // Adjust the subtraction for padding/margins if needed clientFrame.style.height = window.innerHeight - 50 + 'px'; // Adjust the subtraction for padding/margins if needed
} }
window.addEventListener('resize', resizeIframe); window.addEventListener('resize', resizeIframe);
resizeIframe(); // Call it once on page load resizeIframe(); // Call it once on page load
</script> </script>
<script>
const apiEndpoint = '<%= api_endpoint %>'; // Templating syntax (Jinja2)
console.log("API Endpoint:", apiEndpoint);
// Use apiEndpoint in your frontend code...
</script>
</body> </body>
</html> </html>
+63 -25
View File
@@ -2,36 +2,74 @@
# imported to more than one other module. The rational for defining these things here # imported to more than one other module. The rational for defining these things here
# is that it is easier to avoid circular imports when they are defined in a central location. # is that it is easier to avoid circular imports when they are defined in a central location.
import logging import logging
global log_level # Remember, in Python globals are only global in the module it is defined in
log_level = 'INFO' # Default logging level if not specified in config file
def configure_logging(level=log_level): class GlobalState:
""" """
Set up logging for the project. This is the root logger instance. This class holds various variables and methods which are accessible across
All child loggers inherit from this logger. different modules in the Python project using the Singleton design pattern.
This ensures that only one instance of the class is created and shared among
all modules, preventing circular imports and providing a centralized location
for managing shared resources.
""" """
numeric_level = getattr(logging, level.upper()) # Convert string to numeric level _instance = None # Private class attribute to hold the single instance of the class
logger = logging.getLogger() # Get the root logger def __new__(cls):
logger.setLevel(numeric_level) if cls._instance is None:
cls._instance = super(GlobalState, cls).__new__(cls)
cls._instance.log_level = 'INFO' # Default logging level
cls._instance.logger = logging.getLogger() # Get root logger for the caller module
handler = logging.StreamHandler() # Or other handler (FileHandler for logs to file)
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
handler.setFormatter(formatter)
cls._instance.logger.addHandler(handler)
cls._instance.logger.setLevel(getattr(logging, cls._instance.log_level)) # Initialize root logger level
cls._instance.logger.info(" __new__(cls): Logger in GlobalState created: %s", cls._instance.logger)
cls._instance.llm = "phi3:mini" # Default LLM for queries. TODO: Check with ollama server that it actually exists
cls._instance.backend_api_ep = "http://localhost:5005/api/chat" # Default backend API endpoint
return cls._instance
handler = logging.StreamHandler() # Or other handler (FileHandler for logs to file) def configure_logging(self, level=None):
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") """Set up logging for the project."""
handler.setFormatter(formatter) if level is None:
logger.addHandler(handler) level = self.log_level
# numeric_level = getattr(logging, level.upper()) # Convert string to numeric level
numeric_level = getattr(logging, level.upper()) # Convert string to numeric level
self.logger.setLevel(numeric_level)
self.logger.debug(f"utils.py -- configure_logging(): effective log level is {level} which is {self.logger.getEffectiveLevel()}")
# To be removed? def set_log_level(self, level = 'INFO'):
# def set_local_logger(log_instance): """Set the logging level."""
# """ self.log_level = level
# Configure logging based on the global variable log_level self.configure_logging()
# Logging is controlled by integer values, where DEBUG < INFO < WARNING < ERROR < CRITICAL.
# To turn off logging completely, set numeric_log_level to at least CRITICAL + 1. def get_log_level(self):
# """ """Getter for log_level attribute."""
# global log_level return self.log_level
# numeric_log_level = getattr(logging, log_level, None)
# if not isinstance(numeric_log_level, int):
# raise ValueError('Invalid log level: %s' % log_level)
# log_instance.basicConfig(level=numeric_log_level) # Set the root logger level to the configured level def get_effective_log_level(self):
# log_instance.info('Current log level set to {}'.format(log_instance.getLogger().getEffectiveLevel())) # Example usage of the logger """Getter for effective log level of loggerattribute."""
return self.logger.getEffectiveLevel()
def getLogger(self, module_name = None):
"""Return a logger based on the module name."""
if module_name is None:
module_name = __name__
logger = logging.getLogger(module_name)
return logger
def set_llm(self, model_name="phi3:mini"):
"""Set LLM for queries"""
self.llm = model_name
def get_llm(self):
"""Getter for which LLM is used for queries"""
return self.llm
def set_backend_api_ep(self, be_api_ep=None):
"""Set backend API endpoint"""
self.backend_api_ep = be_api_ep
def get_backend_api_ep(self):
"""Getter for backend API endpoint"""
return self.backend_api_ep