diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6ef0784..f68d1e8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,9 @@
# CHANGELOG
+## 0.6.0 (2023-06-12)
+* Enable users to chat with .csv documents.
+* Enable users to customize chat settings (e.g. font size and background color).
+
## 0.5.0 (2023-06-08)
* Enable users to ask follow-up questions when chatting with documents.
* Enable users to customize chatbot parameters by uploading a configuration file.
diff --git a/README.md b/README.md
index 79ce62d..8f02cd3 100644
--- a/README.md
+++ b/README.md
@@ -2,30 +2,71 @@
[![PyPI](https://badge.fury.io/py/kanu.svg)](https://badge.fury.io/py/kanu)
-KANU is a minimalistic Python-based GUI for various chatbots.
+Welcome to KANU, a minimalistic Python-based GUI for various chatbots.
-## Chatbots
+There are currently two chatbots available in KANU:
-### ChatGPT
+- [ChatGPT](#chatgpt) harnesses the power of ChatGPT, bringing it directly to your local computer
+- [DocGPT](#docgpt) allows you to effortlessly interact with your documents and ask questions about them
-![Alt Text](https://raw.githubusercontent.com/sbslee/kanu/main/images/chatgpt.gif)
+Other features of KANU inclde:
-### DocGPT
-
-![Alt Text](https://raw.githubusercontent.com/sbslee/kanu/main/images/docgpt.gif)
+- Customize chat settings (e.g. font size and background color)
+- Customize chatbot parameters (e.g. prompt, temperature, and chunk size) by directly using the GUI or uploading a configuration file
## Installation
+The recommended way is via pip:
+
```
$ pip install kanu
```
+KANU requires a different set of dependencies for each chatbot. You can find the dependencies specific to each chatbot in the [Chatbots](#chatbots) section.
+
## Running
```
$ kanu
```
+
+## Chatbots
+
+
+### ChatGPT
+
+![Alt Text](https://raw.githubusercontent.com/sbslee/kanu/main/images/chatgpt.gif)
+
+The following packages are required to run ChatGPT:
+
+```
+openai # Required.
+```
+
+
+### DocGPT
+
+![Alt Text](https://raw.githubusercontent.com/sbslee/kanu/main/images/docgpt.gif)
+
+The following document formats are supported by DocGPT:
+
+- .txt
+- .pdf
+- .doc and .docx
+- .csv
+
+The following packages are required to run DocGPT:
+
+```
+langchain # Required.
+chromadb # Required.
+tiktoken # Required.
+pdfminer.six # Optional. Only required for .pdf documents.
+unstructured # Optional. Only required for .doc and .docx documents.
+tabulate # Optional. Only required for .doc and .docx documents.
+```
+
## Changelog
See the [CHANGELOG.md](https://github.com/sbslee/kanu/blob/main/CHANGELOG.md) file for details.
\ No newline at end of file
diff --git a/kanu/__main__.py b/kanu/__main__.py
index 0127342..924cb55 100644
--- a/kanu/__main__.py
+++ b/kanu/__main__.py
@@ -84,14 +84,20 @@ def config_chatgpt(self):
def parse_chatgpt_config(self):
config = configparser.ConfigParser()
- config.read(filedialog.askopenfilename())
+ file_path = filedialog.askopenfilename()
+ if not file_path:
+ return
+ config.read(file_path)
self.deploy_agent("ChatGPT", config["USER"]["openai_key"], config["DEFAULT"]["model"], float(config["DEFAULT"]["temperature"]), config["DEFAULT"]["prompt"])
def template_chatgpt_config(self):
+ file_path = filedialog.asksaveasfilename()
+ if not file_path:
+ return
config = configparser.ConfigParser()
config["DEFAULT"] = {"model": "gpt-3.5-turbo", "temperature": "0.5", "prompt": CHATGPT_PROMPT}
config["USER"] = {"openai_key": ""}
- with open(filedialog.asksaveasfilename(), "w") as f:
+ with open(file_path, "w") as f:
config.write(f)
def config_docgpt(self):
@@ -144,21 +150,27 @@ def config_docgpt(self):
l.grid(row=18, column=0, columnspan=2)
e = tk.Entry(self.container)
e.grid(row=19, column=0, columnspan=2)
- b = tk.Button(self.container, text="Submit", command=lambda: self.deploy_agent("DocGPT", e.get(), self.model.get(), self.prompt.get("1.0", "end-1c"), self.temperature.get()))
+ b = tk.Button(self.container, text="Submit", command=lambda: self.deploy_agent("DocGPT", e.get(), self.model.get(), self.prompt.get("1.0", "end-1c"), self.temperature.get(), 1000, 50))
b.grid(row=20, column=0)
b = tk.Button(self.container, text="Go back", command=lambda: self.homepage())
b.grid(row=20, column=1)
def parse_docgpt_config(self):
config = configparser.ConfigParser()
- config.read(filedialog.askopenfilename())
- self.deploy_agent("DocGPT", config["USER"]["openai_key"], config["DEFAULT"]["model"], float(config["DEFAULT"]["temperature"]), config["DEFAULT"]["prompt"])
+ file_path = filedialog.askopenfilename()
+ if not file_path:
+ return
+ config.read(file_path)
+ self.deploy_agent("DocGPT", config["USER"]["openai_key"], config["DEFAULT"]["model"], float(config["DEFAULT"]["temperature"]), config["DEFAULT"]["prompt"], config["DEFAULT"]["chunk_size"], config["DEFAULT"]["chunk_overlap"])
def template_docgpt_config(self):
+ file_path = filedialog.asksaveasfilename()
+ if not file_path:
+ return
config = configparser.ConfigParser()
- config["DEFAULT"] = {"model": "gpt-3.5-turbo", "temperature": "0.5", "prompt": DOCGPT_PROMPT}
+ config["DEFAULT"] = {"model": "gpt-3.5-turbo", "temperature": "0.5", "prompt": DOCGPT_PROMPT, "chunk_size": 1000, "chunk_overlap": 50}
config["USER"] = {"openai_key": ""}
- with open(filedialog.asksaveasfilename(), "w") as f:
+ with open(file_path, "w") as f:
config.write(f)
def deploy_agent(self, agent, *args, **kwargs):
diff --git a/kanu/chatgpt.py b/kanu/chatgpt.py
index 978fc0a..ee705ac 100644
--- a/kanu/chatgpt.py
+++ b/kanu/chatgpt.py
@@ -2,6 +2,8 @@
import openai
+from .utils import Settings
+
class ChatGPT:
def __init__(self, kanu, openai_key, model, temperature, prompt):
self.kanu = kanu
@@ -9,24 +11,29 @@ def __init__(self, kanu, openai_key, model, temperature, prompt):
self.temperature = temperature
self.prompt = prompt
openai.api_key = openai_key
+ self.settings = Settings(self)
def run(self):
self.kanu.container.pack_forget()
self.kanu.container = tk.Frame(self.kanu.root)
self.kanu.container.pack()
l = tk.Label(self.kanu.container, text="ChatGPT")
- l.grid(row=0, column=0, columnspan=3)
+ l.grid(row=0, column=0, columnspan=4)
self.session = tk.Text(self.kanu.container, width=70, height=20)
- self.session.grid(row=1, column=0, columnspan=3)
- e = tk.Entry(self.kanu.container, width=54)
- e.grid(row=2, column=0, columnspan=3)
+ self.session.grid(row=1, column=0, columnspan=4)
+ self.session.tag_config("user", **self.settings.get_user_kwargs())
+ self.session.tag_config("bot", **self.settings.get_bot_kwargs())
+ user_input = tk.Entry(self.kanu.container, width=54)
+ user_input.grid(row=2, column=0, columnspan=4)
self.messages = []
- b = tk.Button(self.kanu.container, text="Send", command=lambda: self.send_message(e))
+ b = tk.Button(self.kanu.container, text="Send", command=lambda: self.send_message(user_input))
b.grid(row=3, column=0)
b = tk.Button(self.kanu.container, text="Clear", command=lambda: self.clear_session())
b.grid(row=3, column=1)
b = tk.Button(self.kanu.container, text="Go back", command=lambda: self.kanu.config_chatgpt())
b.grid(row=3, column=2)
+ b = tk.Button(self.kanu.container, text="Settings", command=lambda: self.settings.page())
+ b.grid(row=3, column=3)
def send_message(self, entry):
if not self.messages:
@@ -39,8 +46,8 @@ def send_message(self, entry):
)
response = bot_response["choices"][0]["message"]["content"]
self.messages += [{"role": "assistant", "content": response}]
- self.session.insert(tk.END, "You: " + entry.get() + "\n")
- self.session.insert(tk.END, f"Bot: " + response + "\n")
+ self.session.insert(tk.END, "You: " + entry.get() + "\n", "user")
+ self.session.insert(tk.END, f"Bot: " + response + "\n", "bot")
entry.delete(0, tk.END)
def clear_session(self):
diff --git a/kanu/docgpt.py b/kanu/docgpt.py
index d29835e..7803419 100644
--- a/kanu/docgpt.py
+++ b/kanu/docgpt.py
@@ -13,25 +13,30 @@
from langchain.document_loaders import (
TextLoader,
PDFMinerLoader,
- UnstructuredWordDocumentLoader
+ UnstructuredWordDocumentLoader,
+ CSVLoader,
)
-from .utils import Tooltip
+from .utils import Tooltip, Settings
DOCUMENT_LOADERS = {
".txt": (TextLoader, {"encoding": "utf8"}),
".pdf": (PDFMinerLoader, {}),
".doc": (UnstructuredWordDocumentLoader, {}),
".docx": (UnstructuredWordDocumentLoader, {}),
+ ".csv": (CSVLoader, {}),
}
class DocGPT:
- def __init__(self, kanu, openai_key, model, temperature, prompt):
+ def __init__(self, kanu, openai_key, model, temperature, prompt, default_chunk_size, default_chunk_overlap):
self.kanu = kanu
self.model = model
self.temperature = temperature
self.prompt = prompt
+ self.default_chunk_size = default_chunk_size
+ self.default_chunk_overlap = default_chunk_overlap
os.environ["OPENAI_API_KEY"] = openai_key
+ self.settings = Settings(self)
def run(self):
self.kanu.container.pack_forget()
@@ -62,13 +67,13 @@ def run(self):
l = tk.Label(self.kanu.container, text="Chunk size ⓘ:")
Tooltip(l, "The maximum number of characters in each chunk.")
l.grid(row=5, column=0)
- self.chunk_size = tk.IntVar(self.kanu.container, value=1000)
+ self.chunk_size = tk.IntVar(self.kanu.container, value=self.default_chunk_size)
e = tk.Entry(self.kanu.container, textvariable=self.chunk_size)
e.grid(row=5, column=1, columnspan=2)
l = tk.Label(self.kanu.container, text="Chunk overlap ⓘ:")
Tooltip(l, "The number of overlapping characters between adjacent chunks.")
l.grid(row=6, column=0)
- self.chunk_overlap = tk.IntVar(self.kanu.container, value=50)
+ self.chunk_overlap = tk.IntVar(self.kanu.container, value=self.default_chunk_overlap)
e = tk.Entry(self.kanu.container, textvariable=self.chunk_overlap)
e.grid(row=6, column=1, columnspan=2)
self.option1_button = tk.Button(self.kanu.container, text="Go with Option 1", command=self.go_with_option1)
@@ -101,22 +106,26 @@ def query(self):
self.kanu.container = tk.Frame(self.kanu.root)
self.kanu.container.pack()
l = tk.Label(self.kanu.container, text="DocGPT")
- l.grid(row=0, column=0, columnspan=3)
+ l.grid(row=0, column=0, columnspan=4)
self.session = tk.Text(self.kanu.container, width=70, height=20)
- self.session.grid(row=1, column=0, columnspan=3)
- e = tk.Entry(self.kanu.container, width=54)
- e.grid(row=2, column=0, columnspan=3)
- b = tk.Button(self.kanu.container, text="Send", command=lambda: self.send_message(e))
+ self.session.grid(row=1, column=0, columnspan=4)
+ self.session.tag_config("user", **self.settings.get_user_kwargs())
+ self.session.tag_config("bot", **self.settings.get_bot_kwargs())
+ user_input = tk.Entry(self.kanu.container, width=54)
+ user_input.grid(row=2, column=0, columnspan=4)
+ b = tk.Button(self.kanu.container, text="Send", command=lambda: self.send_message(user_input))
b.grid(row=3, column=0)
b = tk.Button(self.kanu.container, text="Clear", command=lambda: self.clear_session())
b.grid(row=3, column=1)
b = tk.Button(self.kanu.container, text="Go back", command=lambda: self.run())
b.grid(row=3, column=2)
+ b = tk.Button(self.kanu.container, text="Settings", command=lambda: self.settings.page())
+ b.grid(row=3, column=3)
def send_message(self, entry):
- self.session.insert(tk.END, "You: " + entry.get() + "\n")
+ self.session.insert(tk.END, "You: " + entry.get() + "\n", "user")
response = self.qa(entry.get())["answer"]
- self.session.insert(tk.END, "Bot: " + response + "\n")
+ self.session.insert(tk.END, "Bot: " + response + "\n", "bot")
entry.delete(0, tk.END)
def go_with_option1(self):
@@ -129,12 +138,12 @@ def go_with_option1(self):
continue
loader_class, loader_kwargs = DOCUMENT_LOADERS[file_ext]
loader = loader_class(file_path, **loader_kwargs)
- document = loader.load()[0]
- documents.append(document)
+ document = loader.load()
+ documents.extend(document)
text_splitter = RecursiveCharacterTextSplitter(chunk_size=self.chunk_size.get(), chunk_overlap=self.chunk_overlap.get())
texts = text_splitter.split_documents(documents)
db = Chroma.from_documents(texts, OpenAIEmbeddings(), persist_directory=self.database_directory)
- db.add_documents(texts)
+ db.add_documents(texts)
db.persist()
db = None
self.query()
diff --git a/kanu/utils.py b/kanu/utils.py
index 748a678..d0e4879 100644
--- a/kanu/utils.py
+++ b/kanu/utils.py
@@ -1,4 +1,109 @@
import tkinter as tk
+from tkinter import font
+
+class Settings:
+ def __init__(self, agent):
+ self.default_font = font.nametofont("TkDefaultFont").actual()
+ self.default_user_background_color = "gray85"
+ self.default_user_foreground_color = "black"
+ self.default_user_font_family = self.default_font["family"]
+ self.default_user_font_size = self.default_font["size"]
+ self.default_bot_background_color = "white"
+ self.default_bot_foreground_color = "black"
+ self.default_bot_font_family = self.default_font["family"]
+ self.default_bot_font_size = self.default_font["size"]
+ self.agent = agent
+ self.user_background_color = tk.StringVar(self.agent.kanu.container, value=self.default_user_background_color)
+ self.user_foreground_color = tk.StringVar(self.agent.kanu.container, value=self.default_user_foreground_color)
+ self.user_font_family = tk.StringVar(self.agent.kanu.container, value=self.default_user_font_family)
+ self.user_font_size = tk.IntVar(self.agent.kanu.container, value=self.default_user_font_size)
+ self.bot_background_color = tk.StringVar(self.agent.kanu.container, value=self.default_bot_background_color)
+ self.bot_foreground_color = tk.StringVar(self.agent.kanu.container, value=self.default_bot_foreground_color)
+ self.bot_font_family = tk.StringVar(self.agent.kanu.container, value=self.default_bot_font_family)
+ self.bot_font_size = tk.IntVar(self.agent.kanu.container, value=self.default_bot_font_size)
+
+ def get_user_kwargs(self):
+ return dict(
+ background=self.user_background_color.get(),
+ foreground=self.user_foreground_color.get(),
+ font=(self.user_font_family.get(), self.user_font_size.get())
+ )
+
+ def get_bot_kwargs(self):
+ return dict(
+ background=self.bot_background_color.get(),
+ foreground=self.bot_foreground_color.get(),
+ font=(self.bot_font_family.get(), self.bot_font_size.get())
+ )
+
+ def page(self):
+ self.agent.previous = self.agent.kanu.container
+ self.agent.kanu.container.pack_forget()
+ self.agent.kanu.container = tk.Frame(self.agent.kanu.root)
+ self.agent.kanu.container.pack()
+ l = tk.Label(self.agent.kanu.container, text=self.agent.__class__.__name__)
+ l.grid(row=0, column=0, columnspan=3)
+ l = tk.Label(self.agent.kanu.container, text="User background color")
+ l.grid(row=1, column=0)
+ e = tk.Entry(self.agent.kanu.container, textvariable=self.user_background_color)
+ e.grid(row=1, column=1, columnspan=2)
+ l = tk.Label(self.agent.kanu.container, text="User foreground color")
+ l.grid(row=2, column=0)
+ e = tk.Entry(self.agent.kanu.container, textvariable=self.user_foreground_color)
+ e.grid(row=2, column=1, columnspan=2)
+ l = tk.Label(self.agent.kanu.container, text="User font family")
+ l.grid(row=3, column=0)
+ e = tk.Entry(self.agent.kanu.container, textvariable=self.user_font_family)
+ e.grid(row=3, column=1, columnspan=2)
+ l = tk.Label(self.agent.kanu.container, text="User font size")
+ l.grid(row=4, column=0)
+ e = tk.Entry(self.agent.kanu.container, textvariable=self.user_font_size)
+ e.grid(row=4, column=1, columnspan=2)
+ l = tk.Label(self.agent.kanu.container, text="Bot background color")
+ l.grid(row=5, column=0)
+ e = tk.Entry(self.agent.kanu.container, textvariable=self.bot_background_color)
+ e.grid(row=5, column=1, columnspan=2)
+ l = tk.Label(self.agent.kanu.container, text="Bot foreground color")
+ l.grid(row=6, column=0)
+ e = tk.Entry(self.agent.kanu.container, textvariable=self.bot_foreground_color)
+ e.grid(row=6, column=1, columnspan=2)
+ l = tk.Label(self.agent.kanu.container, text="Bot font family")
+ l.grid(row=7, column=0)
+ e = tk.Entry(self.agent.kanu.container, textvariable=self.bot_font_family)
+ e.grid(row=7, column=1, columnspan=2)
+ l = tk.Label(self.agent.kanu.container, text="Bot font size")
+ l.grid(row=8, column=0)
+ e = tk.Entry(self.agent.kanu.container, textvariable=self.bot_font_size)
+ e.grid(row=8, column=1, columnspan=2)
+ b = tk.Button(self.agent.kanu.container, text="Apply", command=lambda: self.apply())
+ b.grid(row=9, column=0)
+ b = tk.Button(self.agent.kanu.container, text="Reset", command=lambda: self.reset())
+ b.grid(row=9, column=1)
+ b = tk.Button(self.agent.kanu.container, text="Go back", command=lambda: self.go_back())
+ b.grid(row=9, column=2)
+
+ def go_back(self):
+ self.agent.kanu.container.pack_forget()
+ self.agent.kanu.container = self.agent.previous
+ self.agent.kanu.container.pack()
+
+ def apply(self):
+ self.agent.session.tag_config("user", **self.get_user_kwargs())
+ self.agent.session.tag_config("bot", **self.get_bot_kwargs())
+ self.agent.kanu.container.pack_forget()
+ self.agent.kanu.container = self.agent.previous
+ self.agent.kanu.container.pack()
+
+ def reset(self):
+ self.user_background_color = tk.StringVar(self.agent.kanu.container, value=self.default_user_background_color)
+ self.user_foreground_color = tk.StringVar(self.agent.kanu.container, value=self.default_user_foreground_color)
+ self.user_font_family = tk.StringVar(self.agent.kanu.container, value=self.default_user_font_family)
+ self.user_font_size = tk.IntVar(self.agent.kanu.container, value=self.default_user_font_size)
+ self.bot_background_color = tk.StringVar(self.agent.kanu.container, value=self.default_bot_background_color)
+ self.bot_foreground_color = tk.StringVar(self.agent.kanu.container, value=self.default_bot_foreground_color)
+ self.bot_font_family = tk.StringVar(self.agent.kanu.container, value=self.default_bot_font_family)
+ self.bot_font_size = tk.IntVar(self.agent.kanu.container, value=self.default_bot_font_size)
+ self.apply()
class Tooltip:
def __init__(self, widget, text):
diff --git a/kanu/version.py b/kanu/version.py
index 5a28280..da74604 100644
--- a/kanu/version.py
+++ b/kanu/version.py
@@ -1 +1 @@
-__version__ = "0.5.0"
\ No newline at end of file
+__version__ = "0.6.0"
\ No newline at end of file