Skip to content

Commit

Permalink
Merge pull request #1 from sbslee/0.2.0-dev
Browse files Browse the repository at this point in the history
0.2.0 dev
  • Loading branch information
sbslee authored May 26, 2023
2 parents 153b8fc + 5297245 commit b40dbc4
Show file tree
Hide file tree
Showing 13 changed files with 283 additions and 47 deletions.
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
# CHANGELOG

## 0.2.0 (2023-03-27)
* Enable users to initiate a new chat session.
* Enable users to choose their preferred GPT model.
* Enable users to chat with their documents.

## 0.1.0 (2023-03-24)
Initial release.
* Initial release.
27 changes: 26 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,26 @@
# kanu
# README

KANU is a minimalistic Python-based GUI for various chatbots.

## Installation

```
$ pip install kanu
```

## Screenshots

### Homepage

![Alt Text](./images/home.png)

### ChatGPT

![Alt Text](./images/chatgpt-1.png)
![Alt Text](./images/chatgpt-2.png)

### DocGPT

![Alt Text](./images/docgpt-1.png)
![Alt Text](./images/docgpt-2.png)
![Alt Text](./images/docgpt-3.png)
Binary file added images/chatgpt-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/chatgpt-2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/docgpt-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/docgpt-2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/docgpt-3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/home.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file added kanu/__init__.py
Empty file.
113 changes: 71 additions & 42 deletions kanu/__main__.py
Original file line number Diff line number Diff line change
@@ -1,55 +1,84 @@
import tkinter as tk
from tkinter import messagebox

import openai
import importlib.util

from .version import __version__

def get_input(entry):
user_input = entry.get()
openai.api_key = user_input

def send_message(chat_log, entry, messages):
if openai.api_key is None:
messagebox.showerror("Error", "OpenAI API Key is not set.")
return

message = entry.get()
messages += [{"role": "user", "content": message}]
bot_response = openai.ChatCompletion.create(
model='gpt-3.5-turbo',
messages=messages,
)
response = bot_response["choices"][0]["message"]["content"]
messages += [{"role": "assistant", "content": response}]
chat_log.insert(tk.END, "\nYou: " + message)
chat_log.insert(tk.END, "\nBot: " + response)
entry.delete(0, tk.END)

def main():
root = tk.Tk()
root.title(f"KANU ({__version__})")
root.geometry("800x700")
label = tk.Label(root, text="OpenAI API Key")
label.pack()
class KANU:
def __init__(self, root):
self.container = None
self.root = root
self.root.title(f"KANU ({__version__})")
self.root.geometry("600x400")
self.home_page()

openai_api_key_entry = tk.Entry(root)
openai_api_key_entry.pack()
def home_page(self):
if self.container is not None:
self.container.pack_forget()
self.container = tk.Frame(self.root)
self.container.pack()
title_label = tk.Message(self.container, width=350, text="Welcome to KANU, a minimalistic Python-based GUI for various chatbots! Please select a chatbot to begin.")
title_label.pack()
chatgpt_button = tk.Button(self.container, text="ChatGPT", command=lambda: self.chatgpt_config())
chatgpt_button.pack()
docgpt_button = tk.Button(self.container, text="DocGPT", command=lambda: self.docgpt_config())
docgpt_button.pack()

openai_api_key_button = tk.Button(root, text="Submit", command=lambda: get_input(openai_api_key_entry))
openai_api_key_button.pack()
def chatgpt_config(self):
self.container.pack_forget()
self.container = tk.Frame(self.root)
self.container.pack()
title_label = tk.Label(self.container, text="ChatGPT")
title_label.grid(row=0, column=0, columnspan=2)
self._list_dependencies(1, ["openai"])
key_label = tk.Label(self.container, text="OpenAI API key:")
key_label.grid(row=3, column=0, columnspan=2)
key_entry = tk.Entry(self.container)
key_entry.grid(row=4, column=0, columnspan=2)
submit_button = tk.Button(self.container, text="Submit", command=lambda: self._deploy("ChatGPT", key_entry.get()))
submit_button.grid(row=5, column=0)
back_button = tk.Button(self.container, text="Go back", command=lambda: self.home_page())
back_button.grid(row=5, column=1)

messages = [{"role": "system", "content": "You are a helpful assistant."}]
def docgpt_config(self):
self.container.pack_forget()
self.container = tk.Frame(self.root)
self.container.pack()
title_label = tk.Label(self.container, text="DocGPT")
title_label.grid(row=0, column=0, columnspan=2)
self._list_dependencies(1, ["langchain", "chromadb", "tiktoken"])
key_label = tk.Label(self.container, text="OpenAI API key:")
key_label.grid(row=5, column=0, columnspan=2)
key_entry = tk.Entry(self.container)
key_entry.grid(row=6, column=0, columnspan=2)
submit_button = tk.Button(self.container, text="Submit", command=lambda: self._deploy("DocGPT", key_entry.get()))
submit_button.grid(row=7, column=0)
back_button = tk.Button(self.container, text="Go back", command=lambda: self.home_page())
back_button.grid(row=7, column=1)

