Initial commit

This commit is contained in:
ParisNeo 2023-04-06 21:12:49 +02:00
parent 4ca35902ef
commit dfaf61e0ac
24 changed files with 2452 additions and 0 deletions

133
.gitignore vendored Normal file
View File

@ -0,0 +1,133 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# Database
*.db
/data

43
CODE_OF_CONDUCT.md Normal file
View File

@ -0,0 +1,43 @@
## Code of Conduct
Our chatbot is an inclusive community that values respect, collaboration, and innovation. We welcome contributors of all backgrounds and skill levels to join us in building a positive and productive community. To ensure that our community remains safe, respectful, and welcoming, we ask all contributors to abide by this code of conduct.
### 1. Respectful Communication
We value open communication and encourage contributors to express themselves in a respectful and constructive manner. We do not tolerate discrimination, harassment, or abuse of any kind, including but not limited to:
- Offensive comments related to gender, gender identity and expression, sexual orientation, disability, mental illness, race, ethnicity, age, nationality, religion, or physical appearance
- Threats, intimidation, or bullying
- Inappropriate sexual advances or imagery
### 2. Collaborative Development
We encourage collaboration and teamwork among contributors and ask that everyone work together in a constructive and positive manner. We do not tolerate disruptive behavior, including but not limited to:
- Spamming, trolling, or flaming
- Hijacking discussions or derailing conversations
- Refusal to consider alternative viewpoints or approaches
### 3. Innovative Contributions
We welcome contributions of all kinds and encourage innovation and experimentation. However, we do not tolerate the use of our chatbot for any form of misinformation, including but not limited to:
- The dissemination of false information, rumors, or hoaxes
- The promotion of conspiracy theories or fake news
- The use of our chatbot for malicious purposes, including but not limited to fraud, scams, or phishing
### 4. Consequences of Unacceptable Behavior
We take all reports of unacceptable behavior seriously and will investigate all incidents promptly and thoroughly. We reserve the right to take any action deemed necessary, including but not limited to:
- Warning the individual responsible for the unacceptable behavior
- Temporarily or permanently revoking their access to the chatbot
- Banning them from future participation in the community
### 5. Reporting Unacceptable Behavior
If you experience or witness behavior that violates this code of conduct, please report it immediately to the chatbot administrator. All reports will be kept confidential and will be investigated promptly and thoroughly.
### 6. Acknowledgment of Code of Conduct
By contributing to our chatbot, you acknowledge that you have read and agree to abide by this code of conduct. You also acknowledge that you have the responsibility to report any violations of this code of conduct.

9
Change Log.md Normal file
View File

@ -0,0 +1,9 @@
# GPT4ALL-Webui Change Log
# V 0.0.1
1 - Interaction with the bot in threaded discussion
2 - List of past discussions
3 - New Discussion
4 - Edit discussion name
5 - Remove discussion
6 - Export database as json

14
Dockerfile Normal file
View File

@ -0,0 +1,14 @@
FROM python:3.10
WORKDIR /srv
COPY ./requirements.txt .
RUN python3.10 -m venv env
RUN . env/bin/activate
RUN python3.10 -m pip install -r requirements.txt --upgrade pip
COPY ./app.py /srv/app.py
COPY ./static /srv/static
COPY ./templates /srv/templates
CMD ["python", "app.py", "--host", "0.0.0.0", "--port", "4685", "--db_path", "data/database.db"]

158
README.md Normal file
View File

