Upgraded database classes

This commit is contained in:
Saifeddine ALOUI 2023-04-10 10:27:25 +02:00
parent 29dc3ff5b1
commit eead6bec64
5 changed files with 245 additions and 149 deletions

59
app.py
View File

@ -5,7 +5,7 @@ import traceback
from datetime import datetime
from concurrent.futures import ThreadPoolExecutor
import sys
from db import Discussion, export_to_json, check_discussion_db, last_discussion_has_messages
from db import DiscussionsDB, Discussion
from flask import (
Flask,
Response,
@ -28,6 +28,9 @@ class Gpt4AllWebUI:
self.current_discussion = None
self.app = _app
self.db_path = args.db_path
self.db = DiscussionsDB(self.db_path)
# If the database is empty, populate it with tables
self.db.populate()
# workaround for non interactive mode
self.full_message = ""
@ -35,17 +38,21 @@ class Gpt4AllWebUI:
# This is the queue used to stream text to the ui as the bot spits out its response
self.text_queue = Queue(0)
self.add_endpoint(
"/list_models", "list_models", self.list_models, methods=["GET"]
)
self.add_endpoint(
"/list_discussions", "list_discussions", self.list_discussions, methods=["GET"]
)
self.add_endpoint("/", "", self.index, methods=["GET"])
self.add_endpoint("/export_discussion", "export_discussion", self.export_discussion, methods=["GET"])
self.add_endpoint("/export", "export", self.export, methods=["GET"])
self.add_endpoint(
"/new_discussion", "new_discussion", self.new_discussion, methods=["GET"]
)
self.add_endpoint("/bot", "bot", self.bot, methods=["POST"])
self.add_endpoint(
"/discussions", "discussions", self.discussions, methods=["GET"]
)
self.add_endpoint("/rename", "rename", self.rename, methods=["POST"])
self.add_endpoint(
"/load_discussion", "load_discussion", self.load_discussion, methods=["POST"]
@ -64,11 +71,6 @@ class Gpt4AllWebUI:
"/update_model_params", "update_model_params", self.update_model_params, methods=["POST"]
)
self.add_endpoint(
"/list_models", "list_models", self.list_models, methods=["GET"]
)
self.add_endpoint(
"/get_args", "get_args", self.get_args, methods=["GET"]
)
@ -80,6 +82,22 @@ class Gpt4AllWebUI:
models = [f.name for f in models_dir.glob('*.bin')]
return jsonify(models)
def list_discussions(self):
try:
discussions = self.db.get_discussions()
return jsonify(discussions)
except Exception as ex:
print(ex)
return jsonify({
"status":"Error",
"content": "<b style='color:red;'>Exception :<b>"
+ str(ex)
+ "<br>"
+ traceback.format_exc()
+ "<br>Please report exception"
})
def prepare_a_new_chatbot(self):
# Create chatbot
self.chatbot_bindings = self.create_chatbot()
@ -164,8 +182,11 @@ GPT4All:Welcome! I'm here to assist you with anything you need. What can I do fo
return message
def export(self):
return jsonify(export_to_json(self.db_path))
return jsonify(self.db.export_to_json())
def export_discussion(self):
return jsonify(self.full_message)
def generate_message(self):
self.generating=True
self.text_queue=Queue()
@ -231,7 +252,7 @@ GPT4All:Welcome! I'm here to assist you with anything you need. What can I do fo
self.stop = True
try:
if self.current_discussion is None or not last_discussion_has_messages(
if self.current_discussion is None or not self.db.does_last_discussion_have_messages(
self.db_path
):
self.current_discussion = Discussion.create_discussion(self.db_path)
@ -258,19 +279,6 @@ GPT4All:Welcome! I'm here to assist you with anything you need. What can I do fo
+ "<br>Please report exception"
)
def discussions(self):
try:
discussions = Discussion.get_discussions(self.db_path)
return jsonify(discussions)
except Exception as ex:
print(ex)
return (
"<b style='color:red;'>Exception :<b>"
+ str(ex)
+ "<br>"
+ traceback.format_exc()
+ "<br>Please report exception"
)
def rename(self):
data = request.get_json()
@ -295,7 +303,7 @@ GPT4All:Welcome! I'm here to assist you with anything you need. What can I do fo
def load_discussion(self):
data = request.get_json()
discussion_id = data["id"]
self.current_discussion = Discussion(discussion_id, self.db_path)
self.current_discussion = Discussion(discussion_id, self.db)
messages = self.current_discussion.get_messages()
self.full_message = ""
@ -426,7 +434,6 @@ if __name__ == "__main__":
parser.set_defaults(debug=False)
args = parser.parse_args()
check_discussion_db(args.db_path)
executor = ThreadPoolExecutor(max_workers=2)
app.config['executor'] = executor

270
db.py
View File

@ -1,98 +1,109 @@
import sqlite3
# =================================== Database ==================================================================
class Discussion:
def __init__(self, discussion_id, db_path="database.db"):
self.discussion_id = discussion_id
class DiscussionsDB:
def __init__(self, db_path="database.db"):
self.db_path = db_path
@staticmethod
def create_discussion(db_path="database.db", title="untitled"):
with sqlite3.connect(db_path) as conn:
cur = conn.cursor()
cur.execute("INSERT INTO discussion (title) VALUES (?)", (title,))
discussion_id = cur.lastrowid
conn.commit()
return Discussion(discussion_id, db_path)
@staticmethod
def get_discussion(db_path="database.db", discussion_id=0):
return Discussion(discussion_id, db_path)
def add_message(self, sender, content):
def populate(self):
"""
create database schema
"""
print("Checking discussions database...")
with sqlite3.connect(self.db_path) as conn:
cur = conn.cursor()
cur.execute(
"INSERT INTO message (sender, content, discussion_id) VALUES (?, ?, ?)",
(sender, content, self.discussion_id),
)
message_id = cur.lastrowid
conn.commit()
return message_id
@staticmethod
def get_discussions(db_path):
with sqlite3.connect(db_path) as conn:
cursor = conn.cursor()
cursor.execute("SELECT * FROM discussion")
rows = cursor.fetchall()
cursor.execute("""
CREATE TABLE IF NOT EXISTS discussion (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT
)
""")
cursor.execute("""
CREATE TABLE IF NOT EXISTS message (
id INTEGER PRIMARY KEY AUTOINCREMENT,
sender TEXT NOT NULL,
content TEXT NOT NULL,
discussion_id INTEGER NOT NULL,
FOREIGN KEY (discussion_id) REFERENCES discussion(id)
)
"""
)
conn.commit()
def select(self, query, fetch_all=True):
"""
Execute the specified SQL select query on the database,
with optional parameters.
Returns the cursor object for further processing.
"""
with sqlite3.connect(self.db_path) as conn:
cursor = conn.cursor()
cursor.execute(query)
if fetch_all:
return cursor.fetchall()
else:
return cursor.fetchone()
def delete(self, query, fetch_all=True):
"""
Execute the specified SQL delete query on the database,
with optional parameters.
Returns the cursor object for further processing.
"""
with sqlite3.connect(self.db_path) as conn:
cursor = conn.cursor()
cursor.execute(query)
conn.commit()
def insert(self, query, params=None):
"""
Execute the specified INSERT SQL query on the database,
with optional parameters.
Returns the ID of the newly inserted row.
"""
with sqlite3.connect(self.db_path) as conn:
self.conn = conn
cursor = self.execute(query, params)
rowid = cursor.lastrowid
conn.commit()
self.conn = None
return rowid
def create_discussion(self, title="untitled"):
"""Creates a new discussion
Args:
title (str, optional): The title of the discussion. Defaults to "untitled".
Returns:
Discussion: A Discussion instance
"""
discussion_id = self.insert(f"INSERT INTO discussion (title) VALUES ({title})")
return Discussion(discussion_id, self)
def build_discussion(self, discussion_id=0):
return Discussion(discussion_id, self)
def get_discussions(self):
rows = self.select("SELECT * FROM discussion")
return [{"id": row[0], "title": row[1]} for row in rows]
@staticmethod
def rename(db_path, discussion_id, title):
with sqlite3.connect(db_path) as conn:
cursor = conn.cursor()
cursor.execute(
"UPDATE discussion SET title=? WHERE id=?", (title, discussion_id)
)
conn.commit()
def delete_discussion(self):
with sqlite3.connect(self.db_path) as conn:
cur = conn.cursor()
cur.execute(
"DELETE FROM message WHERE discussion_id=?", (self.discussion_id,)
)
cur.execute("DELETE FROM discussion WHERE id=?", (self.discussion_id,))
conn.commit()
def get_messages(self):
with sqlite3.connect(self.db_path) as conn:
cur = conn.cursor()
cur.execute(
"SELECT * FROM message WHERE discussion_id=?", (self.discussion_id,)
)
rows = cur.fetchall()
return [{"sender": row[1], "content": row[2], "id": row[0]} for row in rows]
def update_message(self, message_id, new_content):
with sqlite3.connect(self.db_path) as conn:
cur = conn.cursor()
cur.execute(
"UPDATE message SET content = ? WHERE id = ?", (new_content, message_id)
)
conn.commit()
def remove_discussion(self):
with sqlite3.connect(self.db_path) as conn:
conn.cursor().execute(
"DELETE FROM discussion WHERE id=?", (self.discussion_id,)
)
conn.commit()
def does_last_discussion_have_messages(self):
last_message = self.select("SELECT * FROM message ORDER BY id DESC LIMIT 1", fetch_all=False)
return last_message is not None
def remove_discussions(self):
self.delete("DELETE FROM message")
self.delete("DELETE FROM discussion")
def last_discussion_has_messages(db_path="database.db"):
with sqlite3.connect(db_path) as conn:
cursor = conn.cursor()
cursor.execute("SELECT * FROM message ORDER BY id DESC LIMIT 1")
last_message = cursor.fetchone()
return last_message is not None
def export_to_json(db_path="database.db"):
with sqlite3.connect(db_path) as conn:
cur = conn.cursor()
cur.execute("SELECT * FROM discussion")
def export_to_json(self):
cur = self.execute("SELECT * FROM discussion")
discussions = []
for row in cur.fetchall():
discussion_id = row[0]
@ -106,41 +117,72 @@ def export_to_json(db_path="database.db"):
return discussions
def remove_discussions(db_path="database.db"):
with sqlite3.connect(db_path) as conn:
cur = conn.cursor()
cur.execute("DELETE FROM message")
cur.execute("DELETE FROM discussion")
conn.commit()
class Discussion:
def __init__(self, discussion_id, discussions_db:DiscussionsDB):
self.discussion_id = discussion_id
self.discussions_db = discussions_db
def add_message(self, sender, content):
"""Adds a new message to the discussion
# create database schema
def check_discussion_db(db_path):
print("Checking discussions database...")
with sqlite3.connect(db_path) as conn:
cur = conn.cursor()
cur.execute(
"""
CREATE TABLE IF NOT EXISTS discussion (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT
)
Args:
sender (str): The sender name
content (str): The text sent by the sender
Returns:
int: The added message id
"""
self.discussions_db.execute(
f"INSERT INTO message (sender, content, discussion_id) VALUES ({sender}, {content}, {self.discussion_id})",
)
cur.execute(
"""
CREATE TABLE IF NOT EXISTS message (
id INTEGER PRIMARY KEY AUTOINCREMENT,
sender TEXT NOT NULL,
content TEXT NOT NULL,
discussion_id INTEGER NOT NULL,
FOREIGN KEY (discussion_id) REFERENCES discussion(id)
)
"""
)
conn.commit()
message_id = self.discussions_db.conn.cursor().lastrowid
self.discussions_db.commit()
return message_id
print("Ok")
def rename(self, new_title):
"""Renames the discussion
Args:
new_title (str): The nex discussion name
"""
self.discussions_db.execute(
f"UPDATE discussion SET title={new_title} WHERE id={self.discussion_id}"
)
self.discussions_db.commit()
def delete_discussion(self):
"""Deletes the discussion
"""
self.discussions_db.execute(
f"DELETE FROM message WHERE discussion_id={self.discussion_id}"
)
self.discussions_db.execute(
f"DELETE FROM discussion WHERE id={self.discussion_id}"
)
self.discussions_db.commit()
def get_messages(self):
"""Gets a list of messages information
Returns:
list: List of entries in the format {"sender":sender name, "content":message content,"id":message id}
"""
rows = self.discussions_db.select(
f"SELECT * FROM message WHERE discussion_id={self.discussion_id}"
)
return [{"sender": row[1], "content": row[2], "id": row[0]} for row in rows]
def update_message(self, message_id, new_content):
"""Updates the content of a message
Args:
message_id (int): The id of the message to be changed
new_content (str): The nex message content
"""
self.discussions_db.execute(
f"UPDATE message SET content = {new_content} WHERE id = {message_id}"
)
self.discussions_db.commit()
# ========================================================================================================================

View File

@ -110,6 +110,18 @@ function addMessage(sender, message, id, can_edit=false) {
messageElement.appendChild(messageTextElement);
if(can_edit)
{
// Create buttons container
const buttonsContainer = document.createElement('div');
// Add the 'flex' class to the div
buttonsContainer.classList.add('flex');
// Add the 'justify-end' class to the div
buttonsContainer.classList.add('justify-end');
// Set the width and height of the container to 100%
buttonsContainer.style.width = '100%';
buttonsContainer.style.height = '100%';
const editButton = document.createElement('button');
editButton.classList.add('my-1','mx-1','outline-none','px-4','bg-accent','text-black','rounded-md','hover:bg-[#7ba0ea]','active:bg-[#3d73e1]','transition-colors','ease-in-out');
editButton.style.float = 'right'; // set the float property to right
@ -121,7 +133,7 @@ function addMessage(sender, message, id, can_edit=false) {
inputField.classList.add('font-medium', 'text-md', 'border', 'border-gray-300', 'p-1');
inputField.value = messageTextElement.innerHTML;
editButton.style.display="none"
buttonsContainer.style.display="none"
const saveButton = document.createElement('button');
saveButton.classList.add('bg-green-500', 'hover:bg-green-700', 'text-white', 'font-bold', 'py-2', 'px-4', 'rounded', 'my-2', 'ml-2');
@ -143,10 +155,10 @@ function addMessage(sender, message, id, can_edit=false) {
.catch(error => {
console.error('There was a problem updating the message:', error);
});
editButton.style.display='inline-block'
messageElement.replaceChild(messageTextElement, inputField);
//messageElement.removeChild(inputField);
messageElement.removeChild(saveButton);
buttonsContainer.style.display='inline-block'
messageElement.replaceChild(messageTextElement, inputField);
//messageElement.removeChild(inputField);
messageElement.removeChild(saveButton);
});
messageElement.replaceChild(inputField, messageTextElement);
@ -154,7 +166,8 @@ function addMessage(sender, message, id, can_edit=false) {
inputField.focus();
});
messageElement.appendChild(editButton);
buttonsContainer.appendChild(editButton);
messageElement.appendChild(buttonsContainer);
}
chatWindow.appendChild(messageElement);
chatWindow.appendChild(hiddenElement);

View File

@ -4,7 +4,7 @@ function populate_discussions_list()
// Populate discussions list
const discussionsList = document.querySelector('#discussions-list');
discussionsList.innerHTML = "";
fetch('/discussions')
fetch('/list_discussions')
.then(response => response.json())
.then(discussions => {
discussions.forEach(discussion => {
@ -162,7 +162,39 @@ function populate_discussions_list()
// First time we populate the discussions list
populate_discussions_list()
// adding export discussion button
const exportDiscussionButton = document.createElement('button');
exportDiscussionButton.classList.add(
'my-1',
'mx-1',
'outline-none',
'px-4',
'bg-accent',
'text-black',
'rounded-md',
'hover:bg-[#7ba0ea]',
'active:bg-[#3d73e1]',
'transition-colors',
'ease-in-out'
);
exportDiscussionButton.style.float = 'right'; // set the float property to right
exportDiscussionButton.style.display='inline-block'
exportDiscussionButton.innerHTML = 'Export discussion to text';
exportDiscussionButton.addEventListener('click', () => {
fetch('/bot', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ message })
}).then(function(response) {
}).catch(function(error){
});
});
const actionBtns = document.querySelector('#action-buttons');
actionBtns.appendChild(exportDiscussionButton);
const newDiscussionBtn = document.querySelector('#new-discussion-btn');

View File

@ -19,6 +19,10 @@
<div id="discussions-list" class="h-96 overflow-y-auto">
</div>
<div id="action-buttons" class="h-96 overflow-y-auto">
<input type="button" value="New Discussion" id="new-discussion-btn" class="my-1 mx-1 outline-none px-4 bg-accent text-black rounded-md hover:bg-[#7ba0ea] active:bg-[#3d73e1] transition-colors ease-in-out">
<input type="button" value="Export" id="export-button" class="my-1 mx-1 outline-none px-4 bg-accent text-black rounded-md hover:bg-[#7ba0ea] active:bg-[#3d73e1] transition-colors ease-in-out">
</div>
</section>
<section class="md:h-1/2 md:border-b border-accent flex flex md:flex-col">
<div>
@ -72,8 +76,6 @@
</section>
</main>
<footer class="border-t border-accent flex">
<input type="button" value="New Chat" id="new-discussion-btn" class="my-1 mx-1 outline-none px-4 bg-accent text-black rounded-md hover:bg-[#7ba0ea] active:bg-[#3d73e1] transition-colors ease-in-out">
<input type="button" value="Export" id="export-button" class="my-1 mx-1 outline-none px-4 bg-accent text-black rounded-md hover:bg-[#7ba0ea] active:bg-[#3d73e1] transition-colors ease-in-out">
<form id="chat-form" class="flex w-full">
<input type="text" id="user-input" placeholder="Type your message..." class="bg-secondary my-1 mx-1 outline-none drop-shadow-sm w-full rounded-md p-2">
<input type="submit" value="Send" id="submit-input" class="my-1 mx-1 outline-none px-4 bg-accent text-black rounded-md hover:bg-[#7ba0ea] active:bg-[#3d73e1] transition-colors ease-in-out">