chat_log = tk.Text(root, width=70, height=20)
chat_log.pack()
def _deploy(self, agent, *args, **kwargs):
if agent == "ChatGPT":
from .chatgpt import ChatGPT
chatgpt = ChatGPT(self, *args, **kwargs)
chatgpt.run()
elif agent == "DocGPT":
from .docgpt import DocGPT
docgpt = DocGPT(self, *args, **kwargs)
docgpt.run()
else:
raise ValueError(f"Unknown agent {agent}")

chat_entry = tk.Entry(root, width=50)
chat_entry.pack()

chat_send_button = tk.Button(root, text="Send", command=lambda: send_message(chat_log, chat_entry, messages))
chat_send_button.pack()
def _list_dependencies(self, row, packages):
title = tk.Label(self.container, text="Required packages:")
title.grid(row=row, column=0, columnspan=2)
for i, package in enumerate(packages, row+1):
name = tk.Label(self.container, text=package)
name.grid(row=i, column=0)
status = tk.Label(self.container, text="❌" if importlib.util.find_spec(package) is None else "✅")
status.grid(row=i, column=1)

def main():
root = tk.Tk()
kanu = KANU(root)
root.mainloop()

if __name__ == "__main__":
Expand Down
53 changes: 53 additions & 0 deletions kanu/chatgpt.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import tkinter as tk

import openai

class ChatGPT:
def __init__(self, kanu, openai_key):
self.kanu = kanu
self.kanu.container.pack_forget()
self.kanu.container = tk.Frame(self.kanu.root)
self.kanu.container.pack()
openai.api_key = openai_key

def run(self):
title_label = tk.Label(self.kanu.container, text="ChatGPT")
title_label.grid(row=0, column=0, columnspan=3)
self.model = tk.StringVar(self.kanu.container, value="gpt-3.5-turbo")
model_label = tk.Label(self.kanu.container, text="Model:")
model_label.grid(row=1, column=0)
model1_button = tk.Radiobutton(self.kanu.container, variable=self.model, text="gpt-3.5-turbo", value="gpt-3.5-turbo")
model2_button = tk.Radiobutton(self.kanu.container, variable=self.model, text="gpt-4", value="gpt-4")
model1_button.grid(row=1, column=1)
model2_button.grid(row=1, column=2)
session_label = tk.Label(self.kanu.container, text="Chat session")
session_label.grid(row=2, column=0, columnspan=3)
self.session = tk.Text(self.kanu.container, width=70, height=20)
self.session.grid(row=3, column=0, columnspan=3)
entry = tk.Entry(self.kanu.container, width=54)
entry.grid(row=4, column=0, columnspan=3)
self.messages = []
send_button = tk.Button(self.kanu.container, text="Send", command=lambda: self._send_message(entry))
send_button.grid(row=5, column=0)
clear_butoon = tk.Button(self.kanu.container, text="Clear", command=lambda: self._clear_session())
clear_butoon.grid(row=5, column=1)
back_button = tk.Button(self.kanu.container, text="Go back", command=lambda: self.kanu.chatgpt_config())
back_button.grid(row=5, column=2)

def _send_message(self, entry):
if not self.messages:
self.messages.append({"role": "system", "content": "You are a helpful assistant."})
self.messages += [{"role": "user", "content": entry.get()}]
bot_response = openai.ChatCompletion.create(
model=self.model.get(),
messages=self.messages,
)
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 ({self.model.get()}): " + response + "\n")
entry.delete(0, tk.END)

def _clear_session(self):
self.session.delete(1.0, tk.END)
self.messages.clear()
125 changes: 125 additions & 0 deletions kanu/docgpt.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import os
import tkinter as tk
from tkinter import filedialog

from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.text_splitter import CharacterTextSplitter
from langchain.vectorstores import Chroma
from langchain.llms import OpenAI
from langchain.document_loaders import TextLoader
from langchain.chains import RetrievalQA

class DocGPT:
def __init__(self, kanu, openai_key):
self.kanu = kanu
os.environ["OPENAI_API_KEY"] = openai_key

