Compare commits
96 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9203641e6a | |||
| 14eaf57335 | |||
| 9ae5840e8b | |||
| 659b0937d3 | |||
| 7bc8a129c6 | |||
| a12c11c058 | |||
| 5771e52c49 | |||
| d038f1ac24 | |||
| 86374c91fe | |||
| 0adbb9c222 | |||
| c3d7f3ba4f | |||
| d864d7529a | |||
| 0b971dffc4 | |||
| ecf45bd2e7 | |||
| b88e573761 | |||
| ecf2ddf46e | |||
| b4a35bafef | |||
| de3e59afea | |||
| 9f5bdddb8f | |||
| 854ebbdd94 | |||
| 7d954e7380 | |||
| d843a2b74c | |||
| ab94c06e75 | |||
| 1757542077 | |||
| 941b426574 | |||
| 534874e810 | |||
| 0cccd02050 | |||
| e69a131875 | |||
| d797440288 | |||
| 1b5624018c | |||
| 807a766a63 | |||
| 5245e53954 | |||
| d1b0554748 | |||
| f8c3bcd156 | |||
| 25e5c6acea | |||
| 20fa94f533 | |||
| ccc8e73f48 | |||
| f179e8e19b | |||
| bfe8479016 | |||
| b261437952 | |||
| dac233ba9c | |||
| 7c9d20896e | |||
| ce5532e9a6 | |||
| feb7e61fd8 | |||
| 9a9b1388ce | |||
| 61974859bd | |||
| bf165d8181 | |||
| fbcac82bbf | |||
| 60ad3d109b | |||
| 8ddb920e99 | |||
| adbf14c43c | |||
| a2144e0480 | |||
| d0935f771a | |||
| 9bb9b76227 | |||
| 3b2577084e | |||
| eaba29fc6f | |||
| a5d54c2cb6 | |||
| b3136a9bcd | |||
| 81f2352291 | |||
| b102309d7b | |||
| 753353445c | |||
| 03c08d4024 | |||
| 38c78934a5 | |||
| 66620cc6ca | |||
| f5244ee88b | |||
| a18bfca9f8 | |||
| a90b3e5f2d | |||
| c4e4d8ded0 | |||
| fd31d583af | |||
| 89b1d16bfd | |||
| cfb6b6a9bd | |||
| 18dbee6cbb | |||
| f1fe8f0bfc | |||
| 2aa9aae282 | |||
| a3d9d07ead | |||
| 305b8485e3 | |||
| fcf6db2686 | |||
| c82f603d51 | |||
| 4f4d9af67d | |||
| 10469df5be | |||
| 8a9e57be76 | |||
| cecc2f7520 | |||
| 1b7142f157 | |||
| 3fef179723 | |||
| 99e3737718 | |||
| c39b831a59 | |||
| 3b70ab6eb9 | |||
| 4743fc26ad | |||
| 3ad564a75d | |||
| 9ea88d693d | |||
| 1837c1282a | |||
| 8d9369c502 | |||
| 957b8f46c8 | |||
| c37b7d46b4 | |||
| d771951b80 | |||
| 7340e32ce4 |
@@ -160,3 +160,5 @@ cython_debug/
|
|||||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||||
#.idea/
|
#.idea/
|
||||||
|
|
||||||
|
# Exclude venv from smartassist
|
||||||
|
smartassist/smartassist_dev_venv
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# This is "explain.prompt", a slash command to explain code
|
||||||
|
# It is used to define and reuse prompts within Continue
|
||||||
|
# Continue will automatically create a slash command for each prompt in the .prompts folder
|
||||||
|
# To learn more, see the full .prompt file reference: https://docs.continue.dev/walkthroughs/prompt-files
|
||||||
|
temperature: 0.3
|
||||||
|
---
|
||||||
|
<system>
|
||||||
|
You are an expert programmer
|
||||||
|
</system>
|
||||||
|
|
||||||
|
{{{ input }}}
|
||||||
|
|
||||||
|
Please analyze the source code snippet above and provide a detailed explanation of its functionality.
|
||||||
|
|
||||||
|
Input: Clearly identify where the code takes input from (e.g., user input, file, database, API call). Specify the format of this input (e.g., text string, numerical values, JSON object).
|
||||||
|
Processing: Describe step-by-step how the code processes the input. Explain the purpose of each key function, loop, or conditional statement. Use clear and concise language, avoiding jargon where possible.
|
||||||
|
Output: Specify what the code produces as output (e.g., printed text, modified file, database update, API response). Describe the format and content of this output.
|
||||||
|
|
||||||
|
Invocation Context: Who would typically use this code snippet? What is its intended purpose or application? Provide examples of real-world scenarios where this code might be employed.
|
||||||
|
|
||||||
|
If you need to provide more context, please include it in your explanation.
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
# Frontend Configuration
|
||||||
|
frontend:
|
||||||
|
url: "http://localhost:5004"
|
||||||
|
|
||||||
|
# Backend Configuration
|
||||||
|
backend:
|
||||||
|
url: "http://localhost:5004"
|
||||||
|
api: "/api/chat"
|
||||||
|
|
||||||
|
# Ollama Server Configuration
|
||||||
|
ollama:
|
||||||
|
url: "http://localhost:11434"
|
||||||
|
api_key: "${OLLAMA_API_KEY}" # Refer to environment variable
|
||||||
|
model: "phi3:mini" # 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: "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: "gemma2:27b"
|
||||||
|
|
||||||
|
# Logging – comment out the whole section for default level which is INFO
|
||||||
|
logging:
|
||||||
|
level: DEBUG # Set log level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
|
||||||
|
# level: INFO # Set log level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
|
||||||
|
|
||||||
|
# Additional Configuration Options (Optional – ignored for now)
|
||||||
|
|
||||||
|
# Cache Settings (Optional)
|
||||||
|
cache:
|
||||||
|
enabled: true
|
||||||
|
timeout: 60 # Seconds
|
||||||
|
|
||||||
|
test:
|
||||||
|
test_key_1: "ett"
|
||||||
|
test_key_2: "ett"
|
||||||
|
test_key_3: "${THIS_IS_A_DUMMY_VAR}"
|
||||||
|
|
||||||
|
another_test: "Syns detta?"
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
propose a python project that involves using using ollama for using the power of LLM
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Certainly! Let's design a Python project that leverages the capabilities of an Open Language Learning Model Architecture (OLLAMA) to create a versatile and powerful application. Our project will be named "SmartAssist", which is an intelligent assistant powered by a large language model (LLM). The main goal of SmartAssist is to provide users with personalized assistance, recommendations, and information retrieval through natural language processing and machine learning techniques.
|
||||||
|
|
||||||
|
### Project Overview:
|
||||||
|
|
||||||
|
**Name:** SmartAssist
|
||||||
|
|
||||||
|
**Objective:** Develop a versatile AI-powered assistant that can interact with users in natural language and perform tasks such as answering questions, providing recommendations, summarizing texts, and more.
|
||||||
|
|
||||||
|
### Features:
|
||||||
|
|
||||||
|
1. **Natural Language Processing (NLP):** Utilize OLLAMA to understand user queries and generate appropriate responses.
|
||||||
|
2. **Task Automation:** Enable the assistant to perform various automated tasks based on user requests.
|
||||||
|
3. **Integration with External APIs:** Fetch real-time data from external sources to provide up-to-date information.
|
||||||
|
4. **User Preferences:** Allow users to customize their interaction experience by setting preferences or providing feedback.
|
||||||
|
5. **Multi-lingual Support:** Expand the assistant's capabilities to support multiple languages if OLLAMA offers this feature.
|
||||||
|
6. **Continuous Learning:** Use machine learning algorithms to continuously improve the model's performance based on user interactions and feedback.
|
||||||
|
7. **Cross-platform Compatibility:** Develop a web app, mobile app, or desktop application that can be accessed from various devices.
|
||||||
|
|
||||||
|
### Technical Architecture:
|
||||||
|
|
||||||
|
1. **Backend:** Python with Flask or FastAPI for creating RESTful APIs to handle requests from the frontend and interact with OLLAMA.
|
||||||
|
2. **OLLAMA Integration:** Use a library or API provided by OLLAMA to interface with the large language model.
|
||||||
|
3. **Frontend:** React Native (for mobile), Vue.js (or Angular) for web, or Swift/Kotlin (for iOS and Android).
|
||||||
|
4. **Database:** SQLite for storing user preferences, interaction logs, and other data required by the application.
|
||||||
|
5. **Continuous Integration/Deployment:** Implement CI/CD pipelines using GitHub Actions or GitLab CI to automate testing and deployment processes.
|
||||||
|
|
||||||
|
### Implementation Steps:
|
||||||
|
|
||||||
|
1. **Setup Environment:** Install necessary libraries such as Flask, SQLite, and OLLAMA-related dependencies.
|
||||||
|
2. **API Endpoints:** Create API endpoints for user interactions (GET/POST requests).
|
||||||
|
3. **OLLAMA Integration:** Implement a function to interact with OLLAMA using its API or library, allowing the assistant to generate responses based on input queries.
|
||||||
|
4. **Frontend Development:** Develop responsive and interactive interfaces for users to engage with SmartAssist.
|
||||||
|
5. **Testing:** Perform unit testing, integration testing, and user acceptance testing to ensure the application works as expected.
|
||||||
|
6. **Deployment:** Deploy the application using cloud services like AWS, Azure, or Google Cloud, ensuring scalability and high availability.
|
||||||
|
7. **Maintenance and Updates:** Regularly update OLLAMA library versions and add new features based on feedback and emerging technologies.
|
||||||
|
|
||||||
|
### Example Code Snippet:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Example function to interact with OLLAMA
|
||||||
|
from ollama import OllamaClient
|
||||||
|
|
||||||
|
def get_response(user_query):
|
||||||
|
client = OllamaClient()
|
||||||
|
response = client.generate_response(user_query)
|
||||||
|
return response
|
||||||
|
|
||||||
|
# Flask endpoint for user interaction
|
||||||
|
from flask import Flask, request, jsonify
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
@app.route('/smartassist', methods=['POST'])
|
||||||
|
def smartassist():
|
||||||
|
data = request.json
|
||||||
|
user_query = data['query']
|
||||||
|
response = get_response(user_query)
|
||||||
|
return jsonify({"response": response})
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app.run(debug=True)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Conclusion:
|
||||||
|
|
||||||
|
By developing SmartAssist, we will create a versatile and intelligent assistant that can handle various tasks through natural language processing, providing users with personalized assistance across multiple platforms. This project not only showcases the power of OLLAMA but also highlights the importance of continuous learning and adaptation in AI-driven applications.
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# A requirements file for the project.
|
||||||
|
#
|
||||||
|
# Run 'pip install -r smartassist/requirements_dev.txt' to install all required packages.
|
||||||
|
|
||||||
|
|
||||||
|
ollama
|
||||||
|
Flask
|
||||||
|
flask_cors
|
||||||
|
requests
|
||||||
|
PyYAML
|
||||||
@@ -0,0 +1,179 @@
|
|||||||
|
|
||||||
|
# Import the necessary functions from ollama, Flask, requests, threading
|
||||||
|
from ollama import Client
|
||||||
|
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.
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import utils
|
||||||
|
from utils import GlobalState
|
||||||
|
|
||||||
|
# Create a logger for this module
|
||||||
|
global_state = GlobalState() # Import the singleton that holds global states (e.g., logger)
|
||||||
|
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)
|
||||||
|
logger.debug("Current working directory: %s", os.getcwd())
|
||||||
|
|
||||||
|
# Initialize a Flask application
|
||||||
|
app = Flask(__name__)
|
||||||
|
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)
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
"""
|
||||||
|
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 '/'")
|
||||||
|
# api_endpoint = os.environ['BE_API_ENDPOINT'] # Retrieve the environment variable
|
||||||
|
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:
|
||||||
|
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 (all characters): %s", client_html) # Print to see if it's loading
|
||||||
|
|
||||||
|
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>')
|
||||||
|
def serve_static(filename):
|
||||||
|
return send_from_directory(app.config['STATIC_FOLDER'], filename)
|
||||||
|
|
||||||
|
|
||||||
|
# CORS(app, resources={
|
||||||
|
# r"/api/chat": {
|
||||||
|
# "origins": "*",
|
||||||
|
# "headers": ["Origin", "Content-Type", "Authorization"],
|
||||||
|
# }
|
||||||
|
# })
|
||||||
|
|
||||||
|
CORS(app, resources={
|
||||||
|
r"/api/chat": {
|
||||||
|
"origins": "*"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/api/chat', methods=['POST'])
|
||||||
|
def chat(url_server = "http://localhost:11434/api/generate", model = "phi3:mini"):
|
||||||
|
"""
|
||||||
|
This function handles the chat. The frontend client (web browser) calls the
|
||||||
|
backend server through this endpoint (/api/chat) that manage queries
|
||||||
|
to the LLM (Large Language Model) server and it also manages the response
|
||||||
|
from the LLM server.
|
||||||
|
"""
|
||||||
|
# Get the message from the JSON in the request body
|
||||||
|
data = request.get_json()
|
||||||
|
message = data.get('query')
|
||||||
|
url_server = data.get('url_server', url_server) # Use provided URL or default
|
||||||
|
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:
|
||||||
|
url = url_server
|
||||||
|
headers = {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
}
|
||||||
|
|
||||||
|
response = requests.post(url,
|
||||||
|
headers=headers,
|
||||||
|
data=json.dumps(data_to_send))
|
||||||
|
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()
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
logger.error("Request Exception: %s", str(e))
|
||||||
|
return jsonify({'error': 'Failed to process request'}), 500
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
logger.error("JSON Decode Error: %s", str(e)) # Corresponds to print(f"JSON Decode Error: {e}")
|
||||||
|
return jsonify({'error': 'Invalid JSON response from server'}), 500
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/smartassist', methods=["POST"])
|
||||||
|
def smartassist():
|
||||||
|
# Extract the query from the incoming JSON data
|
||||||
|
data = request.json
|
||||||
|
user_query = data['query']
|
||||||
|
|
||||||
|
# Get the response from the OLLAMA API based on the user's query
|
||||||
|
# NOTE: Should we append message history here? Maybe interact with SQLlite?
|
||||||
|
response = get_response(user_query)
|
||||||
|
|
||||||
|
# Return the response as a JSON object in the HTTP response
|
||||||
|
return jsonify({"response": response})
|
||||||
|
|
||||||
|
def get_response(user_query):
|
||||||
|
client = Client() # Create a client object for interacting with OLLAMA API
|
||||||
|
response = client.generate_response(user_query) # Generate and retrieve the response based on user's query
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
def run_flask(fport=5005):
|
||||||
|
"""
|
||||||
|
Starts the Flask server
|
||||||
|
"""
|
||||||
|
# Flask endpoint for user interaction
|
||||||
|
logger.debug("Entering run_flask()")
|
||||||
|
# app.run(port = str(str(fport)), debug=False)
|
||||||
|
app.run(port = str(str(fport)), debug=True)
|
||||||
|
# app.run(port=5000, debug=True, use_reloader=False)
|
||||||
|
logger.debug("Exiting run_flask()")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
# Run the Flask application
|
||||||
|
run_flask()
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Ollama Chat</title>
|
||||||
|
<link rel="stylesheet" href="/css/clientstyle.css">
|
||||||
|
<!-- <link rel="stylesheet" href="python_test/smartassist/src/css/clientstyle.css"> -->
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Ollama Chat</h1>
|
||||||
|
<div id="chatbox">
|
||||||
|
<!-- messages will be rendered here -->
|
||||||
|
</div>
|
||||||
|
<textarea id="userInput" placeholder="Type your message..." rows="5"></textarea>
|
||||||
|
<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>
|
||||||
|
const chatContainer = document.getElementById('chatbox');
|
||||||
|
|
||||||
|
// Handle resize events
|
||||||
|
window.addEventListener('resize',
|
||||||
|
function() {
|
||||||
|
chatContainer.style.height = 'auto';
|
||||||
|
});
|
||||||
|
|
||||||
|
const userInputElement = document.getElementById('userInput');
|
||||||
|
|
||||||
|
userInputElement.addEventListener('keydown', function(event) {
|
||||||
|
if (event.shiftKey && event.key === 'Enter') { // Shift+Enter for newline
|
||||||
|
event.preventDefault();
|
||||||
|
userInputElement.value += '\n';
|
||||||
|
} else if (event.key === 'Enter') { // Enter to send message
|
||||||
|
window.frontendApi.sendMessage();
|
||||||
|
userInputElement.value = ''; // Clear the input field after sending
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- Get the javascript handling communication with the backend -->
|
||||||
|
<script src="/js/frontend.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
# Start all services
|
||||||
|
import subprocess
|
||||||
|
import os
|
||||||
|
import yaml
|
||||||
|
import json
|
||||||
|
import socket
|
||||||
|
import urllib.parse
|
||||||
|
from backend import run_flask
|
||||||
|
import logging
|
||||||
|
import utils
|
||||||
|
from utils import GlobalState
|
||||||
|
|
||||||
|
global_state = GlobalState() # Configure root logger. The level will be adjusted later based on config file
|
||||||
|
logger = global_state.getLogger(__name__) # Logger for this module, inherit properties of the root logger
|
||||||
|
|
||||||
|
def configure():
|
||||||
|
"""
|
||||||
|
Reads YAML configruation file into dictionary, parse it and fill all referenceed
|
||||||
|
environment variables with their values.
|
||||||
|
"""
|
||||||
|
####################################
|
||||||
|
# Read YAML config
|
||||||
|
####################################
|
||||||
|
# Load configuration file that defines parameters for services
|
||||||
|
with open('./smartassist/config/smartassist.yaml') as f:
|
||||||
|
config = yaml.safe_load(f)
|
||||||
|
|
||||||
|
def resolve_env_var(value):
|
||||||
|
if isinstance(value, str) and value.startswith("${") and value.endswith("}"):
|
||||||
|
env_var_name = value[2:-1] # Extract name between ${}
|
||||||
|
return os.getenv(env_var_name, None)
|
||||||
|
return value
|
||||||
|
|
||||||
|
def update_dict_with_env_vars(d):
|
||||||
|
for key in d:
|
||||||
|
if isinstance(d[key], dict):
|
||||||
|
update_dict_with_env_vars(d[key]) # Recursively check nested dictionaries
|
||||||
|
elif isinstance(d[key], str):
|
||||||
|
d[key] = resolve_env_var(d[key])
|
||||||
|
return d
|
||||||
|
|
||||||
|
# Update the config dictionary with resolved environment variables
|
||||||
|
updated_config = update_dict_with_env_vars(config)
|
||||||
|
|
||||||
|
####################################
|
||||||
|
# Extract global logging level
|
||||||
|
####################################
|
||||||
|
if isinstance(updated_config.get('logging'), dict): # Look for 'logging' key in config file
|
||||||
|
logging_config = updated_config['logging']
|
||||||
|
if isinstance(logging_config.get('level'), str): # Set to value of the yaml file if specified
|
||||||
|
global_state.set_log_level(logging_config['level'])
|
||||||
|
logger.debug("configure(): This logger now has effective log level %s", logger.getEffectiveLevel())
|
||||||
|
|
||||||
|
####################################
|
||||||
|
# Extract and export backend API
|
||||||
|
# endpoint as global state variable
|
||||||
|
####################################
|
||||||
|
if isinstance(updated_config.get('backend'), dict): # Look for 'backend' key
|
||||||
|
if isinstance(updated_config['backend'].get('url'), str): # Look for 'url' key
|
||||||
|
url = updated_config['backend'].get('url')
|
||||||
|
if isinstance(updated_config['backend'].get('api'), str): # Look for 'api' key
|
||||||
|
api = updated_config['backend'].get('api')
|
||||||
|
# backend_api_ep = url+api # Extract API endpoint if defined
|
||||||
|
logger.debug(f"Constructing endpoint address as url+api: {url+api}")
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
# def start_frontend(config):
|
||||||
|
# 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
|
||||||
|
# port = parsed_url.port # This is the server port
|
||||||
|
|
||||||
|
# # 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.
|
||||||
|
# with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||||
|
# try:
|
||||||
|
# s.bind((hostname, port))
|
||||||
|
# logger.debug("No server is running on %s -— starting one.", parsed_url.netloc)
|
||||||
|
# # Start frontend (web server) as a separate process
|
||||||
|
# subprocess.Popen(["python", "-m", "http.server", str(port)])
|
||||||
|
# except socket.error as e:
|
||||||
|
# if e.errno == 48:
|
||||||
|
# logger.debug("A server is already running on %s -— will use this.", parsed_url.netloc)
|
||||||
|
# else:
|
||||||
|
# raise # Unexpected error, re-raise it so we can see the traceback
|
||||||
|
# except Exception as e:
|
||||||
|
# logger.error("Failed to start frontend: %s", str(e)) # Corresponds to print(f"Failed to start frontend: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def start_backend(config):
|
||||||
|
parsed_url = urllib.parse.urlparse(config['backend']['url'])
|
||||||
|
# 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
|
||||||
|
logger.debug('Backend parsed url set to {}'.format(parsed_url))
|
||||||
|
logger.debug('Backend port set to {}'.format(port))
|
||||||
|
|
||||||
|
try:
|
||||||
|
run_flask(fport = port)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("Failed to start backend: %s", str(e)) # Corresponds to print(f"Failed to start backend: {e}")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
conf = configure() # Read config from file and set up config dict
|
||||||
|
logger.debug('conf dictionary set to {}'.format(json.dumps(conf, indent=4)))
|
||||||
|
# start_frontend(config=conf) # Not needed as we are using Flask for backend now
|
||||||
|
start_backend(config=conf)
|
||||||
|
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
background-color: #f4f4f4;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 100vh;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#chatbox {
|
||||||
|
width: calc(50% - 60px); /* Adjust width for input and button */
|
||||||
|
/* max-width: 500px; */
|
||||||
|
height: 600px;
|
||||||
|
/* background-color: #fff8bc; */
|
||||||
|
background-color: #ffffff;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 20px;
|
||||||
|
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
||||||
|
overflow: auto; /* Allow horizontal and vertical scrolling of the chatbox */
|
||||||
|
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 {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-message {
|
||||||
|
background-color: #9cc1ecbb;
|
||||||
|
padding: 10px 15px;
|
||||||
|
border-radius: 10px;
|
||||||
|
text-align: left; /* Align user messages to the left */
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-response {
|
||||||
|
/* background-color: #f0f8ff; */
|
||||||
|
background-color: #f5ecd0;
|
||||||
|
padding: 10px 15px;
|
||||||
|
border-radius: 10px;
|
||||||
|
text-align: left; /* Align AI responses to the left */
|
||||||
|
}
|
||||||
|
|
||||||
|
#userInput {
|
||||||
|
width: calc(50% - 60px); /* Adjust width for input and button */
|
||||||
|
padding: 10px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
font-family: 'Courier New', Courier, monospace; /* Fixed width typeface */
|
||||||
|
}
|
||||||
|
|
||||||
|
#userInput:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #66afe9; /* Blue outline on focus */
|
||||||
|
}
|
||||||
|
|
||||||
|
button[onclick="sendMessage()"] {
|
||||||
|
background-color: #4CAF50; /* Green */
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
padding: 10px 20px;
|
||||||
|
text-align: center;
|
||||||
|
text-decoration: none;
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 16px;
|
||||||
|
border-radius: 5px;
|
||||||
|
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 */
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +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
|
||||||
|
const chatbox = document.getElementById('chatbox');
|
||||||
|
const userInput = document.getElementById('userInput');
|
||||||
|
const parser = window.markdownit({
|
||||||
|
linkify: true,
|
||||||
|
strikethrough: true,
|
||||||
|
});
|
||||||
|
parser.enable(['table']);
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
const frontendApi = {
|
||||||
|
// Define a function to send the user's message to the AI
|
||||||
|
sendMessage: function() {
|
||||||
|
if (!window.apiEndpoint || !window.useModel) { // Check if we're ready before proceeding
|
||||||
|
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();
|
||||||
|
// Check if the message is not empty
|
||||||
|
if (query !== '') {
|
||||||
|
fetch(window.apiEndpoint, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ query, model: window.useModel }), // 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
|
||||||
|
this.renderMessage(query, 'user-message');
|
||||||
|
// Render the AI's response in the chatbox
|
||||||
|
this.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
|
||||||
|
renderMessage: function(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;
|
||||||
|
// Use the markdown-it parser
|
||||||
|
const html = parser.render(text);
|
||||||
|
messageElement.innerHTML = html;
|
||||||
|
// Append the message element to the chatbox immediately
|
||||||
|
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
|
||||||
|
});
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<!-- <title>Frontend</title> -->
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- This iframe will hold the content from client.html -->
|
||||||
|
<!-- 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 -->
|
||||||
|
<script>
|
||||||
|
const clientFrame = document.getElementById('client-frame');
|
||||||
|
|
||||||
|
function resizeIframe() {
|
||||||
|
clientFrame.style.height = window.innerHeight - 50 + 'px'; // Adjust the subtraction for padding/margins if needed
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('resize', resizeIframe);
|
||||||
|
resizeIframe(); // Call it once on page load
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
# This module contains definitions of variables, functions, classes, et cetera, that are
|
||||||
|
# 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.
|
||||||
|
import logging
|
||||||
|
|
||||||
|
class GlobalState:
|
||||||
|
"""
|
||||||
|
This class holds various variables and methods which are accessible across
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
_instance = None # Private class attribute to hold the single instance of the class
|
||||||
|
|
||||||
|
def __new__(cls):
|
||||||
|
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
|
||||||
|
|
||||||
|
def configure_logging(self, level=None):
|
||||||
|
"""Set up logging for the project."""
|
||||||
|
if level is None:
|
||||||
|
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()}")
|
||||||
|
|
||||||
|
def set_log_level(self, level = 'INFO'):
|
||||||
|
"""Set the logging level."""
|
||||||
|
self.log_level = level
|
||||||
|
self.configure_logging()
|
||||||
|
|
||||||
|
def get_log_level(self):
|
||||||
|
"""Getter for log_level attribute."""
|
||||||
|
return self.log_level
|
||||||
|
|
||||||
|
def get_effective_log_level(self):
|
||||||
|
"""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
|
||||||
|
|
||||||
Reference in New Issue
Block a user