@ -0,0 +1,158 @@
# Gpt4All Web UI
![GitHub license](https://img.shields.io/github/license/ParisNeo/Gpt4All-webui)
![GitHub issues](https://img.shields.io/github/issues/ParisNeo/Gpt4All-webui)
![GitHub stars](https://img.shields.io/github/stars/ParisNeo/Gpt4All-webui)
![GitHub forks](https://img.shields.io/github/forks/ParisNeo/Gpt4All-webui)
This is a Flask web application that provides a chat UI for interacting with the GPT4All chatbot.
[Discord server](https://discord.gg/DZ4wsgg4)
## What is GPT4All
GPT4All is a language model built by Nomic-AI, a company specializing in natural language processing. The app uses Nomic-AI's library to communicate with the GPT4All model, which runs locally on the user's PC. For more details about this project, head on to their [github repository](https://github.com/nomic-ai/gpt4all). You can also reald their [Technical report](https://s3.amazonaws.com/static.nomic.ai/gpt4all/2023_GPT4All_Technical_Report.pdf) for more information about the training process, the batabase etc.
The app allows users to send messages to the chatbot and view its responses in real-time. Additionally, users can export the entire chat history in text or JSON format.
The model has just been released and it may evolve over time, this webui is meant for community to get easy and fully local access to a chatbot that may become better with time.
## Disclaimer
The model used by GPT4ALL has been fine-tuned using the LORA technique on LLAMA 7B weights (for now). It is important to note that the LLAMA weights are under commercial proprietary license, and therefore, this model cannot be used for commercial purposes. We do not provide the weights ourselves, but have built a UI wrapper on top of the Nomic library, which downloads the weights automatically upon running the program.
It is important to understand that we are not responsible for any misuse of this tool. Please use it responsibly and at your own risk. While we hope that Nomic will address this issue in the future by providing clean weights that can be used freely, for now, this model is intended for testing purposes only.
## UI screenshot
![image](https://user-images.githubusercontent.com/827993/229951093-27114d9f-0e1f-4d84-b103-e35cd3f9310d.png)
**Note for Windows users:** At the moment, Nomic-AI has not provided a wheel for Windows, so you will need to use the app with the Windows Subsystem for Linux (WSL). To install WSL, follow these steps:
- Open the Windows Features settings (you can find this by searching for "Windows Features" in the Start menu).
- Enable the "Windows Subsystem for Linux" feature.
- Restart your computer when prompted.
- Install a Linux distribution from the Microsoft Store (e.g., Ubuntu).
- Open the Linux distribution and follow the prompts to create a new user account.
- We apologize for any inconvenience this may cause. We are working on a more widespread version.
## Installation
To install the app, follow these steps:
1. Clone the GitHub repository:
```
git clone https://github.com/ParisNeo/Gpt4All-webui
```
1. Navigate to the project directory:
```
cd Gpt4All-webui
```
1. Run the appropriate installation script for your platform:
On Windows with WSL:
- When Nomic add windows support you would be able to use this :
```
install.bat
```
- On linux/ Mac os
```
./install.sh
```
On Linux/MacOS, if you have issues, refer more details are presented [here](docs/Linux_Osx_Install.md)
These scripts will create a Python virtual environment and install the required dependencies.
## Usage
To run the Flask server, execute the following command:
```bash
python app.py [--port PORT] [--host HOST] [--temp TEMP] [--n-predict N_PREDICT] [--top-k TOP_K] [--top-p TOP_P] [--repeat-penalty REPEAT_PENALTY] [--repeat-last-n REPEAT_LAST_N] [--ctx-size CTX_SIZE]
```
On Linux/MacOS more details are [here](docs/Linux_Osx_Usage.md)
## Options
* `--port`: the port on which to run the server (default: 9600)
* `--host`: the host address on which to run the server (default: localhost)
* `--temp`: the sampling temperature for the model (default: 0.1)
* `--n-predict`: the number of tokens to predict at a time (default: 128)
* `--top-k`: the number of top-k candidates to consider for sampling (default: 40)
* `--top-p`: the cumulative probability threshold for top-p sampling (default: 0.90)
* `--repeat-penalty`: the penalty to apply for repeated n-grams (default: 1.3)
* `--repeat-last-n`: the number of tokens to use for detecting repeated n-grams (default: 64)
* `--ctx-size`: the maximum context size to use for generating responses (default: 2048)
Note: All options are optional, and have default values.
Once the server is running, open your web browser and navigate to http://localhost:9600 (or http://your host name:your port number if you have selected different values for those) to access the chatbot UI. To use the app, open a web browser and navigate to this URL.
Make sure to adjust the default values and descriptions of the options to match your specific application.
## Contribute
This is an open-source project by the community for the community. Our chatbot is a UI wrapper for Nomic AI's model, which enables natural language processing and machine learning capabilities.
We welcome contributions from anyone who is interested in improving our chatbot. Whether you want to report a bug, suggest a feature, or submit a pull request, we encourage you to get involved and help us make our chatbot even better.
Before contributing, please take a moment to review our [code of conduct](./CODE_OF_CONDUCT.md). We expect all contributors to abide by this code of conduct, which outlines our expectations for respectful communication, collaborative development, and innovative contributions.
### Reporting Bugs
If you find a bug or other issue with our chatbot, please report it by [opening an issue](https://github.com/your-username/your-chatbot/issues/new). Be sure to provide as much detail as possible, including steps to reproduce the issue and any relevant error messages.
### Suggesting Features
If you have an idea for a new feature or improvement to our chatbot, we encourage you to [open an issue](https://github.com/your-username/your-chatbot/issues/new) to discuss it. We welcome feedback and ideas from the community and will consider all suggestions that align with our project goals.
### Contributing Code
If you want to contribute code to our chatbot, please follow these steps:
1. Fork the repository and create a new branch for your changes.
2. Make your changes and ensure that they follow our coding conventions.
3. Test your changes to ensure that they work as expected.
4. Submit a pull request with a clear description of your changes and the problem they solve.
We will review your pull request as soon as possible and provide feedback on any necessary changes. We appreciate your contributions and look forward to working with you!
Please note that all contributions are subject to review and approval by our project maintainers. We reserve the right to reject any contribution that does not align with our project goals or standards.
## Future Plans
Here are some of the future plans for this project:
**Enhanced control of chatbot parameters:** We plan to improve the user interface (UI) of the chatbot to allow users to control the parameters of the chatbot such as temperature and other variables. This will give users more control over the chatbot's responses, and allow for a more customized experience.
**Extension system for plugins:** We are also working on an extension system that will allow developers to create plugins for the chatbot. These plugins will be able to add new features and capabilities to the chatbot, and allow for greater customization of the chatbot's behavior.
**Enhanced UI with themes and skins:** Additionally, we plan to enhance the user interface of the chatbot to allow for themes and skins. This will allow users to personalize the appearance of the chatbot, and make it more visually appealing.
We are excited about these future plans for the project and look forward to implementing them in the near future. Stay tuned for updates!
## License
This project is licensed under the Apache 2.0 License. See the [LICENSE](https://github.com/ParisNeo/Gpt4All-webui/blob/main/LICENSE) file for details.
## Special thanks
Special thanks to :
- [cclaar-byte](https://github.com/cclaar-byte)
- [CybearWarfare](https://github.com/CybearWarfare)
- [Jan Brummelte](https://github.com/brummelte)
- [higorvaz](https://github.com/higorvaz)
for their contributions.

355
app.py Normal file
View File

@ -0,0 +1,355 @@
from flask import Flask, jsonify, request, render_template, Response, stream_with_context
from nomic.gpt4all import GPT4All
import argparse
import threading
from io import StringIO
import sys
import re
import sqlite3
from datetime import datetime
import sqlite3
import json
import time
import traceback
import select
#=================================== Database ==================================================================
class Discussion:
def __init__(self, discussion_id, db_path='database.db'):
self.discussion_id = discussion_id
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', id=0):
return Discussion(id, db_path)
def add_message(self, sender, content):
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()
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 last_discussion_has_messages(db_path='database.db'):
with sqlite3.connect(db_path) as conn:
c = conn.cursor()
c.execute("SELECT * FROM message ORDER BY id DESC LIMIT 1")
last_message = c.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')
discussions = []
for row in cur.fetchall():
discussion_id = row[0]
discussion = {'id': discussion_id, 'messages': []}
cur.execute('SELECT * FROM message WHERE discussion_id=?', (discussion_id,))
for message_row in cur.fetchall():
discussion['messages'].append({'sender': message_row[1], 'content': message_row[2]})
discussions.append(discussion)
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()
# 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
)
''')
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()
print("Ok")
# ========================================================================================================================
app = Flask("GPT4All-WebUI", static_url_path='/static', static_folder='static')
class Gpt4AllWebUI():
def __init__(self, chatbot_bindings, app, db_path='database.db') -> None:
self.current_discussion = None
self.chatbot_bindings = chatbot_bindings
self.app=app
self.db_path= db_path
self.add_endpoint('/', '', self.index, methods=['GET'])
self.add_endpoint('/stream', 'stream', self.stream, 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('/get_messages', 'get_messages', self.get_messages, methods=['POST'])
self.add_endpoint('/delete_discussion', 'delete_discussion', self.delete_discussion, methods=['POST'])
self.add_endpoint('/update_message', 'update_message', self.update_message, methods=['GET'])
# Chatbot conditionning
# response = self.chatbot_bindings.prompt("This is a discussion between A user and an AI. AI responds to user questions in a helpful manner. AI is not allowed to lie or deceive. AI welcomes the user\n### Response:")
# print(response)
def add_endpoint(self, endpoint=None, endpoint_name=None, handler=None, methods=['GET'], *args, **kwargs):
self.app.add_url_rule(endpoint, endpoint_name, handler, methods=methods, *args, **kwargs)
def index(self):
return render_template('chat.html')
def format_message(self, message):
# Look for a code block within the message
pattern = re.compile(r"(```.*?```)", re.DOTALL)
match = pattern.search(message)
# If a code block is found, replace it with a <code> tag
if match:
code_block = match.group(1)
message = message.replace(code_block, f"<code>{code_block[3:-3]}</code>")
# Return the formatted message
return message
def stream(self):
def generate():
# Replace this with your text-generating code
for i in range(10):
yield f'This is line {i+1}\n'
time.sleep(1)
return Response(stream_with_context(generate()))
def export(self):
return jsonify(export_to_json(self.db_path))
@stream_with_context
def parse_to_prompt_stream(self, message, message_id):
bot_says = ['']
point = b''
bot = self.chatbot_bindings.bot
self.stop=False
# very important. This is the maximum time we wait for the model
wait_val = 15.0 # At the beginning the server may need time to send data. we wait 15s
# send the message to the bot
print(f"Received message : {message}")
bot = self.chatbot_bindings.bot
bot.stdin.write(message.encode('utf-8'))
bot.stdin.write(b"\n")
bot.stdin.flush()
# First we need to send the new message ID to the client
response_id = self.current_discussion.add_message("GPT4All",'') # first the content is empty, but we'll fill it at the end
yield(json.dumps({'type':'input_message_infos','message':message, 'id':message_id, 'response_id':response_id}))
#Now let's wait for the bot to answer
while not self.stop:
readable, _, _ = select.select([bot.stdout], [], [], wait_val)
wait_val = 4.0 # Once started, the process doesn't take that much so we reduce the wait
if bot.stdout in readable:
point += bot.stdout.read(1)
try:
character = point.decode("utf-8")
if character == "\n":
bot_says.append('\n')
yield '\n'
else:
bot_says[-1] += character
yield character
point = b''
except UnicodeDecodeError:
if len(point) > 4:
point = b''
else:
self.current_discussion.update_message(response_id,bot_says)
return "\n".join(bot_says)
def bot(self):
self.stop=True
with sqlite3.connect(self.db_path) as conn:
try:
if self.current_discussion is None or not last_discussion_has_messages(self.db_path):
self.current_discussion=Discussion.create_discussion(self.db_path)
message_id = self.current_discussion.add_message("user", request.json['message'])
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)))
except Exception as ex:
print(ex)
msg = traceback.print_exc()
return "<b style='color:red;'>Exception :<b>"+str(ex)+"<br>"+traceback.format_exc()+"<br>Please report exception"
def discussions(self):
try:
discussions = Discussion.get_discussions(self.db_path)
return jsonify(discussions)
except Exception as ex:
print(ex)
msg = traceback.print_exc()
return "<b style='color:red;'>Exception :<b>"+str(ex)+"<br>"+traceback.format_exc()+"<br>Please report exception"
def rename(self):
data = request.get_json()
id = data['id']
title = data['title']
Discussion.rename(self.db_path, id, title)
return "renamed successfully"
def get_messages(self):
data = request.get_json()
id = data['id']
self.current_discussion = Discussion(id,self.db_path)
messages = self.current_discussion.get_messages()
return jsonify(messages)
def delete_discussion(self):
data = request.get_json()
id = data['id']
self.current_discussion = Discussion(id, self.db_path)
self.current_discussion.delete_discussion()
self.current_discussion = None
return jsonify({})
def update_message(self):
try:
id = request.args.get('id')
new_message = request.args.get('message')
self.current_discussion.update_message(id, new_message)
return jsonify({"status":'ok'})
except Exception as ex:
print(ex)
msg = traceback.print_exc()
return "<b style='color:red;'>Exception :<b>"+str(ex)+"<br>"+traceback.format_exc()+"<br>Please report exception"
def new_discussion(self):
title = request.args.get('title')
self.current_discussion= Discussion.create_discussion(self.db_path, title)
# Get the current timestamp
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# add a new discussion
self.chatbot_bindings.close()
self.chatbot_bindings.open()
# Return a success response
return json.dumps({'id': self.current_discussion.discussion_id})
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Start the chatbot Flask app.')
parser.add_argument('--temp', type=float, default=0.1, help='Temperature parameter for the model.')
parser.add_argument('--n_predict', type=int, default=128, help='Number of tokens to predict at each step.')
parser.add_argument('--top_k', type=int, default=40, help='Value for the top-k sampling.')
parser.add_argument('--top_p', type=float, default=0.95, help='Value for the top-p sampling.')
parser.add_argument('--repeat_penalty', type=float, default=1.3, help='Penalty for repeated tokens.')
parser.add_argument('--repeat_last_n', type=int, default=64, help='Number of previous tokens to consider for the repeat penalty.')
parser.add_argument('--ctx_size', type=int, default=2048, help='Size of the context window for the model.')
parser.add_argument('--debug', dest='debug', action='store_true', help='launch Flask server in debug mode')
parser.add_argument('--host', type=str, default='localhost', help='the hostname to listen on')
parser.add_argument('--port', type=int, default=9600, help='the port to listen on')
parser.add_argument('--db_path', type=str, default='database.db', help='Database path')
parser.set_defaults(debug=False)
args = parser.parse_args()
chatbot_bindings = GPT4All(decoder_config = {
'temp': args.temp,
'n_predict':args.n_predict,
'top_k':args.top_k,
'top_p':args.top_p,
#'color': True,#"## Instruction",
'repeat_penalty': args.repeat_penalty,
'repeat_last_n':args.repeat_last_n,
'ctx_size': args.ctx_size
})
chatbot_bindings.open()
check_discussion_db(args.db_path)
bot = Gpt4AllWebUI(chatbot_bindings, app, args.db_path)
if args.debug:
app.run(debug=True, host=args.host, port=args.port)
else:
app.run(host=args.host, port=args.port)

12
docker-compose.yml Normal file
View File

@ -0,0 +1,12 @@
version: '3.8'
services:
webui:
build:
context: .
dockerfile: Dockerfile
volumes:
- ./data:/srv/data
- ./data/.nomic:/root/.nomic/
ports:
- "4685:4685"

55
docs/Linux_Osx_Install.md Normal file
View File

@ -0,0 +1,55 @@
# Installing GPT4All-Webui on Linux or macOS:
\- Install requirements
python3.11 -m pip install -r requirements.txt 
![](https://user-images.githubusercontent.com/9384127/230159652-120e60f3-b737-434a-ac01-15819a0e7698.png)
\- Review the install script 🙏🏻
```
nano -lASimYsh install.sh
```
![](https://user-images.githubusercontent.com/9384127/229646387-9fea98c6-fb13-496b-b8eb-9db6fe241556.png)
\- Make it runnable
```
chmod +x install.sh
```
\- Run the install script
```
./install.sh
```
![](https://user-images.githubusercontent.com/9384127/229650379-e70a54b3-a8c0-44c6-a44f-26b96dfbcf4e.png)
\- Install nomic 
```
pip install nomic
```
or force pip to install with Python 3.11
```
python3.11 -m pip install nomic
```
![](https://user-images.githubusercontent.com/9384127/229660511-ea6ef97e-712a-4e59-81d4-b4162e796728.png)
![](https://user-images.githubusercontent.com/9384127/229660570-a960cfc3-4634-4354-868f-259ba9ffe888.png)
\- Install/updt venv 
```
sudo apt install python3.11-venv
```
![](https://user-images.githubusercontent.com/9384127/229801745-3c84e89e-c62c-460d-9e79-dafe5aa518d5.png)
\- ToDo

26
docs/Linux_Osx_Usage.md Normal file
View File

@ -0,0 +1,26 @@
# Using GPT4All-Webui on Linux or macOS:
To run the Flask server, execute the following command:
```bash
python app.py [--port PORT] [--host HOST] [--temp TEMP] [--n-predict N_PREDICT] [--top-k TOP_K] [--top-p TOP_P] [--repeat-penalty REPEAT_PENALTY] [--repeat-last-n REPEAT_LAST_N] [--ctx-size CTX_SIZE]
```
On Kali Linux it runned well but Ubuntu requires some upgrades:
 - python3.11 -m pip install numpy --upgrade
![](https://user-images.githubusercontent.com/9384127/229806717-1b260484-723f-4780-b69b-d19c7375a84e.png)
![](https://user-images.githubusercontent.com/9384127/229807131-623e9017-1536-473c-9e54-58d64f007991.png)
![](https://user-images.githubusercontent.com/9384127/229809099-3ef4d87f-18ce-4873-b43b-e6f9d7accb50.png)
![Magic Memes](https://www.memesmonkey.com/images/memesmonkey/77/771330e9f7a2a22e7b412187a657045c.jpeg)
😅
?Root?
![](https://user-images.githubusercontent.com/9384127/230199605-ab29926d-07dc-4d4d-9fd9-c51f9e117dfb.jpeg)

63
install.3.10.sh Normal file
View File

@ -0,0 +1,63 @@
#!/usr/bin/bash
# Install Python 3.10 and pip
echo -n "Checking for python3.10..."
if command -v python3.10 > /dev/null 2>&1; then
echo "OK"
else
read -p "Python3.10 is not installed. Would you like to install Python3.10? [Y/N] " choice
if [ "$choice" = "Y" ] || [ "$choice" = "y" ]; then
echo "Installing Python3.10..."
sudo apt update
sudo apt install -y python3.10 python3.10-venv
else
echo "Please install Python3.10 and try again."
exit 1
fi
fi
# Install venv module
echo -n "Checking for venv module..."
if python3.10 -m venv env > /dev/null 2>&1; then
echo "OK"
else
read -p "venv module is not available. Would you like to install it? [Y/N] " choice
if [ "$choice" = "Y" ] || [ "$choice" = "y" ]; then
echo "Installing venv module..."
sudo apt update
sudo apt install -y python3.10-venv
else
echo "Please install venv module and try again."
exit 1
fi
fi
# Create a new virtual environment
echo -n "Creating virtual environment..."
python3.10 -m venv env
if [ $? -ne 0 ]; then
echo "Failed to create virtual environment. Please check your Python installation and try again."
exit 1
else
echo "OK"
fi
# Activate the virtual environment
echo -n "Activating virtual environment..."
source env/bin/activate
echo "OK"
# Install the required packages
echo "Installing requirements..."
export DS_BUILD_OPS=0
export DS_BUILD_AIO=0
python3.10 -m pip install pip --upgrade
python3.10 -m pip install -r requirements.txt
if [ $? -ne 0 ]; then
echo "Failed to install required packages. Please check your internet connection and try again."
exit 1
fi
echo "Virtual environment created and packages installed successfully."
exit 0

96
install.bat Normal file
View File

@ -0,0 +1,96 @@
@echo off
REM Check if Python is installed
set /p="Checking for python..." <nul
where python >nul 2>&1
if %ERRORLEVEL% neq 0 (
echo Python is not installed. Would you like to install Python? [Y/N]
set /p choice=
if /i "%choice%" equ "Y" (
REM Download Python installer
echo Downloading Python installer...
powershell -Command "Invoke-WebRequest -Uri 'https://www.python.org/ftp/python/3.10.0/python-3.10.0-amd64.exe' -OutFile 'python.exe'"
REM Install Python
echo Installing Python...
python.exe /quiet /norestart
) else (
echo Please install Python and try again.
pause
exit /b 1
)
) else (
echo OK
)
REM Check if pip is installed
set /p="Checking for pip..." <nul
python -m pip >nul 2>&1
if %ERRORLEVEL% neq 0 (
echo Pip is not installed. Would you like to install pip? [Y/N]
set /p choice=
if /i "%choice%" equ "Y" (
REM Download get-pip.py
echo Downloading get-pip.py...
powershell -Command "Invoke-WebRequest -Uri 'https://bootstrap.pypa.io/get-pip.py' -OutFile 'get-pip.py'"
REM Install pip
echo Installing pip...
python get-pip.py
) else (
echo Please install pip and try again.
pause
exit /b 1
)
) else (
echo OK
)
REM Check if venv module is available
set /p="Checking for venv..." <nul
python -c "import venv" >nul 2>&1
if %ERRORLEVEL% neq 0 (
echo venv module is not available. Would you like to upgrade Python to the latest version? [Y/N]
set /p choice=
if /i "%choice%" equ "Y" (
REM Upgrade Python
echo Upgrading Python...
python -m pip install --upgrade pip setuptools wheel
python -m pip install --upgrade --user python
) else (
echo Please upgrade your Python installation and try again.
pause
exit /b 1
)
) else (
echo OK
)
REM Create a new virtual environment
set /p="Creating virtual environment ..." <nul
python -m venv env
if %ERRORLEVEL% neq 0 (
echo Failed to create virtual environment. Please check your Python installation and try again.
pause
exit /b 1
) else (
echo OK
)
REM Activate the virtual environment
set /p="Activating virtual environment ..." <nul
call env\Scripts\activate.bat
echo OK
REM Install the required packages
echo Installing requirements ...
set DS_BUILD_OPS=0
set DS_BUILD_AIO=0
python -m pip install pip --upgrade
python -m pip install -r requirements.txt
if %ERRORLEVEL% neq 0 (
echo Failed to install required packages. Please check your internet connection and try again.
pause
exit /b 1
)
echo Virtual environment created and packages installed successfully.
pause
exit /b 0

63
install.sh Normal file
View File

@ -0,0 +1,63 @@
#!/usr/bin/bash
# Install Python 3.11 and pip
echo -n "Checking for python3.11..."
if command -v python3.11 > /dev/null 2>&1; then
echo "OK"
else
read -p "Python3.11 is not installed. Would you like to install Python3.11? [Y/N] " choice
if [ "$choice" = "Y" ] || [ "$choice" = "y" ]; then
echo "Installing Python3.11..."
sudo apt update
sudo apt install -y python3.11 python3.11-venv
else
echo "Please install Python3.11 and try again."
exit 1
fi
fi
# Install venv module
echo -n "Checking for venv module..."
if python3.11 -m venv env > /dev/null 2>&1; then
echo "OK"
else
read -p "venv module is not available. Would you like to install it? [Y/N] " choice
if [ "$choice" = "Y" ] || [ "$choice" = "y" ]; then
echo "Installing venv module..."
sudo apt update
sudo apt install -y python3.11-venv
else
echo "Please install venv module and try again."
exit 1
fi
fi
# Create a new virtual environment
echo -n "Creating virtual environment..."
python3.11 -m venv env
if [ $? -ne 0 ]; then
echo "Failed to create virtual environment. Please check your Python installation and try again."
exit 1
else
echo "OK"
fi
# Activate the virtual environment
echo -n "Activating virtual environment..."
source env/bin/activate
echo "OK"
# Install the required packages
echo "Installing requirements..."
export DS_BUILD_OPS=0
export DS_BUILD_AIO=0
python3.11 -m pip install pip --upgrade
python3.11 -m pip install -r requirements.txt
if [ $? -ne 0 ]; then
echo "Failed to install required packages. Please check your internet connection and try again."
exit 1
fi
echo "Virtual environment created and packages installed successfully."
exit 0

4
requirements.txt Normal file
View File

@ -0,0 +1,4 @@
flask
nomic
pytest
pyllamacpp

788
static/css/chat.css Normal file
View File

@ -0,0 +1,788 @@
/*
! tailwindcss v3.1.4 | MIT License | https://tailwindcss.com
*//*
1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
*/
*,
::before,
::after {
box-sizing: border-box; /* 1 */
border-width: 0; /* 2 */
border-style: solid; /* 2 */
border-color: #e5e7eb; /* 2 */
}
::before,
::after {
--tw-content: '';
}
/*
1. Use a consistent sensible line-height in all browsers.
2. Prevent adjustments of font size after orientation changes in iOS.
3. Use a more readable tab size.
4. Use the user's configured `sans` font-family by default.
*/
html {
line-height: 1.5; /* 1 */
-webkit-text-size-adjust: 100%; /* 2 */
-moz-tab-size: 4; /* 3 */
-o-tab-size: 4;
tab-size: 4; /* 3 */
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; /* 4 */
}
/*
1. Remove the margin in all browsers.
2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
*/
body {
margin: 0; /* 1 */
line-height: inherit; /* 2 */
}
/*
1. Add the correct height in Firefox.
2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
3. Ensure horizontal rules are visible by default.
*/
hr {
height: 0; /* 1 */
color: inherit; /* 2 */
border-top-width: 1px; /* 3 */
}
/*
Add the correct text decoration in Chrome, Edge, and Safari.
*/
abbr:where([title]) {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
}
/*
Remove the default font size and weight for headings.
*/
h1,
h2,
h3,
h4,
h5,
h6 {
font-size: inherit;
font-weight: inherit;
}
/*
Reset links to optimize for opt-in styling instead of opt-out.
*/
a {
color: inherit;
text-decoration: inherit;
}
/*
Add the correct font weight in Edge and Safari.
*/
b,
strong {
font-weight: bolder;
}
/*
1. Use the user's configured `mono` font family by default.
2. Correct the odd `em` font sizing in all browsers.
*/
code,
kbd,
samp,
pre {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; /* 1 */
font-size: 1em; /* 2 */
}
/*
Add the correct font size in all browsers.
*/
small {
font-size: 80%;
}
/*
Prevent `sub` and `sup` elements from affecting the line height in all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
/*
1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
3. Remove gaps between table borders by default.
*/
table {
text-indent: 0; /* 1 */
border-color: inherit; /* 2 */
border-collapse: collapse; /* 3 */
}
/*
1. Change the font styles in all browsers.
2. Remove the margin in Firefox and Safari.
3. Remove default padding in all browsers.
*/
button,
input,
optgroup,
select,
textarea {
font-family: inherit; /* 1 */
font-size: 100%; /* 1 */
font-weight: inherit; /* 1 */
line-height: inherit; /* 1 */
color: inherit; /* 1 */
margin: 0; /* 2 */
padding: 0; /* 3 */
}
/*
Remove the inheritance of text transform in Edge and Firefox.
*/
button,
select {
text-transform: none;
}
/*
1. Correct the inability to style clickable types in iOS and Safari.
2. Remove default button styles.
*/
button,
[type='button'],
[type='reset'],
[type='submit'] {
-webkit-appearance: button; /* 1 */
background-color: transparent; /* 2 */
background-image: none; /* 2 */
}
/*
Use the modern Firefox focus style for all focusable elements.
*/
:-moz-focusring {
outline: auto;
}
/*
Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
*/
:-moz-ui-invalid {
box-shadow: none;
}
/*
Add the correct vertical alignment in Chrome and Firefox.
*/
progress {
vertical-align: baseline;
}
/*
Correct the cursor style of increment and decrement buttons in Safari.
*/
::-webkit-inner-spin-button,
::-webkit-outer-spin-button {
height: auto;
}
/*
1. Correct the odd appearance in Chrome and Safari.
2. Correct the outline style in Safari.
*/
[type='search'] {
-webkit-appearance: textfield; /* 1 */
outline-offset: -2px; /* 2 */
}
/*
Remove the inner padding in Chrome and Safari on macOS.
*/
::-webkit-search-decoration {
-webkit-appearance: none;
}
/*
1. Correct the inability to style clickable types in iOS and Safari.
2. Change font properties to `inherit` in Safari.
*/
::-webkit-file-upload-button {
-webkit-appearance: button; /* 1 */
font: inherit; /* 2 */
}
/*
Add the correct display in Chrome and Safari.
*/
summary {
display: list-item;
}
/*
Removes the default spacing and border for appropriate elements.
*/
blockquote,
dl,
dd,
h1,
h2,
h3,
h4,
h5,
h6,
hr,
figure,
p,
pre {
margin: 0;
}
fieldset {
margin: 0;
padding: 0;
}
legend {
padding: 0;
}
ol,
ul,
menu {
list-style: none;
margin: 0;
padding: 0;
}
/*
Prevent resizing textareas horizontally by default.
*/
textarea {
resize: vertical;
}
/*
1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
2. Set the default placeholder color to the user's configured gray 400 color.
*/
input::-moz-placeholder, textarea::-moz-placeholder {
opacity: 1; /* 1 */
color: #9ca3af; /* 2 */
}
input:-ms-input-placeholder, textarea:-ms-input-placeholder {
opacity: 1; /* 1 */
color: #9ca3af; /* 2 */
}
input::placeholder,
textarea::placeholder {
opacity: 1; /* 1 */
color: #9ca3af; /* 2 */
}
/*
Set the default cursor for buttons.
*/
button,
[role="button"] {
cursor: pointer;
}
/*
Make sure disabled buttons don't get the pointer cursor.
*/
:disabled {
cursor: default;
}
/*
1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
This can trigger a poorly considered lint error in some tools but is included by design.
*/
img,
svg,
video,
canvas,
audio,
iframe,
embed,
object {
display: block; /* 1 */
vertical-align: middle; /* 2 */
}
/*
Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
*/
img,
video {
max-width: 100%;
height: auto;
}
*, ::before, ::after {
--tw-border-spacing-x: 0;
--tw-border-spacing-y: 0;
--tw-translate-x: 0;
--tw-translate-y: 0;
--tw-rotate: 0;
--tw-skew-x: 0;
--tw-skew-y: 0;
--tw-scale-x: 1;
--tw-scale-y: 1;
--tw-pan-x: ;
--tw-pan-y: ;
--tw-pinch-zoom: ;
--tw-scroll-snap-strictness: proximity;
--tw-ordinal: ;
--tw-slashed-zero: ;
--tw-numeric-figure: ;
--tw-numeric-spacing: ;
--tw-numeric-fraction: ;
--tw-ring-inset: ;
--tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff;
--tw-ring-color: rgb(59 130 246 / 0.5);
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
--tw-shadow: 0 0 #0000;
--tw-shadow-colored: 0 0 #0000;
--tw-blur: ;
--tw-brightness: ;
--tw-contrast: ;
--tw-grayscale: ;
--tw-hue-rotate: ;
--tw-invert: ;
--tw-saturate: ;
--tw-sepia: ;
--tw-drop-shadow: ;
--tw-backdrop-blur: ;
--tw-backdrop-brightness: ;
--tw-backdrop-contrast: ;
--tw-backdrop-grayscale: ;
--tw-backdrop-hue-rotate: ;
--tw-backdrop-invert: ;
--tw-backdrop-opacity: ;
--tw-backdrop-saturate: ;
--tw-backdrop-sepia: ;
}
::-webkit-backdrop {
--tw-border-spacing-x: 0;
--tw-border-spacing-y: 0;
--tw-translate-x: 0;
--tw-translate-y: 0;
--tw-rotate: 0;
--tw-skew-x: 0;
--tw-skew-y: 0;
--tw-scale-x: 1;
--tw-scale-y: 1;
--tw-pan-x: ;
--tw-pan-y: ;
--tw-pinch-zoom: ;
--tw-scroll-snap-strictness: proximity;
--tw-ordinal: ;
--tw-slashed-zero: ;
--tw-numeric-figure: ;
--tw-numeric-spacing: ;
--tw-numeric-fraction: ;
--tw-ring-inset: ;
--tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff;
--tw-ring-color: rgb(59 130 246 / 0.5);
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
--tw-shadow: 0 0 #0000;
--tw-shadow-colored: 0 0 #0000;
--tw-blur: ;
--tw-brightness: ;
--tw-contrast: ;
--tw-grayscale: ;
--tw-hue-rotate: ;
--tw-invert: ;
--tw-saturate: ;
--tw-sepia: ;
--tw-drop-shadow: ;
--tw-backdrop-blur: ;
--tw-backdrop-brightness: ;
--tw-backdrop-contrast: ;
--tw-backdrop-grayscale: ;
--tw-backdrop-hue-rotate: ;
--tw-backdrop-invert: ;
--tw-backdrop-opacity: ;
--tw-backdrop-saturate: ;
--tw-backdrop-sepia: ;
}
::backdrop {
--tw-border-spacing-x: 0;
--tw-border-spacing-y: 0;
--tw-translate-x: 0;
--tw-translate-y: 0;
--tw-rotate: 0;
--tw-skew-x: 0;
--tw-skew-y: 0;
--tw-scale-x: 1;
--tw-scale-y: 1;
--tw-pan-x: ;
--tw-pan-y: ;
--tw-pinch-zoom: ;
--tw-scroll-snap-strictness: proximity;
--tw-ordinal: ;
--tw-slashed-zero: ;
--tw-numeric-figure: ;
--tw-numeric-spacing: ;
--tw-numeric-fraction: ;
--tw-ring-inset: ;
--tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff;
--tw-ring-color: rgb(59 130 246 / 0.5);
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
--tw-shadow: 0 0 #0000;
--tw-shadow-colored: 0 0 #0000;
--tw-blur: ;
--tw-brightness: ;
--tw-contrast: ;
--tw-grayscale: ;
--tw-hue-rotate: ;
--tw-invert: ;
--tw-saturate: ;
--tw-sepia: ;
--tw-drop-shadow: ;
--tw-backdrop-blur: ;
--tw-backdrop-brightness: ;
--tw-backdrop-contrast: ;
--tw-backdrop-grayscale: ;
--tw-backdrop-hue-rotate: ;
--tw-backdrop-invert: ;
--tw-backdrop-opacity: ;
--tw-backdrop-saturate: ;
--tw-backdrop-sepia: ;
}
.mx-6 {
margin-left: 1.5rem;
margin-right: 1.5rem;
}
.my-4 {
margin-top: 1rem;
margin-bottom: 1rem;
}
.my-1 {
margin-top: 0.25rem;
margin-bottom: 0.25rem;
}
.mx-1 {
margin-left: 0.25rem;
margin-right: 0.25rem;
}
.flex {
display: flex;
}
.h-screen {
height: 100vh;
}
.h-20 {
height: 5rem;
}
.h-12 {
height: 3rem;
}
.h-full {
height: 100%;
}
.max-h-full {
max-height: 100%;
}
.w-screen {
width: 100vw;
}
.w-full {
width: 100%;
}
.w-12 {
width: 3rem;
}
.flex-col {
flex-direction: column;
}
.items-center {
align-items: center;
}
.justify-between {
justify-content: space-between;
}
.space-y-0 > :not([hidden]) ~ :not([hidden]) {
--tw-space-y-reverse: 0;
margin-top: calc(0px * calc(1 - var(--tw-space-y-reverse)));
margin-bottom: calc(0px * var(--tw-space-y-reverse));
}
.space-x-2 > :not([hidden]) ~ :not([hidden]) {
--tw-space-x-reverse: 0;
margin-right: calc(0.5rem * var(--tw-space-x-reverse));
margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse)));
}
.overflow-hidden {
overflow: hidden;
}
.overflow-y-auto {
overflow-y: auto;
}
.rounded-md {
border-radius: 0.375rem;
}
.border-b {
border-bottom-width: 1px;
}
.border-t {
border-top-width: 1px;
}
.border-accent {
border-color: var(--accent);
}
.bg-primary {
background-color: var(--primary);
}
.bg-tertiary {
background-color: var(--tertiary);
}
.bg-secondary {
background-color: var(--secondary);
}
.bg-accent {
background-color: var(--accent);
}
.p-4 {
padding: 1rem;
}
.p-2 {
padding: 0.5rem;
}
.px-6 {
padding-left: 1.5rem;
padding-right: 1.5rem;
}
.px-4 {
padding-left: 1rem;
padding-right: 1rem;
}
.text-2xl {
font-size: 1.5rem;
line-height: 2rem;
}
.text-sm {
font-size: 0.875rem;
line-height: 1.25rem;
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
.font-medium {
font-weight: 500;
}
.text-gray-400 {
--tw-text-opacity: 1;
color: rgb(156 163 175 / var(--tw-text-opacity));
}
.text-black {
--tw-text-opacity: 1;
color: rgb(0 0 0 / var(--tw-text-opacity));
}
.underline {
-webkit-text-decoration-line: underline;
text-decoration-line: underline;
}
.outline-none {
outline: 2px solid transparent;
outline-offset: 2px;
}
.drop-shadow-sm {
--tw-drop-shadow: drop-shadow(0 1px 1px rgb(0 0 0 / 0.05));
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
}
.transition-colors {
transition-property: color, background-color, border-color, fill, stroke, -webkit-text-decoration-color;
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, -webkit-text-decoration-color;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}
.ease-in-out {
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}
:root {
--primary: #1a1d21;
--secondary: #212529;
--teriary: #23282c;
--accent: #6691e7;
}
.hover\:bg-\[\#7ba0ea\]:hover {
--tw-bg-opacity: 1;
background-color: rgb(123 160 234 / var(--tw-bg-opacity));
}
.active\:bg-\[\#3d73e1\]:active {
--tw-bg-opacity: 1;
background-color: rgb(61 115 225 / var(--tw-bg-opacity));
}
@media (min-width: 640px) {
.sm\:space-x-reverse > :not([hidden]) ~ :not([hidden]) {
--tw-space-x-reverse: 1;
}
.sm\:border-b {
border-bottom-width: 1px;
}
}
@media (min-width: 768px) {
.md\:h-1\/2 {
height: 50%;
}
.md\:w-1\/3 {
width: 33.333333%;
}
.md\:flex-row {
flex-direction: row;
}
.md\:flex-col {
flex-direction: column;
}
.md\:border-r {
border-right-width: 1px;
}
.md\:border-b-0 {
border-bottom-width: 0px;
}
.md\:border-b {
border-bottom-width: 1px;
}
}
@media (min-width: 1024px) {
.lg\:w-1\/4 {
width: 25%;
}
}
@media (min-width: 1280px) {
.xl\:w-1\/5 {
width: 20%;
}
}
.collapsible-header {
cursor: pointer;
}
.collapsible-content {
display: none;
}
/* Wait animation */
.lds-facebook {
display: inline-block;
position: relative;
width: 40px;
height: 40px;
}
.lds-facebook div {
display: inline-block;
position: absolute;
left: 8px;
width: 8px;
background: #fff;
animation: lds-facebook 1.2s cubic-bezier(0, 0.5, 0.5, 1) infinite;
}
.lds-facebook div:nth-child(1) {
left: 4px;
animation-delay: -0.24s;
}
.lds-facebook div:nth-child(2) {
left: 16px;
animation-delay: -0.12s;
}
.lds-facebook div:nth-child(3) {
left: 28px;
animation-delay: 0;
}
@keyframes lds-facebook {
0% {
top: 8px;
height: 34px;
}
50%, 100% {
top: 12px;
height: 18px;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
static/images/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!-- Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path d="M402.6 83.2l90.2 90.2c3.8 3.8 3.8 10 0 13.8L274.4 405.6l-92.8 10.3c-12.4 1.4-22.9-9.1-21.5-21.5l10.3-92.8L388.8 83.2c3.8-3.8 10-3.8 13.8 0zm162-22.9l-48.8-48.8c-15.2-15.2-39.9-15.2-55.2 0l-35.4 35.4c-3.8 3.8-3.8 10 0 13.8l90.2 90.2c3.8 3.8 10 3.8 13.8 0l35.4-35.4c15.2-15.3 15.2-40 0-55.2zM384 346.2V448H64V128h229.8c3.2 0 6.2-1.3 8.5-3.5l40-40c7.6-7.6 2.2-20.5-8.5-20.5H48C21.5 64 0 85.5 0 112v352c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48V306.2c0-10.7-12.9-16-20.5-8.5l-40 40c-2.2 2.3-3.5 5.3-3.5 8.5z"/></svg>

After

Width:  |  Height:  |  Size: 724 B

541
static/js/chat.js Normal file
View File

@ -0,0 +1,541 @@
const chatWindow = document.getElementById('chat-window');
const chatForm = document.getElementById('chat-form');
const userInput = document.getElementById('user-input');
chatForm.addEventListener('submit', event => {
event.preventDefault();
// get user input and clear input field
message = userInput.value;
userInput.value = '';
// add user message to chat window
const sendbtn = document.querySelector("#submit-input")
const waitAnimation = document.querySelector("#wait-animation")
sendbtn.style.display="none";
waitAnimation.style.display="block";
fetch('/bot', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ message })
}).then(function(response) {
const stream = new ReadableStream({
start(controller) {
const reader = response.body.getReader();
function push() {
reader.read().then(function(result) {
if (result.done) {
sendbtn.style.display="block";
waitAnimation.style.display="none";
controller.close();
return;
}
controller.enqueue(result.value);
push();
})
}
push();
}
});
const textDecoder = new TextDecoder();
const readableStreamDefaultReader = stream.getReader();
let entry_counter = 0
function readStream() {
readableStreamDefaultReader.read().then(function(result) {
if (result.done) {
return;
}
text = textDecoder.decode(result.value);
// The server will first send a json containing information about the message just sent
if(entry_counter==0)
{
// We parse it and
infos = JSON.parse(text)
addMessage('User', infos.message, infos.id, true);
elements = addMessage('GPT4ALL', '', infos.response_id, true);
messageTextElement=elements['messageTextElement'];
hiddenElement=elements['hiddenElement'];
entry_counter ++;
}
else{
// For the other enrtries, these are just the text of the chatbot
for (const char of text) {
txt = hiddenElement.innerHTML;
if (char != '\f') {
txt += char
hiddenElement.innerHTML = txt
messageTextElement.innerHTML = txt.replace(/\n/g, "<br>")
}
// scroll to bottom of chat window
chatWindow.scrollTop = chatWindow.scrollHeight;
}
entry_counter ++;
}
readStream();
});
}
readStream();
});
});
function addMessage(sender, message, id, can_edit=false) {
console.log(id)
const messageElement = document.createElement('div');
messageElement.classList.add('bg-secondary', 'drop-shadow-sm', 'p-4', 'mx-6', 'my-4', 'flex', 'flex-col', 'space-x-2');
messageElement.classList.add(sender);
messageElement.setAttribute('id', id);
const senderElement = document.createElement('div');
senderElement.classList.add('font-normal', 'underline', 'text-sm');
senderElement.innerHTML = sender;
const messageTextElement = document.createElement('div');
messageTextElement.classList.add('font-medium', 'text-md');
messageTextElement.innerHTML = message;
// Create a hidden div element needed to buffer responses before commiting them to the visible message
const hiddenElement = document.createElement('div');
hiddenElement.style.display = 'none';
hiddenElement.innerHTML = '';
messageElement.appendChild(senderElement);
messageElement.appendChild(messageTextElement);
if(can_edit)
{
const editButton = document.createElement('button');
editButton.classList.add('bg-blue-500', 'hover:bg-blue-700', 'text-white', 'font-bold', 'py-2', 'px-4', 'rounded', 'my-2');
editButton.innerHTML = 'Edit';
editButton.addEventListener('click', () => {
const inputField = document.createElement('input');
inputField.type = 'text';
inputField.classList.add('font-medium', 'text-md', 'border', 'border-gray-300', 'p-1');
inputField.value = messageTextElement.innerHTML;
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');
saveButton.innerHTML = 'Save';
saveButton.addEventListener('click', () => {
const newText = inputField.value;
messageTextElement.innerHTML = newText;
// make request to update message
const url = `/update_message?id=${id}&message=${newText}`;
fetch(url)
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
else{
console.log("Updated")
}
})
.catch(error => {
console.error('There was a problem updating the message:', error);
});
messageElement.replaceChild(messageTextElement, inputField);
//messageElement.removeChild(inputField);
messageElement.removeChild(saveButton);
});
messageElement.replaceChild(inputField, messageTextElement);
messageElement.appendChild(saveButton);
inputField.focus();
});
messageElement.appendChild(editButton);
}
chatWindow.appendChild(messageElement);
chatWindow.appendChild(hiddenElement);
// scroll to bottom of chat window
chatWindow.scrollTop = chatWindow.scrollHeight;
// Return all needed stuff
return {'messageTextElement':messageTextElement, 'hiddenElement':hiddenElement}
}
const exportButton = document.getElementById('export-button');
exportButton.addEventListener('click', () => {
const messages = Array.from(chatWindow.querySelectorAll('.message')).map(messageElement => {
const senderElement = messageElement.querySelector('.sender');
const messageTextElement= messageElement.querySelector('.message-text');
const sender = senderElement.textContent;
const messageText = messageTextElement.textContent;
return { sender, messageText };
});
const exportFormat = 'json'; // replace with desired export format
if (exportFormat === 'text') {
const exportText = messages.map(({ sender, messageText }) => `${sender}: ${messageText}`).join('\n');
downloadTextFile(exportText);
} else if (exportFormat === 'json') {
fetch('/export')
.then(response => response.json())
.then(data => {
db_data = JSON.stringify(data)
// Do something with the data, such as displaying it on the page
console.log(db_data);
downloadJsonFile(db_data);
})
.catch(error => {
// Handle any errors that occur
console.error(error);
});
} else {
console.error(`Unsupported export format: ${exportFormat}`);
}
});
function downloadTextFile(text) {
const blob = new Blob([text], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
downloadUrl(url);
}
function downloadJsonFile(json) {
const blob = new Blob([json], { type: 'application/json' });
const url = URL.createObjectURL(blob);
downloadUrl(url);
}
function downloadUrl(url) {
const link = document.createElement('a');
link.href = url;
link.download = 'chat.txt';
link.click();
}
const newDiscussionBtn = document.querySelector('#new-discussion-btn');
newDiscussionBtn.addEventListener('click', () => {
const discussionName = prompt('Enter a name for the new discussion:');
if (discussionName) {
const sendbtn = document.querySelector("#submit-input")
const waitAnimation = document.querySelector("#wait-animation")
sendbtn.style.display="none";
waitAnimation.style.display="block";
// Add the discussion to the discussion list
const discussionItem = document.createElement('li');
discussionItem.textContent = discussionName;
fetch(`/new_discussion?title=${discussionName}`)
.then(response => response.json())
.then(data => {
console.log(`New chat ${data}`)
// Select the new discussion
//selectDiscussion(discussionId);
chatWindow.innerHTML=""
addMessage("GPT4ALL", welcome_message,0);
populate_discussions_list()
sendbtn.style.display="block";
waitAnimation.style.display="none";
})
.catch(error => {
// Handle any errors that occur
console.error(error);
});
}
});
function populate_discussions_list()
{
// Populate discussions list
const discussionsList = document.querySelector('#discussions-list');
discussionsList.innerHTML = "";
fetch('/discussions')
.then(response => response.json())
.then(discussions => {
discussions.forEach(discussion => {
const buttonWrapper = document.createElement('div');
//buttonWrapper.classList.add('flex', 'space-x-2', 'mt-2');
buttonWrapper.classList.add('flex', 'items-center', 'mt-2', 'py-4', 'text-left');
const renameButton = document.createElement('button');
renameButton.classList.add('bg-green-500', 'hover:bg-green-700', 'text-white', 'font-bold', 'py-0', 'px-0', 'rounded', 'mr-2');
const renameImg = document.createElement('img');
renameImg.src = "/static/images/edit_discussion.png";
renameImg.style.width='20px'
renameImg.style.height='20px'
renameButton.appendChild(renameImg);
//renameButton.style.backgroundImage = "/rename_discussion.svg"; //.textContent = 'Rename';
renameButton.addEventListener('click', () => {
const dialog = document.createElement('dialog');
dialog.classList.add('bg-white', 'rounded', 'p-4');
const inputLabel = document.createElement('label');
inputLabel.textContent = 'New name: ';
const inputField = document.createElement('input');
inputField.classList.add('border', 'border-gray-400', 'rounded', 'py-1', 'px-2');
inputField.setAttribute('type', 'text');
inputField.setAttribute('name', 'title');
inputField.setAttribute('value', discussion.title);
inputLabel.appendChild(inputField);
dialog.appendChild(inputLabel);
const cancelButton = document.createElement('button');
cancelButton.textContent = 'Cancel';
cancelButton.addEventListener('click', () => {
dialog.close();
});
const renameConfirmButton = document.createElement('button');
renameConfirmButton.classList.add('bg-green-500', 'hover:bg-green-700', 'text-white', 'font-bold', 'py-2', 'px-4', 'rounded', 'ml-2');
renameConfirmButton.textContent = 'Rename';
renameConfirmButton.addEventListener('click', () => {
const newTitle = inputField.value;
if (newTitle === '') {
alert('New name cannot be empty');
} else {
fetch('/rename', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ id: discussion.id, title: newTitle })
})
.then(response => {
if (response.ok) {
discussion.title = newTitle;
discussionButton.textContent = newTitle;
dialog.close();
} else {
alert('Failed to rename discussion');
}
})
.catch(error => {
console.error('Failed to rename discussion:', error);
alert('Failed to rename discussion');
});
}
});
dialog.appendChild(cancelButton);
dialog.appendChild(renameConfirmButton);
document.body.appendChild(dialog);
dialog.showModal();
});
const deleteButton = document.createElement('button');
deleteButton.classList.add('bg-green-500', 'hover:bg-green-700', 'text-white', 'font-bold', 'py-0', 'px-0', 'rounded', 'ml-2');
const deleteImg = document.createElement('img');
deleteImg.src = "/static/images/delete_discussion.png";
deleteImg.style.width='20px'
deleteImg.style.height='20px'
deleteButton.addEventListener('click', () => {
fetch('/delete_discussion', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ id: discussion.id})
})
.then(response => {
if (response.ok) {
buttonWrapper.remove();
} else {
alert('Failed to delete discussion');
}
})
.catch(error => {
console.error('Failed to delete discussion:', error);
alert('Failed to delete discussion');
});
});
deleteButton.appendChild(deleteImg);
deleteButton.addEventListener('click', () => {
});
const discussionButton = document.createElement('button');
discussionButton.classList.add('flex-grow', 'w-full', 'bg-blue-500', 'hover:bg-blue-700', 'text-white', 'font-bold', 'py-2', 'px-4', 'rounded', 'text-left', 'hover:text-white');
discussionButton.textContent = discussion.title;
discussionButton.addEventListener('click', () => {
// send query with discussion id to reveal discussion messages
fetch('/get_messages', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ id: discussion.id })
})
.then(response => {
if (response.ok) {
response.text().then(data => {
const messages = JSON.parse(data);
console.log(messages)
// process messages
var container = document.getElementById('chat-window');
container.innerHTML = '';
messages.forEach(message => {
addMessage(message.sender, message.content, message.id, true);
});
});
} else {
alert('Failed to query the discussion');
}
})
.catch(error => {
console.error('Failed to get messages:', error);
alert('Failed to get messages');
});
console.log(`Showing messages for discussion ${discussion.id}`);
});
buttonWrapper.appendChild(renameButton);
buttonWrapper.appendChild(deleteButton);
buttonWrapper.appendChild(discussionButton);
discussionsList.appendChild(buttonWrapper);
});
})
.catch(error => {
console.error('Failed to get discussions:', error);
alert('Failed to get discussions');
});
}
// First time we populate the discussions list
populate_discussions_list()
function add_collapsible_div(discussion_title, text, id) {
// Create the outer box element
const box = document.createElement('div');
box.classList.add('bg-gray-100', 'rounded-lg', 'p-4');
// Create the title element
const title = document.createElement('h2');
title.classList.add('text-lg', 'font-medium');
title.textContent = discussion_title;
// Create the toggle button element
const toggleBtn = document.createElement('button');
toggleBtn.classList.add('focus:outline-none');
toggleBtn.id = `${id}-toggle-btn`;
// Create the expand icon element
const expandIcon = document.createElement('path');
expandIcon.id = `${id}-expand-icon`;
expandIcon.setAttribute('d', 'M5 5h10v10H5z');
// Create the collapse icon element
const collapseIcon = document.createElement('path');
collapseIcon.id = `${id}-collapse-icon`;
collapseIcon.setAttribute('d', 'M7 10h6');
// Add the icons to the toggle button element
toggleBtn.appendChild(expandIcon);
toggleBtn.appendChild(collapseIcon);
// Create the content element
const content = document.createElement('div');
content.id = `${id}-box-content`;
content.classList.add('mt-4');
content.textContent = text;
// Add the title, toggle button, and content to the box element
// Create the title and toggle button container element
const titleToggleContainer = document.createElement('div');
titleToggleContainer.classList.add('flex', 'justify-between', 'items-center');
// Add the title and toggle button to the container element
titleToggleContainer.appendChild(title);
titleToggleContainer.appendChild(toggleBtn);
// Add the container element to the box element
box.appendChild(titleToggleContainer);
box.appendChild(content);
// Add the box to the document
document.body.appendChild(box);
// Add the CSS styles to the head of the document
const css = `
#${id}-box-content {
max-height: 0;
overflow: hidden;
transition: max-height 0.2s ease-out;
}
#${id}-box-content.expanded {
max-height: 1000px;
transition: max-height 0.5s ease-in;
}
#${id}-toggle-btn:focus #${id}-collapse-icon {
display: block;
}
#${id}-toggle-btn:focus #${id}-expand-icon {
display: none;
}
#${id}-collapse-icon {
display: none;
}
`;
const head = document.head || document.getElementsByTagName('head')[0];
const style = document.createElement('style');
style.type = 'text/css';
style.appendChild(document.createTextNode(css));
head.appendChild(style);
// Add the JavaScript code to toggle the box
const toggleBtnEl = document.querySelector(`#${id}-toggle-btn`);
const boxContentEl = document.querySelector(`#${id}-box-content`);
toggleBtnEl.addEventListener('click', function() {
boxContentEl.classList.toggle('expanded');
});
return box
}
const welcome_message = `
<div>
<code>This is a very early testing Web UI of GPT4All chatbot.
<br>Keep in mind that this is a 7B parameters model running on your own PC's CPU. It is literally 24 times smaller than GPT-3 in terms of parameter count.
<br>While it is still new and not as powerful as GPT-3.5 or GPT-4, it can still be useful for many applications.
<br>Any feedback and contribution is welcomed.
<br>This Web UI is a binding to the GPT4All model that allows you to test a chatbot locally on your machine. Feel free to ask questions or give instructions.</code>
<br>Examples:<br>
<code>
- A color description has been provided. Find the CSS code associated with that color. A light red color with a medium light shade of pink.<br>
- Come up with an interesting idea for a new movie plot. Your plot should be described with a title and a summary.<br>
- Reverse a string in python.<br>
- List 10 dogs.<br>
- Write me a poem about the fall of Julius Ceasar into a ceasar salad in iambic pentameter.<br>
- What is a three word topic describing the following keywords: baseball, football, soccer.<br>
- Act as ChefAI an AI that has the ability to create recipes for any occasion. Instruction: Give me a recipe for my next anniversary.<br>
</code>
</div>
`;
//welcome_message = add_collapsible_div("Note:", text, 'hints');
addMessage("GPT4ALL",welcome_message,0);
// Code for collapsable text
const collapsibles = document.querySelectorAll('.collapsible');
function uncollapse(id){
console.log("uncollapsing")
const content = document.querySelector(`#${id}`);
content.classList.toggle('active');
}

6
static/js/marked.min.js vendored Normal file

File diff suppressed because one or more lines are too long

42
templates/chat.html Normal file
View File

@ -0,0 +1,42 @@
<!DOCTYPE html>
<html>
<head>
<title>GPT4All - WEBUI</title>
<link rel="stylesheet" href="{{ url_for('static', filename='css/chat.css') }}">
</head>
<body class="w-screen h-screen bg-primary text-gray-400 flex flex-col overflow-hidden">
<div class="w-full h-20 border-b border-accent bg-tertiary text-2xl font-bold flex justify-between items-center px-6">
<div class="w-12 h-12"><img src="{{ url_for('static', filename='images/icon.png') }}"></div>
<h1>GPT4All - WEBUI</h1>
</div>
<main class="h-full flex flex-col md:flex-row overflow-hidden max-h-full">
<section class="w-full md:w-1/3 lg:w-1/4 xl:w-1/5 bg-secondary flex md:flex-col md:border-r border-accent sm:border-b md:border-b-0">
<section class="md:h-1/2 md:border-b border-accent flex flex md:flex-col">
<div>
<h1>Discussions</h1>
</div>
<div id="discussions-list" class="h-96 overflow-y-auto">
</div>
</section>
<section class="md:h-1/2 flex sm:space-x-reverse">
<p>Settings</p>
</section>
</section>
<section id="chat-window" class="w-full overflow-y-auto flex flex-col">
</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">
<div id="wait-animation" style="display: none;" class="lds-facebook bg-secondary my-1 mx-1 outline-none drop-shadow-sm w-full rounded-md p-2"><div></div><div></div><div></div></div>
</form>
</footer>
<script src="{{ url_for('static', filename='js/marked.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/chat.js') }}"></script>
</body>
</html>

12
test/test_app.py Normal file
View File

@ -0,0 +1,12 @@
import pytest
from app import app
@pytest.fixture
def client():
with app.test_client() as client:
yield client
def test_homepage(client):
response = client.get('/')
assert response.status_code == 200
assert b"Welcome to my Flask app" in response.data

16
uninstall.bat Normal file
View File

@ -0,0 +1,16 @@
@echo off
echo This will uninstall the environment. Are you sure? [Y/N]
set /p choice=
if /i "%choice%" equ "Y" (
REM Download Python installer
echo -n
set /p="Removing virtual environment..." <nul
powershell -Command "rm env -y"
echo OK
pause
) else (
echo Please install Python and try again.
pause
exit /b 1
)

15
uninstall.sh Normal file
View File

@ -0,0 +1,15 @@
#!/bin/bash
echo "This will uninstall the environment. Are you sure? [Y/N]"
read choice
if [[ "$choice" =~ [yY] ]]; then
# Download Python installer
printf "Removing virtual environment..."
rm -rf env
echo "OK"
read -p "Press [Enter] to continue..."
else
echo "Please install Python and try again."
read -p "Press [Enter] to continue..."
exit 1
fi