def run(self):
self.kanu.container.pack_forget()
self.kanu.container = tk.Frame(self.kanu.root)
self.kanu.container.pack()
label = tk.Label(self.kanu.container, text="DocGPT")
label.grid(row=0, column=0, columnspan=3)
back_button = tk.Button(self.kanu.container, text="Go back", command=lambda: self.kanu.docgpt_config())
back_button.grid(row=1, column=0)
back_button = tk.Button(self.kanu.container, text="Reload", command=lambda: self.run())
back_button.grid(row=1, column=2)
label = tk.Message(self.kanu.container, width=300, text="Option 1. Create a new database")
label.grid(row=2, column=0, columnspan=3)
f = tk.Label(self.kanu.container, text="Document:")
f.grid(row=3, column=0)
self.document_label = tk.Label(self.kanu.container, text="No file selected", fg="red")
self.document_label.grid(row=3, column=1)
b = tk.Button(self.kanu.container, text="Browse", command=self.specify_document_file)
b.grid(row=3, column=2)
l = tk.Label(self.kanu.container, text="Database:")
l.grid(row=4, column=0)
self.new_database_label = tk.Label(self.kanu.container, text="No directory selected", fg="red")
self.new_database_label.grid(row=4, column=1)
b = tk.Button(self.kanu.container, text="Browse", command=self.specify_new_database_directory)
b.grid(row=4, column=2)
self.option1_button = tk.Button(self.kanu.container, text="Go with Option 1", command=self.go_with_option1)
self.option1_button.grid(row=5, column=0, columnspan=3)
self.option1_button["state"] = tk.DISABLED
label = tk.Message(self.kanu.container, width=300, text="Option 2. Use an existing database")
label.grid(row=6, column=0, columnspan=3)
f = tk.Label(self.kanu.container, text="Database:")
f.grid(row=7, column=0)
self.old_database_label = tk.Label(self.kanu.container, text="No directory selected", fg="red")
self.old_database_label.grid(row=7, column=1)
open_button = tk.Button(self.kanu.container, text="Browse", command=self.specify_old_database_directory)
open_button.grid(row=7, column=2)
self.option2_button = tk.Button(self.kanu.container, text="Go with Option 2", command=self.go_with_option2)
self.option2_button.grid(row=8, column=0, columnspan=3)
self.option2_button["state"] = tk.DISABLED

def query(self):
self.db = Chroma(persist_directory=self.database_directory, embedding_function=OpenAIEmbeddings())
self.qa = RetrievalQA.from_chain_type(llm=OpenAI(), chain_type="stuff", retriever=self.db.as_retriever())
self.kanu.container.pack_forget()
self.kanu.container = tk.Frame(self.kanu.root)
self.kanu.container.pack()
title_label = tk.Label(self.kanu.container, text="DocGPT")
title_label.grid(row=0, column=0, columnspan=3)
session_label = tk.Label(self.kanu.container, text="Chat session")
session_label.grid(row=1, column=0, columnspan=3)
self.session = tk.Text(self.kanu.container, width=70, height=20)
self.session.grid(row=2, column=0, columnspan=3)
entry = tk.Entry(self.kanu.container, width=54)
entry.grid(row=3, column=0, columnspan=3)
send_button = tk.Button(self.kanu.container, text="Send", command=lambda: self._send_message(entry))
send_button.grid(row=4, column=0)
clear_butoon = tk.Button(self.kanu.container, text="Clear", command=lambda: self.clear_session())
clear_butoon.grid(row=4, column=1)
back_button = tk.Button(self.kanu.container, text="Go back", command=lambda: self.run())
back_button.grid(row=4, column=2)

def _send_message(self, entry):
self.session.insert(tk.END, "You: " + entry.get() + "\n")
response = self.qa(entry.get())["result"]
self.session.insert(tk.END, "Bot:" + response + "\n")
entry.delete(0, tk.END)

def go_with_option1(self):
loader = TextLoader(self.document_file)
documents = loader.load()
text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
docs = text_splitter.split_documents(documents)
embeddings = OpenAIEmbeddings()
db = Chroma.from_documents(docs, embeddings, persist_directory=self.database_directory)
db.persist()
db = None
self.query()

def go_with_option2(self):
self.query()

def specify_document_file(self):
file_path = filedialog.askopenfilename()
if not file_path:
return
self.document_file = file_path
self.document_label.configure(text=os.path.basename(file_path), fg="lime green")
if self.new_database_label["text"] != "No directory selected":
self.option1_button["state"] = tk.NORMAL

def specify_new_database_directory(self):
directory_path = filedialog.askdirectory()
if not directory_path:
return
self.database_directory = directory_path
self.new_database_label.configure(text=os.path.basename(directory_path), fg="lime green")
if self.document_label["text"] != "No file selected":
self.option1_button["state"] = tk.NORMAL

def specify_old_database_directory(self):
directory_path = filedialog.askdirectory()
if not directory_path:
return
self.database_directory = directory_path
self.old_database_label.configure(text=os.path.basename(directory_path), fg="lime green")
self.option2_button["state"] = tk.NORMAL

def clear_session(self):
self.session.delete(1.0, tk.END)

5 changes: 2 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,11 @@
version=__version__,
author='Seung-been "Steven" Lee',
author_email="[email protected]",
description="KANU",
description="A minimalistic Python-based GUI for various chatbots",
url="https://github.com/sbslee/kanu",
packages=find_packages(),
install_requires=["openai"],
license="MIT",
entry_points={"console_scripts": ["kanu=kanu.__main__:main"]},
long_description="This is a detailed description of the package.",
long_description="A minimalistic Python-based GUI for various chatbots",
long_description_content_type="text/plain"
)

0 comments on commit b40dbc4

Please sign in to comment.