diff --git a/.gitignore b/.gitignore
index b5e5ead4..3fa045b4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -150,6 +150,10 @@ configs/*
personalities/*
!personalities/gpt4all_chatbot.yaml
+# personalities other than the default one
+databases/*
+!databases/.keep
+
# extensions
extensions/
!extensions/.keep
diff --git a/app.py b/app.py
index a5b89acc..beff611a 100644
--- a/app.py
+++ b/app.py
@@ -13,10 +13,9 @@ import argparse
import json
import re
import traceback
-from datetime import datetime
from concurrent.futures import ThreadPoolExecutor
import sys
-from db import DiscussionsDB, Discussion
+from pyGpt4All.db import DiscussionsDB, Discussion
from flask import (
Flask,
Response,
@@ -26,34 +25,20 @@ from flask import (
stream_with_context,
send_from_directory
)
-from pyllamacpp.model import Model
from queue import Queue
from pathlib import Path
import gc
app = Flask("GPT4All-WebUI", static_url_path="/static", static_folder="static")
import time
-from config import load_config, save_config
+from pyGpt4All.config import load_config, save_config
+from pyGpt4All.api import GPT4AllAPI
import shutil
-class Gpt4AllWebUI:
-
+class Gpt4AllWebUI(GPT4AllAPI):
def __init__(self, _app, config:dict, personality:dict, config_file_path) -> None:
- self.config = config
- self.config_file_path = config_file_path
- self.personality = personality
- self.current_discussion = None
- self.current_message_id = 0
- self.app = _app
- self.db_path = config["db_path"]
- self.db = DiscussionsDB(self.db_path)
- # If the database is empty, populate it with tables
- self.db.populate()
+ super().__init__(config, personality, config_file_path)
+
+ self.app = _app
- # workaround for non interactive mode
- self.full_message = ""
- self.full_message_list = []
- self.prompt_message = ""
- # 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"]
@@ -131,7 +116,6 @@ class Gpt4AllWebUI:
"/help", "help", self.help, methods=["GET"]
)
- self.prepare_a_new_chatbot()
def list_models(self):
models_dir = Path('./models') # replace with the actual path to the models folder
@@ -161,63 +145,6 @@ class Gpt4AllWebUI:
return jsonify(discussions)
- def prepare_a_new_chatbot(self):
- # Create chatbot
- self.chatbot_bindings = self.create_chatbot()
-
-
- def create_chatbot(self):
- try:
- return Model(
- ggml_model=f"./models/{self.config['model']}",
- n_ctx=self.config['ctx_size'],
- seed=self.config['seed'],
- )
- except Exception as ex:
- print(f"Exception {ex}")
- return None
- def condition_chatbot(self, conditionning_message):
- if self.current_discussion is None:
- self.current_discussion = self.db.load_last_discussion()
-
- message_id = self.current_discussion.add_message(
- "conditionner",
- conditionning_message,
- DiscussionsDB.MSG_TYPE_CONDITIONNING,
- 0,
- self.current_message_id
- )
- self.current_message_id = message_id
- if self.personality["welcome_message"]!="":
- message_id = self.current_discussion.add_message(
- self.personality["name"], self.personality["welcome_message"],
- DiscussionsDB.MSG_TYPE_NORMAL,
- 0,
- self.current_message_id
- )
-
- self.current_message_id = message_id
- return message_id
-
- def prepare_query(self):
- self.bot_says = ""
- self.full_text = ""
- self.is_bot_text_started = False
- #self.current_message = message
-
- def new_text_callback(self, text: str):
- print(text, end="")
- sys.stdout.flush()
- self.full_text += text
- if self.is_bot_text_started:
- self.bot_says += text
- self.full_message += text
- self.text_queue.put(text)
-
- #if self.current_message in self.full_text:
- if len(self.prompt_message) < len(self.full_text):
- self.is_bot_text_started = True
-
def add_endpoint(
self,
endpoint=None,
@@ -253,24 +180,6 @@ class Gpt4AllWebUI:
def export_discussion(self):
return jsonify(self.full_message)
- def generate_message(self):
- self.generating=True
- self.text_queue=Queue()
- gc.collect()
-
- self.chatbot_bindings.generate(
- self.prompt_message,#self.full_message,#self.current_message,
- new_text_callback=self.new_text_callback,
- n_predict=len(self.current_message)+self.config['n_predict'],
- temp=self.config['temp'],
- top_k=self.config['top_k'],
- top_p=self.config['top_p'],
- repeat_penalty=self.config['repeat_penalty'],
- repeat_last_n = self.config['repeat_last_n'],
- #seed=self.config['seed'],
- n_threads=8
- )
- self.generating=False
@stream_with_context
def parse_to_prompt_stream(self, message, message_id):
@@ -281,7 +190,7 @@ class Gpt4AllWebUI:
print(f"Received message : {message}")
# First we need to send the new message ID to the client
response_id = self.current_discussion.add_message(
- self.personality["name"], ""
+ self.personality["name"], "", parent = message_id
) # first the content is empty, but we'll fill it at the end
yield (
json.dumps(
@@ -295,15 +204,9 @@ class Gpt4AllWebUI:
)
)
- self.current_message = self.personality["message_prefix"] + message + self.personality["message_suffix"]
- self.full_message += self.current_message
- self.full_message_list.append(self.current_message)
-
- if len(self.full_message_list) > self.config["nb_messages_to_remember"]:
- self.prompt_message = self.personality["personality_conditionning"]+ '\n'.join(self.full_message_list[-self.config["nb_messages_to_remember"]:])
- else:
- self.prompt_message = self.full_message
- self.prepare_query()
+ # prepare query and reception
+ self.discussion_messages = self.prepare_query(message_id)
+ self.prepare_reception()
self.generating = True
app.config['executor'].submit(self.generate_message)
while self.generating or not self.text_queue.empty():
@@ -313,12 +216,8 @@ class Gpt4AllWebUI:
except :
time.sleep(1)
-
-
self.current_discussion.update_message(response_id, self.bot_says)
self.full_message_list.append(self.bot_says)
- #yield self.bot_says# .encode('utf-8').decode('utf-8')
- # TODO : change this to use the yield version in order to send text word by word
return "\n".join(bot_says)
@@ -331,11 +230,13 @@ class Gpt4AllWebUI:
else:
self.current_discussion = self.db.load_last_discussion()
+ message = request.json["message"]
message_id = self.current_discussion.add_message(
- "user", request.json["message"], parent=self.current_message_id
+ "user", message, parent=self.current_message_id
)
message = f"{request.json['message']}"
self.current_message_id = message_id
+
# Segmented (the user receives the output as it comes)
# We will first send a json entry that contains the message id and so on, then the text as it goes
return Response(
@@ -348,19 +249,12 @@ class Gpt4AllWebUI:
def run_to(self):
data = request.get_json()
message_id = data["id"]
-
self.stop = True
- message_id = self.current_discussion.add_message(
- "user", request.json["message"], parent=message_id
- )
-
- message = f"{request.json['message']}"
-
# Segmented (the user receives the output as it comes)
# We will first send a json entry that contains the message id and so on, then the text as it goes
return Response(
stream_with_context(
- self.parse_to_prompt_stream(message, message_id)
+ self.parse_to_prompt_stream("",message_id)
)
)
@@ -370,24 +264,6 @@ class Gpt4AllWebUI:
self.current_discussion.rename(title)
return "renamed successfully"
- def restore_discussion(self, full_message):
- self.prompt_message = full_message
-
- if len(self.full_message_list)>5:
- self.prompt_message = "\n".join(self.full_message_list[-5:])
-
- self.chatbot_bindings.generate(
- self.prompt_message,#full_message,
- new_text_callback=self.new_text_callback,
- n_predict=0,#len(full_message),
- temp=self.config['temp'],
- top_k=self.config['top_k'],
- top_p=self.config['top_p'],
- repeat_penalty= self.config['repeat_penalty'],
- repeat_last_n = self.config['repeat_last_n'],
- n_threads=8
- )
-
def load_discussion(self):
data = request.get_json()
if "id" in data:
@@ -402,15 +278,6 @@ class Gpt4AllWebUI:
messages = self.current_discussion.get_messages()
- self.full_message = ""
- self.full_message_list = []
- for message in messages:
- if message['sender']!="conditionner":
- self.full_message += message['sender'] + ": " + message['content'] + "\n"
- self.full_message_list.append(message['sender'] + ": " + message['content'])
- self.current_message_id=message['id']
- app.config['executor'].submit(self.restore_discussion, self.full_message)
-
return jsonify(messages)
def delete_discussion(self):
@@ -445,17 +312,8 @@ class Gpt4AllWebUI:
def new_discussion(self):
title = request.args.get("title")
- self.current_discussion = self.db.create_discussion(title)
- # Get the current timestamp
- timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
-
- app.config['executor'].submit(self.prepare_a_new_chatbot)
-
- self.full_message =""
-
- # Chatbot conditionning
- self.condition_chatbot(self.personality["personality_conditionning"])
-
+ timestamp = self.create_new_discussion(title)
+ app.config['executor'].submit(self.create_chatbot)
# Return a success response
return json.dumps({"id": self.current_discussion.discussion_id, "time": timestamp, "welcome_message":self.personality["welcome_message"]})
@@ -466,7 +324,7 @@ class Gpt4AllWebUI:
if self.config['model'] != model:
print("New model selected")
self.config['model'] = model
- self.prepare_a_new_chatbot()
+ self.create_chatbot()
self.config['n_predict'] = int(data["nPredict"])
self.config['seed'] = int(data["seed"])
diff --git a/configs/default.yaml b/configs/default.yaml
index 8f92bed8..c23af456 100644
--- a/configs/default.yaml
+++ b/configs/default.yaml
@@ -1,7 +1,8 @@
config: default
ctx_size: 512
-db_path: database.db
+db_path: databases/database.db
debug: false
+n_threads: 8
host: localhost
language: en-US
model: gpt4all-lora-quantized-ggml.bin
diff --git a/databases/.keep b/databases/.keep
new file mode 100644
index 00000000..e69de29b
diff --git a/personalities/gpt4all_chatbot.yaml b/personalities/gpt4all_chatbot.yaml
index 195461c5..527ab018 100644
--- a/personalities/gpt4all_chatbot.yaml
+++ b/personalities/gpt4all_chatbot.yaml
@@ -27,7 +27,7 @@ personality_conditionning: |
welcome_message: "Welcome! I am GPT4All A free and open discussion AI. What can I do for you today?"
# This prefix is added at the beginning of any message input by the user
-message_prefix: "\nuser: "
+message_prefix: "user: "
# This suffix is added at the end of any message input by the user
message_suffix: "\ngpt4all: "
diff --git a/pyGpt4All/api.py b/pyGpt4All/api.py
new file mode 100644
index 00000000..793b32a6
--- /dev/null
+++ b/pyGpt4All/api.py
@@ -0,0 +1,162 @@
+######
+# Project : GPT4ALL-UI
+# File : api.py
+# Author : ParisNeo with the help of the community
+# Supported by Nomic-AI
+# Licence : Apache 2.0
+# Description :
+# A simple api to communicate with gpt4all-ui and its models.
+######
+import gc
+import sys
+from queue import Queue
+from datetime import datetime
+from pyllamacpp.model import Model
+from pyGpt4All.db import DiscussionsDB
+
+class GPT4AllAPI():
+ def __init__(self, config:dict, personality:dict, config_file_path) -> None:
+ self.config = config
+ self.personality = personality
+ self.config_file_path = config_file_path
+
+ # This is the queue used to stream text to the ui as the bot spits out its response
+ self.text_queue = Queue(0)
+
+ # Keeping track of current discussion and message
+ self.current_discussion = None
+ self.current_message_id = 0
+
+ self.db_path = config["db_path"]
+
+ # Create database object
+ self.db = DiscussionsDB(self.db_path)
+
+ # If the database is empty, populate it with tables
+ self.db.populate()
+
+ # This is used to keep track of messages
+ self.full_message_list = []
+
+ # Build chatbot
+ self.chatbot_bindings = self.create_chatbot()
+ print("Chatbot created successfully")
+
+ # tests the model
+ """
+ self.prepare_reception()
+ self.discussion_messages = "Instruction: Act as gpt4all. A kind and helpful AI bot built to help users solve problems.\nuser: how to build a water rocket?\ngpt4all:"
+ self.chatbot_bindings.generate(
+ self.discussion_messages,
+ new_text_callback=self.new_text_callback,
+ n_predict=372,
+ temp=self.config['temp'],
+ top_k=self.config['top_k'],
+ top_p=self.config['top_p'],
+ repeat_penalty=self.config['repeat_penalty'],
+ repeat_last_n = self.config['repeat_last_n'],
+ #seed=self.config['seed'],
+ n_threads=self.config['n_threads']
+ )
+
+ """
+
+ # generation status
+ self.generating=False
+
+ def create_chatbot(self):
+ try:
+ return Model(
+ ggml_model=f"./models/{self.config['model']}",
+ n_ctx=self.config['ctx_size'],
+ seed=self.config['seed'],
+ )
+ except Exception as ex:
+ print(f"Exception {ex}")
+ return None
+
+ def condition_chatbot(self, conditionning_message):
+ if self.current_discussion is None:
+ self.current_discussion = self.db.load_last_discussion()
+
+ message_id = self.current_discussion.add_message(
+ "conditionner",
+ conditionning_message,
+ DiscussionsDB.MSG_TYPE_CONDITIONNING,
+ 0,
+ 0
+ )
+ self.current_message_id = message_id
+ if self.personality["welcome_message"]!="":
+ message_id = self.current_discussion.add_message(
+ self.personality["name"], self.personality["welcome_message"],
+ DiscussionsDB.MSG_TYPE_NORMAL,
+ 0,
+ self.current_message_id
+ )
+
+ self.current_message_id = message_id
+ return message_id
+
+ def prepare_reception(self):
+ self.bot_says = ""
+ self.full_text = ""
+ self.is_bot_text_started = False
+ #self.current_message = message
+
+ def create_new_discussion(self, title):
+ self.current_discussion = self.db.create_discussion(title)
+ # Get the current timestamp
+ timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
+
+ # Chatbot conditionning
+ self.condition_chatbot(self.personality["personality_conditionning"])
+ return timestamp
+
+ def prepare_query(self, message_id=-1):
+ messages = self.current_discussion.get_messages()
+ self.full_message_list = []
+ for message in messages:
+ if message["id"]<= message_id or message_id==-1:
+ if message["type"]!=self.db.MSG_TYPE_CONDITIONNING:
+ if message["sender"]==self.personality["name"]:
+ self.full_message_list.append(message["content"])
+ else:
+ self.full_message_list.append(self.personality["message_prefix"] + message["content"] + self.personality["message_suffix"])
+
+ if len(self.full_message_list) > self.config["nb_messages_to_remember"]:
+ discussion_messages = self.personality["personality_conditionning"]+ '\n'.join(self.full_message_list[-self.config["nb_messages_to_remember"]:])
+ else:
+ discussion_messages = self.personality["personality_conditionning"]+ '\n'.join(self.full_message_list)
+ return discussion_messages[:-1] # Removes the last return
+
+ def new_text_callback(self, text: str):
+ print(text, end="")
+ sys.stdout.flush()
+ self.full_text += text
+ if self.is_bot_text_started:
+ self.bot_says += text
+ self.text_queue.put(text)
+
+ #if self.current_message in self.full_text:
+ if len(self.discussion_messages) < len(self.full_text):
+ self.is_bot_text_started = True
+
+ def generate_message(self):
+ self.generating=True
+ self.text_queue=Queue()
+ gc.collect()
+ total_n_predict = len(self.discussion_messages)+self.config['n_predict']
+ self.chatbot_bindings.generate(
+ self.discussion_messages,
+ new_text_callback=self.new_text_callback,
+ n_predict=total_n_predict,
+ temp=self.config['temp'],
+ top_k=self.config['top_k'],
+ top_p=self.config['top_p'],
+ repeat_penalty=self.config['repeat_penalty'],
+ repeat_last_n = self.config['repeat_last_n'],
+ #seed=self.config['seed'],
+ n_threads=self.config['n_threads']
+ )
+ self.generating=False
diff --git a/config.py b/pyGpt4All/config.py
similarity index 100%
rename from config.py
rename to pyGpt4All/config.py
diff --git a/db.py b/pyGpt4All/db.py
similarity index 99%
rename from db.py
rename to pyGpt4All/db.py
index d95b7d21..63444ff0 100644
--- a/db.py
+++ b/pyGpt4All/db.py
@@ -256,7 +256,7 @@ class Discussion:
list: List of entries in the format {"id":message id, "sender":sender name, "content":message content, "type":message type, "rank": message rank}
"""
rows = self.discussions_db.select(
- f"SELECT * FROM message WHERE discussion_id={self.discussion_id}"
+ "SELECT * FROM message WHERE discussion_id=?", (self.discussion_id,)
)
return [{"id": row[0], "sender": row[1], "content": row[2], "type": row[3], "rank": row[4], "parent": row[5]} for row in rows]
diff --git a/extension.py b/pyGpt4All/extension.py
similarity index 100%
rename from extension.py
rename to pyGpt4All/extension.py
diff --git a/static/js/chat.js b/static/js/chat.js
index 6f9f30e8..4dc571b2 100644
--- a/static/js/chat.js
+++ b/static/js/chat.js
@@ -60,6 +60,10 @@ function addMessage(sender, message, id, rank = 0, can_edit = false) {
sendbtn.style.display = "none";
waitAnimation.style.display = "block";
+ // local stuff
+ let messageTextElement_ = undefined
+ let hiddenElement_ = undefined
+
fetch("/run_to", {
method: 'POST',
headers: {
@@ -105,10 +109,9 @@ function addMessage(sender, message, id, rank = 0, can_edit = false) {
// We parse it and
infos = JSON.parse(text)
console.log(infos)
- addMessage('User', infos.message, infos.id, 0, can_edit = true);
elements = addMessage(infos.sender, '', infos.response_id, 0, can_edit = true);
- messageTextElement = elements['messageTextElement'];
- hiddenElement = elements['hiddenElement'];
+ messageTextElement_ = elements['messageTextElement'];
+ hiddenElement_ = elements['hiddenElement'];
entry_counter++;
}
else {
@@ -117,8 +120,8 @@ function addMessage(sender, message, id, rank = 0, can_edit = false) {
txt = hiddenElement.innerHTML;
if (char != '\f') {
txt += char
- hiddenElement.innerHTML = txt
- messageTextElement.innerHTML = txt.replace(/\n/g, "
")
+ hiddenElement_.innerHTML = txt
+ messageTextElement_.innerHTML = txt.replace(/\n/g, "
")
}
// scroll to bottom of chat window
diff --git a/static/js/main.js b/static/js/main.js
index e7905bf2..79c8acf7 100644
--- a/static/js/main.js
+++ b/static/js/main.js
@@ -16,6 +16,7 @@ function update_main(){
const waitAnimation = document.querySelector("#wait-animation")
sendbtn.style.display="none";
waitAnimation.style.display="block";
+ console.log("Sending message to bot")
fetch('/bot', {
method: 'POST',
headers: {