From 67ffabe331df16de838d64ab99fe986fee855859 Mon Sep 17 00:00:00 2001 From: "Seung-been \"Steven\" Lee" Date: Mon, 12 Jun 2023 12:30:19 +0900 Subject: [PATCH 01/25] Bump up version number --- CHANGELOG.md | 2 ++ kanu/version.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f68d1e8..6a46ef7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # CHANGELOG +## 0.7.0 (in development) + ## 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). diff --git a/kanu/version.py b/kanu/version.py index da74604..faee7a1 100644 --- a/kanu/version.py +++ b/kanu/version.py @@ -1 +1 @@ -__version__ = "0.6.0" \ No newline at end of file +__version__ = "0.7.0" \ No newline at end of file From 6823ea3f25e002e6ca34f1305bcb60a629bcc082 Mon Sep 17 00:00:00 2001 From: Seung-been Lee Date: Mon, 12 Jun 2023 21:43:55 +0900 Subject: [PATCH 02/25] Add token counter and price monitor in chat window --- CHANGELOG.md | 1 + kanu/__main__.py | 33 +++++++++++++++++---------------- kanu/chatgpt.py | 12 ++++++++++-- kanu/utils.py | 23 +++++++++++++++++++++++ 4 files changed, 51 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a46ef7..0b4758e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # CHANGELOG ## 0.7.0 (in development) +* Implement token counter and price monitor in chat window. ## 0.6.0 (2023-06-12) * Enable users to chat with .csv documents. diff --git a/kanu/__main__.py b/kanu/__main__.py index 924cb55..fd1ad2b 100644 --- a/kanu/__main__.py +++ b/kanu/__main__.py @@ -43,44 +43,45 @@ def config_chatgpt(self): l = tk.Label(self.container, text="Required packages:") l.grid(row=1, column=0, columnspan=2) self.display_required_dependency(2, "openai") + self.display_required_dependency(3, "tiktoken") m = tk.Message(self.container, width=300, text="Option 1. Upload a configuration file") - m.grid(row=3, column=0, columnspan=2) + m.grid(row=4, column=0, columnspan=2) b = tk.Button(self.container, text="Browse", command=self.parse_chatgpt_config) - b.grid(row=4, column=0) + b.grid(row=5, column=0) b = tk.Button(self.container, text="Template", command=self.template_chatgpt_config) - b.grid(row=4, column=1) + b.grid(row=5, column=1) m = tk.Message(self.container, width=300, text="Option 2. Configure manually") - m.grid(row=5, column=0, columnspan=2) + m.grid(row=6, column=0, columnspan=2) self.model = tk.StringVar(self.container, value="gpt-3.5-turbo") l = tk.Label(self.container, text="Model:") - l.grid(row=6, column=0, columnspan=2) + l.grid(row=7, column=0, columnspan=2) b = tk.Radiobutton(self.container, variable=self.model, text="gpt-3.5-turbo", value="gpt-3.5-turbo") - b.grid(row=7, column=0) + b.grid(row=8, column=0) b = tk.Radiobutton(self.container, variable=self.model, text="gpt-4", value="gpt-4") - b.grid(row=7, column=1) + b.grid(row=8, column=1) l = tk.Label(self.container, text="System message ⓘ:") Tooltip(l, "The system message helps set the behavior of the chatbot.") - l.grid(row=8, column=0, columnspan=2) + l.grid(row=9, column=0, columnspan=2) self.prompt = tk.Text(self.container, height=9, width=42) sb = tk.Scrollbar(self.container, command=self.prompt.yview) self.prompt.insert("1.0", CHATGPT_PROMPT) - self.prompt.grid(row=9, column=0, columnspan=2, sticky="nsew") - sb.grid(row=9, column=2, sticky="ns") + self.prompt.grid(row=10, column=0, columnspan=2, sticky="nsew") + sb.grid(row=10, column=2, sticky="ns") self.prompt["yscrollcommand"] = sb.set l = tk.Label(self.container, text="Temperature ⓘ:") Tooltip(l, "The randomness in generating responses, which ranges between 0 and 1, with 0 indicating almost deterministic behavior.") - l.grid(row=10, column=0, columnspan=2) + l.grid(row=11, column=0, columnspan=2) self.temperature = tk.DoubleVar(self.container, value=0.5) e = tk.Entry(self.container, textvariable=self.temperature) - e.grid(row=11, column=0, columnspan=2) + e.grid(row=12, column=0, columnspan=2) l = tk.Label(self.container, text="OpenAI API key:") - l.grid(row=12, column=0, columnspan=2) + l.grid(row=13, column=0, columnspan=2) e = tk.Entry(self.container) - e.grid(row=13, column=0, columnspan=2) + e.grid(row=14, column=0, columnspan=2) b = tk.Button(self.container, text="Submit", command=lambda: self.deploy_agent("ChatGPT", e.get(), self.model.get(), self.temperature.get(), self.prompt.get("1.0", "end-1c"))) - b.grid(row=14, column=0) + b.grid(row=15, column=0) b = tk.Button(self.container, text="Go back", command=lambda: self.homepage()) - b.grid(row=14, column=1) + b.grid(row=15, column=1) def parse_chatgpt_config(self): config = configparser.ConfigParser() diff --git a/kanu/chatgpt.py b/kanu/chatgpt.py index ee705ac..c5b089a 100644 --- a/kanu/chatgpt.py +++ b/kanu/chatgpt.py @@ -2,7 +2,7 @@ import openai -from .utils import Settings +from .utils import Settings, Tokenizer class ChatGPT: def __init__(self, kanu, openai_key, model, temperature, prompt): @@ -12,6 +12,7 @@ def __init__(self, kanu, openai_key, model, temperature, prompt): self.prompt = prompt openai.api_key = openai_key self.settings = Settings(self) + self.tokenizer = Tokenizer(model) def run(self): self.kanu.container.pack_forget() @@ -34,6 +35,9 @@ def run(self): 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) + self.tally = tk.Text(self.kanu.container, width=22, height=1) + self.tally.grid(row=4, column=0, columnspan=4) + self.tally.configure(background="gray93", foreground="red") def send_message(self, entry): if not self.messages: @@ -48,8 +52,12 @@ def send_message(self, entry): self.messages += [{"role": "assistant", "content": response}] self.session.insert(tk.END, "You: " + entry.get() + "\n", "user") self.session.insert(tk.END, f"Bot: " + response + "\n", "bot") + self.tokenizer.add(entry.get()) + self.tally.delete(1.0, tk.END) + self.tally.insert(tk.END, f"Tokens: {self.tokenizer.total} (${self.tokenizer.cost()})\n") entry.delete(0, tk.END) def clear_session(self): self.session.delete(1.0, tk.END) - self.messages.clear() \ No newline at end of file + self.messages.clear() + self.tally.delete(1.0, tk.END) \ No newline at end of file diff --git a/kanu/utils.py b/kanu/utils.py index d0e4879..ba34396 100644 --- a/kanu/utils.py +++ b/kanu/utils.py @@ -1,6 +1,29 @@ import tkinter as tk from tkinter import font +import tiktoken + +class Tokenizer: + # https://openai.com/pricing#language-models + PRICES = {"gpt-3.5-turbo": 0.002/1000, "gpt-4": 0.03/1000} + + def __init__(self, model): + self.total = 0 + self.tokens = [] + self.model = model + self.encoding = tiktoken.encoding_for_model(model) + + def add(self, text): + token = len(self.encoding.encode(text)) + self.total += token + self.tokens.append(token) + + def cost(self): + return self.total * self.PRICES[self.model] + + def dollars(self): + return [x * self.PRICES[self.model] for x in self.tokens] + class Settings: def __init__(self, agent): self.default_font = font.nametofont("TkDefaultFont").actual() From d5f4c9ae3da7543f130c41c64ef046d073e67377 Mon Sep 17 00:00:00 2001 From: Seung-been Lee Date: Mon, 12 Jun 2023 21:53:59 +0900 Subject: [PATCH 03/25] Update README.md --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8f02cd3..04c7516 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ Other features of KANU inclde: - 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 +- Display token counter and price monitor in chat window ## Installation @@ -41,7 +42,8 @@ $ kanu The following packages are required to run ChatGPT: ``` -openai # Required. +openai # Required. +tiktoken # Required. ``` @@ -60,7 +62,7 @@ The following packages are required to run DocGPT: ``` langchain # Required. -chromadb # Required. +chromadb # Required. tiktoken # Required. pdfminer.six # Optional. Only required for .pdf documents. unstructured # Optional. Only required for .doc and .docx documents. From a80941ad3a133f44825571a6bb538a9cb84b87eb Mon Sep 17 00:00:00 2001 From: "Seung-been \"Steven\" Lee" Date: Tue, 13 Jun 2023 08:42:04 +0900 Subject: [PATCH 04/25] Add token counter and price monitor in chat window --- kanu/chatgpt.py | 23 ++++++++++++----------- kanu/docgpt.py | 24 ++++++++++++++++-------- 2 files changed, 28 insertions(+), 19 deletions(-) diff --git a/kanu/chatgpt.py b/kanu/chatgpt.py index c5b089a..9ec6d99 100644 --- a/kanu/chatgpt.py +++ b/kanu/chatgpt.py @@ -20,24 +20,24 @@ def run(self): self.kanu.container.pack() l = tk.Label(self.kanu.container, text="ChatGPT") l.grid(row=0, column=0, columnspan=4) + self.tally = tk.Text(self.kanu.container, width=70, height=1) + self.tally.insert(tk.END, "Tokens: 0 ($0)\n") + self.tally.grid(row=1, column=0, columnspan=4) self.session = tk.Text(self.kanu.container, width=70, height=20) - self.session.grid(row=1, column=0, columnspan=4) + self.session.grid(row=2, 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) + user_input.grid(row=3, column=0, columnspan=4) self.messages = [] b = tk.Button(self.kanu.container, text="Send", command=lambda: self.send_message(user_input)) - b.grid(row=3, column=0) + b.grid(row=4, column=0) b = tk.Button(self.kanu.container, text="Clear", command=lambda: self.clear_session()) - b.grid(row=3, column=1) + b.grid(row=4, column=1) b = tk.Button(self.kanu.container, text="Go back", command=lambda: self.kanu.config_chatgpt()) - b.grid(row=3, column=2) + b.grid(row=4, column=2) b = tk.Button(self.kanu.container, text="Settings", command=lambda: self.settings.page()) - b.grid(row=3, column=3) - self.tally = tk.Text(self.kanu.container, width=22, height=1) - self.tally.grid(row=4, column=0, columnspan=4) - self.tally.configure(background="gray93", foreground="red") + b.grid(row=4, column=3) def send_message(self, entry): if not self.messages: @@ -54,10 +54,11 @@ def send_message(self, entry): self.session.insert(tk.END, f"Bot: " + response + "\n", "bot") self.tokenizer.add(entry.get()) self.tally.delete(1.0, tk.END) - self.tally.insert(tk.END, f"Tokens: {self.tokenizer.total} (${self.tokenizer.cost()})\n") + self.tally.insert(tk.END, f"Tokens: {self.tokenizer.total} (${self.tokenizer.cost():.6f})\n") entry.delete(0, tk.END) def clear_session(self): self.session.delete(1.0, tk.END) self.messages.clear() - self.tally.delete(1.0, tk.END) \ No newline at end of file + self.tally.delete(1.0, tk.END) + self.tally.insert(tk.END, "Tokens: 0 ($0)\n") \ No newline at end of file diff --git a/kanu/docgpt.py b/kanu/docgpt.py index 7803419..2ae5d84 100644 --- a/kanu/docgpt.py +++ b/kanu/docgpt.py @@ -17,7 +17,7 @@ CSVLoader, ) -from .utils import Tooltip, Settings +from .utils import Tooltip, Settings, Tokenizer DOCUMENT_LOADERS = { ".txt": (TextLoader, {"encoding": "utf8"}), @@ -37,6 +37,7 @@ def __init__(self, kanu, openai_key, model, temperature, prompt, default_chunk_s self.default_chunk_overlap = default_chunk_overlap os.environ["OPENAI_API_KEY"] = openai_key self.settings = Settings(self) + self.tokenizer = Tokenizer(model) def run(self): self.kanu.container.pack_forget() @@ -106,26 +107,32 @@ 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=4) + l.grid(row=0, column=0, columnspan=4) + self.tally = tk.Text(self.kanu.container, width=70, height=1) + self.tally.insert(tk.END, "Tokens: 0 ($0)\n") + self.tally.grid(row=1, column=0, columnspan=4) self.session = tk.Text(self.kanu.container, width=70, height=20) - self.session.grid(row=1, column=0, columnspan=4) + self.session.grid(row=2, 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) + user_input.grid(row=3, 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.grid(row=4, column=0) b = tk.Button(self.kanu.container, text="Clear", command=lambda: self.clear_session()) - b.grid(row=3, column=1) + b.grid(row=4, column=1) b = tk.Button(self.kanu.container, text="Go back", command=lambda: self.run()) - b.grid(row=3, column=2) + b.grid(row=4, column=2) b = tk.Button(self.kanu.container, text="Settings", command=lambda: self.settings.page()) - b.grid(row=3, column=3) + b.grid(row=4, column=3) def send_message(self, entry): self.session.insert(tk.END, "You: " + entry.get() + "\n", "user") response = self.qa(entry.get())["answer"] self.session.insert(tk.END, "Bot: " + response + "\n", "bot") + self.tokenizer.add(entry.get()) + self.tally.delete(1.0, tk.END) + self.tally.insert(tk.END, f"Tokens: {self.tokenizer.total} (${self.tokenizer.cost():.6f})\n") entry.delete(0, tk.END) def go_with_option1(self): @@ -179,4 +186,5 @@ def specify_old_database_directory(self): def clear_session(self): self.session.delete(1.0, tk.END) + self.tally.insert(tk.END, "Tokens: 0 ($0)\n") From b9eab3b4909e0e9fc659e453037d3ddb27f0bd22 Mon Sep 17 00:00:00 2001 From: "Seung-been \"Steven\" Lee" Date: Tue, 13 Jun 2023 14:40:29 +0900 Subject: [PATCH 05/25] Add token counter and price monitor in chat window --- README.md | 1 - kanu/__main__.py | 35 ++++++++++---------- kanu/chatgpt.py | 37 +++++++++++++-------- kanu/docgpt.py | 52 ++++++++++++++++++++--------- kanu/utils.py | 86 ++++++++++++++++++++++++++++++++++-------------- 5 files changed, 138 insertions(+), 73 deletions(-) diff --git a/README.md b/README.md index 04c7516..d70cf4d 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,6 @@ The following packages are required to run ChatGPT: ``` openai # Required. -tiktoken # Required. ``` diff --git a/kanu/__main__.py b/kanu/__main__.py index fd1ad2b..c52e60f 100644 --- a/kanu/__main__.py +++ b/kanu/__main__.py @@ -19,7 +19,7 @@ def __init__(self, root): self.container = None self.root = root self.root.title(f"KANU ({__version__})") - self.root.geometry("600x620") + self.root.geometry("700x620") self.homepage() def homepage(self): @@ -43,45 +43,44 @@ def config_chatgpt(self): l = tk.Label(self.container, text="Required packages:") l.grid(row=1, column=0, columnspan=2) self.display_required_dependency(2, "openai") - self.display_required_dependency(3, "tiktoken") m = tk.Message(self.container, width=300, text="Option 1. Upload a configuration file") - m.grid(row=4, column=0, columnspan=2) + m.grid(row=3, column=0, columnspan=2) b = tk.Button(self.container, text="Browse", command=self.parse_chatgpt_config) - b.grid(row=5, column=0) + b.grid(row=4, column=0) b = tk.Button(self.container, text="Template", command=self.template_chatgpt_config) - b.grid(row=5, column=1) + b.grid(row=4, column=1) m = tk.Message(self.container, width=300, text="Option 2. Configure manually") - m.grid(row=6, column=0, columnspan=2) + m.grid(row=5, column=0, columnspan=2) self.model = tk.StringVar(self.container, value="gpt-3.5-turbo") l = tk.Label(self.container, text="Model:") - l.grid(row=7, column=0, columnspan=2) + l.grid(row=6, column=0, columnspan=2) b = tk.Radiobutton(self.container, variable=self.model, text="gpt-3.5-turbo", value="gpt-3.5-turbo") - b.grid(row=8, column=0) + b.grid(row=7, column=0) b = tk.Radiobutton(self.container, variable=self.model, text="gpt-4", value="gpt-4") - b.grid(row=8, column=1) + b.grid(row=7, column=1) l = tk.Label(self.container, text="System message ⓘ:") Tooltip(l, "The system message helps set the behavior of the chatbot.") - l.grid(row=9, column=0, columnspan=2) + l.grid(row=8, column=0, columnspan=2) self.prompt = tk.Text(self.container, height=9, width=42) sb = tk.Scrollbar(self.container, command=self.prompt.yview) self.prompt.insert("1.0", CHATGPT_PROMPT) - self.prompt.grid(row=10, column=0, columnspan=2, sticky="nsew") - sb.grid(row=10, column=2, sticky="ns") + self.prompt.grid(row=9, column=0, columnspan=2, sticky="nsew") + sb.grid(row=9, column=2, sticky="ns") self.prompt["yscrollcommand"] = sb.set l = tk.Label(self.container, text="Temperature ⓘ:") Tooltip(l, "The randomness in generating responses, which ranges between 0 and 1, with 0 indicating almost deterministic behavior.") - l.grid(row=11, column=0, columnspan=2) + l.grid(row=10, column=0, columnspan=2) self.temperature = tk.DoubleVar(self.container, value=0.5) e = tk.Entry(self.container, textvariable=self.temperature) - e.grid(row=12, column=0, columnspan=2) + e.grid(row=11, column=0, columnspan=2) l = tk.Label(self.container, text="OpenAI API key:") - l.grid(row=13, column=0, columnspan=2) + l.grid(row=12, column=0, columnspan=2) e = tk.Entry(self.container) - e.grid(row=14, column=0, columnspan=2) + e.grid(row=13, column=0, columnspan=2) b = tk.Button(self.container, text="Submit", command=lambda: self.deploy_agent("ChatGPT", e.get(), self.model.get(), self.temperature.get(), self.prompt.get("1.0", "end-1c"))) - b.grid(row=15, column=0) + b.grid(row=14, column=0) b = tk.Button(self.container, text="Go back", command=lambda: self.homepage()) - b.grid(row=15, column=1) + b.grid(row=14, column=1) def parse_chatgpt_config(self): config = configparser.ConfigParser() diff --git a/kanu/chatgpt.py b/kanu/chatgpt.py index 9ec6d99..70d77b0 100644 --- a/kanu/chatgpt.py +++ b/kanu/chatgpt.py @@ -2,7 +2,7 @@ import openai -from .utils import Settings, Tokenizer +from .utils import Settings, tokens2price class ChatGPT: def __init__(self, kanu, openai_key, model, temperature, prompt): @@ -12,7 +12,8 @@ def __init__(self, kanu, openai_key, model, temperature, prompt): self.prompt = prompt openai.api_key = openai_key self.settings = Settings(self) - self.tokenizer = Tokenizer(model) + self.tokens = 0 + self.price = 0 def run(self): self.kanu.container.pack_forget() @@ -20,14 +21,15 @@ def run(self): self.kanu.container.pack() l = tk.Label(self.kanu.container, text="ChatGPT") l.grid(row=0, column=0, columnspan=4) - self.tally = tk.Text(self.kanu.container, width=70, height=1) - self.tally.insert(tk.END, "Tokens: 0 ($0)\n") - self.tally.grid(row=1, column=0, columnspan=4) - self.session = tk.Text(self.kanu.container, width=70, height=20) + self.system = tk.Text(self.kanu.container, width=80, height=7) + self.system.tag_configure("system", **self.settings.get_system_kwargs()) + self.system.insert(tk.END, "System: A new chat session has been created.\n", "system") + self.system.grid(row=1, column=0, columnspan=4) + self.session = tk.Text(self.kanu.container, width=80, height=20) self.session.grid(row=2, 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 = tk.Entry(self.kanu.container, width=62) user_input.grid(row=3, column=0, columnspan=4) self.messages = [] b = tk.Button(self.kanu.container, text="Send", command=lambda: self.send_message(user_input)) @@ -52,13 +54,20 @@ def send_message(self, entry): self.messages += [{"role": "assistant", "content": response}] self.session.insert(tk.END, "You: " + entry.get() + "\n", "user") self.session.insert(tk.END, f"Bot: " + response + "\n", "bot") - self.tokenizer.add(entry.get()) - self.tally.delete(1.0, tk.END) - self.tally.insert(tk.END, f"Tokens: {self.tokenizer.total} (${self.tokenizer.cost():.6f})\n") + usage = self.calculate_usage(bot_response) + self.system.insert(tk.END, f"{usage}\n", "system") entry.delete(0, tk.END) + def calculate_usage(self, response): + total_tokens = response["usage"]["total_tokens"] + prompt_tokens = response["usage"]["prompt_tokens"] + completion_tokens = response["usage"]["completion_tokens"] + prompt_price = tokens2price(prompt_tokens, self.model, "prompt") + completion_price = tokens2price(completion_tokens, self.model, "completion") + self.price += prompt_price + completion_price + self.tokens += total_tokens + message = f"System: Used {prompt_tokens:,} prompt + {completion_tokens:,} completion = {total_tokens:,} tokens (total: {self.tokens:,} or ${self.price:.6f})." + return message + def clear_session(self): - self.session.delete(1.0, tk.END) - self.messages.clear() - self.tally.delete(1.0, tk.END) - self.tally.insert(tk.END, "Tokens: 0 ($0)\n") \ No newline at end of file + self.run() \ No newline at end of file diff --git a/kanu/docgpt.py b/kanu/docgpt.py index 2ae5d84..920129d 100644 --- a/kanu/docgpt.py +++ b/kanu/docgpt.py @@ -9,6 +9,7 @@ from langchain.chains import ConversationalRetrievalChain from langchain.prompts import PromptTemplate from langchain.memory import ConversationBufferMemory +from langchain.callbacks import get_openai_callback from langchain.document_loaders import ( TextLoader, @@ -17,7 +18,7 @@ CSVLoader, ) -from .utils import Tooltip, Settings, Tokenizer +from .utils import Tooltip, Settings, tokens2price, text2tokens DOCUMENT_LOADERS = { ".txt": (TextLoader, {"encoding": "utf8"}), @@ -37,7 +38,8 @@ def __init__(self, kanu, openai_key, model, temperature, prompt, default_chunk_s self.default_chunk_overlap = default_chunk_overlap os.environ["OPENAI_API_KEY"] = openai_key self.settings = Settings(self) - self.tokenizer = Tokenizer(model) + self.tokens = 0 + self.price = 0 def run(self): self.kanu.container.pack_forget() @@ -108,14 +110,19 @@ def query(self): self.kanu.container.pack() l = tk.Label(self.kanu.container, text="DocGPT") l.grid(row=0, column=0, columnspan=4) - self.tally = tk.Text(self.kanu.container, width=70, height=1) - self.tally.insert(tk.END, "Tokens: 0 ($0)\n") - self.tally.grid(row=1, column=0, columnspan=4) - self.session = tk.Text(self.kanu.container, width=70, height=20) + self.system = tk.Text(self.kanu.container, width=80, height=7) + self.system.tag_configure("system", **self.settings.get_system_kwargs()) + if self.existing: + self.system.insert(tk.END, "System: Using existing database. No tokens were used.\n", "system") + else: + self.system.insert(tk.END, f"System: Creating new database. Embedding used {self.tokens:,} tokens or ${self.price:.6f}.\n", "system") + self.system.insert(tk.END, "System: A new chat session has been created.\n", "system") + self.system.grid(row=1, column=0, columnspan=4) + self.session = tk.Text(self.kanu.container, width=80, height=20) self.session.grid(row=2, 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 = tk.Entry(self.kanu.container, width=62) user_input.grid(row=3, column=0, columnspan=4) b = tk.Button(self.kanu.container, text="Send", command=lambda: self.send_message(user_input)) b.grid(row=4, column=0) @@ -128,13 +135,21 @@ def query(self): def send_message(self, entry): self.session.insert(tk.END, "You: " + entry.get() + "\n", "user") - response = self.qa(entry.get())["answer"] - self.session.insert(tk.END, "Bot: " + response + "\n", "bot") - self.tokenizer.add(entry.get()) - self.tally.delete(1.0, tk.END) - self.tally.insert(tk.END, f"Tokens: {self.tokenizer.total} (${self.tokenizer.cost():.6f})\n") + with get_openai_callback() as cb: + response = self.qa(entry.get()) + usage = self.calculate_usage(cb) + self.session.insert(tk.END, "Bot: " + response["answer"] + "\n", "bot") + self.system.insert(tk.END, f"{usage}\n", "system") entry.delete(0, tk.END) + def calculate_usage(self, cb): + prompt_price = tokens2price(cb.prompt_tokens, self.model, "prompt") + completion_price = tokens2price(cb.completion_tokens, self.model, "completion") + self.price += prompt_price + completion_price + self.tokens += cb.total_tokens + message = f"System: Used {cb.prompt_tokens:,} prompt + {cb.completion_tokens:,} completion = {cb.total_tokens:,} tokens (total: {self.tokens:,} or ${self.price:.6f})." + return message + def go_with_option1(self): documents = [] for root, dirs, files in os.walk(self.document_directory): @@ -149,13 +164,18 @@ def go_with_option1(self): 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) + for text in texts: + self.tokens += text2tokens("text-embedding-ada-002", text.page_content) + self.price = tokens2price(self.tokens, "text-embedding-ada-002", "embedding") + db = Chroma.from_documents(texts, OpenAIEmbeddings(model="text-embedding-ada-002"), persist_directory=self.database_directory) db.add_documents(texts) db.persist() db = None + self.existing = False self.query() def go_with_option2(self): + self.existing = True self.query() def specify_document_directory(self): @@ -185,6 +205,8 @@ def specify_old_database_directory(self): self.option2_button["state"] = tk.NORMAL def clear_session(self): - self.session.delete(1.0, tk.END) - self.tally.insert(tk.END, "Tokens: 0 ($0)\n") + self.query() + # self.session.delete(1.0, tk.END) + # self.system.delete(1.0, tk.END) + # self.system.insert(tk.END, "System: A new chat session has been created.\n", "system") diff --git a/kanu/utils.py b/kanu/utils.py index ba34396..c7f5ca1 100644 --- a/kanu/utils.py +++ b/kanu/utils.py @@ -1,31 +1,36 @@ import tkinter as tk from tkinter import font -import tiktoken +# https://openai.com/pricing#language-models +def tokens2price(tokens, model, task): + if model == "gpt-3.5-turbo": + if task in ["prompt", "completion"]: + return 0.002 / 1000 * tokens + else: + raise ValueError(f"Invalid task for {model}: {task}") + elif model == "text-embedding-ada-002": + if task == "embedding": + return 0.0004 / 1000 * tokens + else: + raise ValueError(f"Invalid task for {model}: {task}") + elif model == "gpt-4": + if task == "completion": + return 0.06 / 1000 * tokens + elif task == "prompt": + return 0.03 / 1000 * tokens + else: + raise ValueError(f"Invalid task for {model}: {task}") + else: + raise ValueError(f"Invalid model: {model}") -class Tokenizer: - # https://openai.com/pricing#language-models - PRICES = {"gpt-3.5-turbo": 0.002/1000, "gpt-4": 0.03/1000} - - def __init__(self, model): - self.total = 0 - self.tokens = [] - self.model = model - self.encoding = tiktoken.encoding_for_model(model) - - def add(self, text): - token = len(self.encoding.encode(text)) - self.total += token - self.tokens.append(token) - - def cost(self): - return self.total * self.PRICES[self.model] - - def dollars(self): - return [x * self.PRICES[self.model] for x in self.tokens] +def text2tokens(model, text): + import tiktoken + encoding = tiktoken.encoding_for_model(model) + return len(encoding.encode(text)) class Settings: def __init__(self, agent): + self.agent = agent self.default_font = font.nametofont("TkDefaultFont").actual() self.default_user_background_color = "gray85" self.default_user_foreground_color = "black" @@ -35,7 +40,10 @@ def __init__(self, agent): 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.default_system_background_color = "white" + self.default_system_foreground_color = "black" + self.default_system_font_family = self.default_font["family"] + self.default_system_font_size = self.default_font["size"] 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) @@ -44,6 +52,10 @@ def __init__(self, agent): 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.system_background_color = tk.StringVar(self.agent.kanu.container, value=self.default_system_background_color) + self.system_foreground_color = tk.StringVar(self.agent.kanu.container, value=self.default_system_foreground_color) + self.system_font_family = tk.StringVar(self.agent.kanu.container, value=self.default_system_font_family) + self.system_font_size = tk.IntVar(self.agent.kanu.container, value=self.default_system_font_size) def get_user_kwargs(self): return dict( @@ -59,6 +71,13 @@ def get_bot_kwargs(self): font=(self.bot_font_family.get(), self.bot_font_size.get()) ) + def get_system_kwargs(self): + return dict( + background=self.system_background_color.get(), + foreground=self.system_foreground_color.get(), + font=(self.system_font_family.get(), self.system_font_size.get()) + ) + def page(self): self.agent.previous = self.agent.kanu.container self.agent.kanu.container.pack_forget() @@ -98,12 +117,28 @@ def page(self): 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) + l = tk.Label(self.agent.kanu.container, text="System background color") + l.grid(row=9, column=0) + e = tk.Entry(self.agent.kanu.container, textvariable=self.system_background_color) + e.grid(row=9, column=1, columnspan=2) + l = tk.Label(self.agent.kanu.container, text="System foreground color") + l.grid(row=10, column=0) + e = tk.Entry(self.agent.kanu.container, textvariable=self.system_foreground_color) + e.grid(row=10, column=1, columnspan=2) + l = tk.Label(self.agent.kanu.container, text="System font family") + l.grid(row=11, column=0) + e = tk.Entry(self.agent.kanu.container, textvariable=self.system_font_family) + e.grid(row=11, column=1, columnspan=2) + l = tk.Label(self.agent.kanu.container, text="System font size") + l.grid(row=12, column=0) + e = tk.Entry(self.agent.kanu.container, textvariable=self.system_font_size) + e.grid(row=12, column=1, columnspan=2) b = tk.Button(self.agent.kanu.container, text="Apply", command=lambda: self.apply()) - b.grid(row=9, column=0) + b.grid(row=13, column=0) b = tk.Button(self.agent.kanu.container, text="Reset", command=lambda: self.reset()) - b.grid(row=9, column=1) + b.grid(row=13, column=1) b = tk.Button(self.agent.kanu.container, text="Go back", command=lambda: self.go_back()) - b.grid(row=9, column=2) + b.grid(row=13, column=2) def go_back(self): self.agent.kanu.container.pack_forget() @@ -113,6 +148,7 @@ def go_back(self): 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.system.tag_config("system", **self.get_system_kwargs()) self.agent.kanu.container.pack_forget() self.agent.kanu.container = self.agent.previous self.agent.kanu.container.pack() From c159476fa5298efe7ffa1b5eca1f1f939172d847 Mon Sep 17 00:00:00 2001 From: Seung-been Lee Date: Tue, 13 Jun 2023 18:46:57 +0900 Subject: [PATCH 06/25] Add token counter and price monitor in chat window --- kanu/docgpt.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/kanu/docgpt.py b/kanu/docgpt.py index 920129d..78a7d12 100644 --- a/kanu/docgpt.py +++ b/kanu/docgpt.py @@ -151,6 +151,7 @@ def calculate_usage(self, cb): return message def go_with_option1(self): + self.tokens = self.price = 0 documents = [] for root, dirs, files in os.walk(self.document_directory): for file in files: @@ -175,6 +176,7 @@ def go_with_option1(self): self.query() def go_with_option2(self): + self.tokens = self.price = 0 self.existing = True self.query() @@ -205,8 +207,6 @@ def specify_old_database_directory(self): self.option2_button["state"] = tk.NORMAL def clear_session(self): + self.existing = True + self.tokens = self.price = 0 self.query() - # self.session.delete(1.0, tk.END) - # self.system.delete(1.0, tk.END) - # self.system.insert(tk.END, "System: A new chat session has been created.\n", "system") - From 5bbd51b386e942fa57f10e25f325d06489125b17 Mon Sep 17 00:00:00 2001 From: Seung-been Lee Date: Tue, 13 Jun 2023 18:49:58 +0900 Subject: [PATCH 07/25] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d70cf4d..6404db6 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ $ kanu The following packages are required to run ChatGPT: ``` -openai # Required. +openai # Required. ``` From b0f08a1ed365c612921f2f03dfc745fd64d9b08c Mon Sep 17 00:00:00 2001 From: Seung-been Lee Date: Tue, 13 Jun 2023 19:51:15 +0900 Subject: [PATCH 08/25] Update chatgpt.py --- kanu/chatgpt.py | 1 + 1 file changed, 1 insertion(+) diff --git a/kanu/chatgpt.py b/kanu/chatgpt.py index 70d77b0..a507992 100644 --- a/kanu/chatgpt.py +++ b/kanu/chatgpt.py @@ -70,4 +70,5 @@ def calculate_usage(self, response): return message def clear_session(self): + self.tokens = self.price = 0 self.run() \ No newline at end of file From d7a4b606aaaa3195d63b7c82f87f4ef7c66c6332 Mon Sep 17 00:00:00 2001 From: "Seung-been \"Steven\" Lee" Date: Wed, 14 Jun 2023 10:36:32 +0900 Subject: [PATCH 09/25] Enable Enter key for chat message sending --- CHANGELOG.md | 1 + kanu/chatgpt.py | 17 ++++++++++------- kanu/docgpt.py | 19 ++++++++++++------- 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b4758e..0afb2b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## 0.7.0 (in development) * Implement token counter and price monitor in chat window. +* Enable the Enter key for sending messages in chat window. ## 0.6.0 (2023-06-12) * Enable users to chat with .csv documents. diff --git a/kanu/chatgpt.py b/kanu/chatgpt.py index a507992..b47e8a0 100644 --- a/kanu/chatgpt.py +++ b/kanu/chatgpt.py @@ -19,6 +19,8 @@ def run(self): self.kanu.container.pack_forget() self.kanu.container = tk.Frame(self.kanu.root) self.kanu.container.pack() + self.kanu.container.bind_all("", lambda event: self.send_message()) + self.kanu.container.focus_set() l = tk.Label(self.kanu.container, text="ChatGPT") l.grid(row=0, column=0, columnspan=4) self.system = tk.Text(self.kanu.container, width=80, height=7) @@ -29,10 +31,11 @@ def run(self): self.session.grid(row=2, 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=62) - user_input.grid(row=3, column=0, columnspan=4) + self.user_input = tk.StringVar() + self.chatbox = tk.Entry(self.kanu.container, width=62, textvariable=self.user_input) + self.chatbox.grid(row=3, column=0, columnspan=4) self.messages = [] - b = tk.Button(self.kanu.container, text="Send", command=lambda: self.send_message(user_input)) + b = tk.Button(self.kanu.container, text="Send", command=lambda: self.send_message()) b.grid(row=4, column=0) b = tk.Button(self.kanu.container, text="Clear", command=lambda: self.clear_session()) b.grid(row=4, column=1) @@ -41,10 +44,10 @@ def run(self): b = tk.Button(self.kanu.container, text="Settings", command=lambda: self.settings.page()) b.grid(row=4, column=3) - def send_message(self, entry): + def send_message(self): if not self.messages: self.messages.append({"role": "system", "content": self.prompt}) - self.messages += [{"role": "user", "content": entry.get()}] + self.messages += [{"role": "user", "content": self.user_input.get()}] bot_response = openai.ChatCompletion.create( model=self.model, messages=self.messages, @@ -52,11 +55,11 @@ 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", "user") + self.session.insert(tk.END, "You: " + self.user_input.get() + "\n", "user") self.session.insert(tk.END, f"Bot: " + response + "\n", "bot") usage = self.calculate_usage(bot_response) self.system.insert(tk.END, f"{usage}\n", "system") - entry.delete(0, tk.END) + self.chatbox.delete(0, tk.END) def calculate_usage(self, response): total_tokens = response["usage"]["total_tokens"] diff --git a/kanu/docgpt.py b/kanu/docgpt.py index 78a7d12..9026ea2 100644 --- a/kanu/docgpt.py +++ b/kanu/docgpt.py @@ -45,6 +45,8 @@ def run(self): self.kanu.container.pack_forget() self.kanu.container = tk.Frame(self.kanu.root) self.kanu.container.pack() + self.kanu.container.bind_all("", lambda event: self.send_message()) + self.kanu.container.focus_set() l = tk.Label(self.kanu.container, text="DocGPT") l.grid(row=0, column=0, columnspan=3) b = tk.Button(self.kanu.container, text="Go back", command=lambda: self.kanu.config_docgpt()) @@ -122,9 +124,12 @@ def query(self): self.session.grid(row=2, 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=62) - user_input.grid(row=3, column=0, columnspan=4) - b = tk.Button(self.kanu.container, text="Send", command=lambda: self.send_message(user_input)) + + self.user_input = tk.StringVar() + self.chatbox = tk.Entry(self.kanu.container, width=62, textvariable=self.user_input) + self.chatbox.grid(row=3, column=0, columnspan=4) + + b = tk.Button(self.kanu.container, text="Send", command=lambda: self.send_message()) b.grid(row=4, column=0) b = tk.Button(self.kanu.container, text="Clear", command=lambda: self.clear_session()) b.grid(row=4, column=1) @@ -133,14 +138,14 @@ def query(self): b = tk.Button(self.kanu.container, text="Settings", command=lambda: self.settings.page()) b.grid(row=4, column=3) - def send_message(self, entry): - self.session.insert(tk.END, "You: " + entry.get() + "\n", "user") + def send_message(self): + self.session.insert(tk.END, "You: " + self.user_input.get() + "\n", "user") with get_openai_callback() as cb: - response = self.qa(entry.get()) + response = self.qa(self.user_input.get()) usage = self.calculate_usage(cb) self.session.insert(tk.END, "Bot: " + response["answer"] + "\n", "bot") self.system.insert(tk.END, f"{usage}\n", "system") - entry.delete(0, tk.END) + self.chatbox.delete(0, tk.END) def calculate_usage(self, cb): prompt_price = tokens2price(cb.prompt_tokens, self.model, "prompt") From 64900aea479b9f9efb4e632b35c910b384634ddd Mon Sep 17 00:00:00 2001 From: "Seung-been \"Steven\" Lee" Date: Thu, 15 Jun 2023 09:13:57 +0900 Subject: [PATCH 10/25] Update GPT prices --- kanu/utils.py | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/kanu/utils.py b/kanu/utils.py index c7f5ca1..f7cbfda 100644 --- a/kanu/utils.py +++ b/kanu/utils.py @@ -4,22 +4,40 @@ # https://openai.com/pricing#language-models def tokens2price(tokens, model, task): if model == "gpt-3.5-turbo": - if task in ["prompt", "completion"]: - return 0.002 / 1000 * tokens - else: + if task not in ["prompt", "completion"]: raise ValueError(f"Invalid task for {model}: {task}") + if tokens <= 4096: + if task == "completion": + return 0.002 / 1000 * tokens + else: + return 0.0015 / 1000 * tokens + elif tokens <= 16384: + if task == "completion": + return 0.004 / 1000 * tokens + else: + return 0.003 / 1000 * tokens + else: + raise ValueError(f"Tokens too large for {model}: {tokens}") elif model == "text-embedding-ada-002": if task == "embedding": return 0.0004 / 1000 * tokens else: raise ValueError(f"Invalid task for {model}: {task}") elif model == "gpt-4": - if task == "completion": - return 0.06 / 1000 * tokens - elif task == "prompt": - return 0.03 / 1000 * tokens - else: + if task not in ["prompt", "completion"]: raise ValueError(f"Invalid task for {model}: {task}") + if tokens <= 8192: + if task == "completion": + return 0.06 / 1000 * tokens + else: + return 0.03 / 1000 * tokens + elif tokens <= 32768: + if task == "completion": + return 0.12 / 1000 * tokens + else: + return 0.06 / 1000 * tokens + else: + raise ValueError(f"Tokens too large for {model}: {tokens}") else: raise ValueError(f"Invalid model: {model}") From 1fac4a49a8140bc594b2fe79d6fd937139a61c4f Mon Sep 17 00:00:00 2001 From: "Seung-been \"Steven\" Lee" Date: Thu, 15 Jun 2023 09:17:52 +0900 Subject: [PATCH 11/25] Update GPT prices --- kanu/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kanu/utils.py b/kanu/utils.py index f7cbfda..475cd73 100644 --- a/kanu/utils.py +++ b/kanu/utils.py @@ -20,7 +20,7 @@ def tokens2price(tokens, model, task): raise ValueError(f"Tokens too large for {model}: {tokens}") elif model == "text-embedding-ada-002": if task == "embedding": - return 0.0004 / 1000 * tokens + return 0.0001 / 1000 * tokens else: raise ValueError(f"Invalid task for {model}: {task}") elif model == "gpt-4": From baabfb63b810acd7eec5735a515cdd0b872211b8 Mon Sep 17 00:00:00 2001 From: "Seung-been \"Steven\" Lee" Date: Thu, 15 Jun 2023 15:35:01 +0900 Subject: [PATCH 12/25] Add gui.py --- kanu/__main__.py | 2 +- kanu/chatgpt.py | 3 +- kanu/docgpt.py | 5 +- kanu/gui.py | 161 +++++++++++++++++++++++++++++++++++++++++++++++ kanu/utils.py | 161 +---------------------------------------------- 5 files changed, 168 insertions(+), 164 deletions(-) create mode 100644 kanu/gui.py diff --git a/kanu/__main__.py b/kanu/__main__.py index c52e60f..1c8ec2b 100644 --- a/kanu/__main__.py +++ b/kanu/__main__.py @@ -4,7 +4,7 @@ import importlib.util from .version import __version__ -from .utils import Tooltip +from .gui import Tooltip CHATGPT_PROMPT = """You are a helpful assistant.""" DOCGPT_PROMPT = """Use the following pieces of context to answer the question at the end. If you don't know the answer, just say that you don't know, don't try to make up an answer. diff --git a/kanu/chatgpt.py b/kanu/chatgpt.py index b47e8a0..46cb085 100644 --- a/kanu/chatgpt.py +++ b/kanu/chatgpt.py @@ -2,7 +2,8 @@ import openai -from .utils import Settings, tokens2price +from .gui import Settings +from .utils import tokens2price class ChatGPT: def __init__(self, kanu, openai_key, model, temperature, prompt): diff --git a/kanu/docgpt.py b/kanu/docgpt.py index 9026ea2..72a5a32 100644 --- a/kanu/docgpt.py +++ b/kanu/docgpt.py @@ -18,7 +18,8 @@ CSVLoader, ) -from .utils import Tooltip, Settings, tokens2price, text2tokens +from .gui import Tooltip, Settings +from .utils import tokens2price, text2tokens DOCUMENT_LOADERS = { ".txt": (TextLoader, {"encoding": "utf8"}), @@ -115,7 +116,7 @@ def query(self): self.system = tk.Text(self.kanu.container, width=80, height=7) self.system.tag_configure("system", **self.settings.get_system_kwargs()) if self.existing: - self.system.insert(tk.END, "System: Using existing database. No tokens were used.\n", "system") + self.system.insert(tk.END, "System: Using existing database. Embedding was skipped and no tokens were used.\n", "system") else: self.system.insert(tk.END, f"System: Creating new database. Embedding used {self.tokens:,} tokens or ${self.price:.6f}.\n", "system") self.system.insert(tk.END, "System: A new chat session has been created.\n", "system") diff --git a/kanu/gui.py b/kanu/gui.py new file mode 100644 index 0000000..be33147 --- /dev/null +++ b/kanu/gui.py @@ -0,0 +1,161 @@ +import tkinter as tk +from tkinter import font + +class Settings: + def __init__(self, agent): + self.agent = 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.default_system_background_color = "white" + self.default_system_foreground_color = "black" + self.default_system_font_family = self.default_font["family"] + self.default_system_font_size = self.default_font["size"] + 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.system_background_color = tk.StringVar(self.agent.kanu.container, value=self.default_system_background_color) + self.system_foreground_color = tk.StringVar(self.agent.kanu.container, value=self.default_system_foreground_color) + self.system_font_family = tk.StringVar(self.agent.kanu.container, value=self.default_system_font_family) + self.system_font_size = tk.IntVar(self.agent.kanu.container, value=self.default_system_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 get_system_kwargs(self): + return dict( + background=self.system_background_color.get(), + foreground=self.system_foreground_color.get(), + font=(self.system_font_family.get(), self.system_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) + l = tk.Label(self.agent.kanu.container, text="System background color") + l.grid(row=9, column=0) + e = tk.Entry(self.agent.kanu.container, textvariable=self.system_background_color) + e.grid(row=9, column=1, columnspan=2) + l = tk.Label(self.agent.kanu.container, text="System foreground color") + l.grid(row=10, column=0) + e = tk.Entry(self.agent.kanu.container, textvariable=self.system_foreground_color) + e.grid(row=10, column=1, columnspan=2) + l = tk.Label(self.agent.kanu.container, text="System font family") + l.grid(row=11, column=0) + e = tk.Entry(self.agent.kanu.container, textvariable=self.system_font_family) + e.grid(row=11, column=1, columnspan=2) + l = tk.Label(self.agent.kanu.container, text="System font size") + l.grid(row=12, column=0) + e = tk.Entry(self.agent.kanu.container, textvariable=self.system_font_size) + e.grid(row=12, column=1, columnspan=2) + b = tk.Button(self.agent.kanu.container, text="Apply", command=lambda: self.apply()) + b.grid(row=13, column=0) + b = tk.Button(self.agent.kanu.container, text="Reset", command=lambda: self.reset()) + b.grid(row=13, column=1) + b = tk.Button(self.agent.kanu.container, text="Go back", command=lambda: self.go_back()) + b.grid(row=13, 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.system.tag_config("system", **self.get_system_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): + self.widget = widget + self.text = text + self.tooltip = None + self.widget.bind("", self.show_tooltip) + self.widget.bind("", self.hide_tooltip) + + def show_tooltip(self, event): + x, y, _, _ = self.widget.bbox("insert") + x += self.widget.winfo_rootx() + 25 + y += self.widget.winfo_rooty() + 25 + self.tooltip = tk.Toplevel(self.widget) + self.tooltip.wm_overrideredirect(True) + self.tooltip.wm_geometry(f"+{x}+{y}") + l = tk.Label(self.tooltip, text=self.text, background="#ffffe0", relief="solid", borderwidth=1, wraplength=400) + l.pack() + + def hide_tooltip(self, event): + if self.tooltip: + self.tooltip.destroy() + self.tooltip = None \ No newline at end of file diff --git a/kanu/utils.py b/kanu/utils.py index 475cd73..b77d684 100644 --- a/kanu/utils.py +++ b/kanu/utils.py @@ -44,163 +44,4 @@ def tokens2price(tokens, model, task): def text2tokens(model, text): import tiktoken encoding = tiktoken.encoding_for_model(model) - return len(encoding.encode(text)) - -class Settings: - def __init__(self, agent): - self.agent = 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.default_system_background_color = "white" - self.default_system_foreground_color = "black" - self.default_system_font_family = self.default_font["family"] - self.default_system_font_size = self.default_font["size"] - 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.system_background_color = tk.StringVar(self.agent.kanu.container, value=self.default_system_background_color) - self.system_foreground_color = tk.StringVar(self.agent.kanu.container, value=self.default_system_foreground_color) - self.system_font_family = tk.StringVar(self.agent.kanu.container, value=self.default_system_font_family) - self.system_font_size = tk.IntVar(self.agent.kanu.container, value=self.default_system_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 get_system_kwargs(self): - return dict( - background=self.system_background_color.get(), - foreground=self.system_foreground_color.get(), - font=(self.system_font_family.get(), self.system_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) - l = tk.Label(self.agent.kanu.container, text="System background color") - l.grid(row=9, column=0) - e = tk.Entry(self.agent.kanu.container, textvariable=self.system_background_color) - e.grid(row=9, column=1, columnspan=2) - l = tk.Label(self.agent.kanu.container, text="System foreground color") - l.grid(row=10, column=0) - e = tk.Entry(self.agent.kanu.container, textvariable=self.system_foreground_color) - e.grid(row=10, column=1, columnspan=2) - l = tk.Label(self.agent.kanu.container, text="System font family") - l.grid(row=11, column=0) - e = tk.Entry(self.agent.kanu.container, textvariable=self.system_font_family) - e.grid(row=11, column=1, columnspan=2) - l = tk.Label(self.agent.kanu.container, text="System font size") - l.grid(row=12, column=0) - e = tk.Entry(self.agent.kanu.container, textvariable=self.system_font_size) - e.grid(row=12, column=1, columnspan=2) - b = tk.Button(self.agent.kanu.container, text="Apply", command=lambda: self.apply()) - b.grid(row=13, column=0) - b = tk.Button(self.agent.kanu.container, text="Reset", command=lambda: self.reset()) - b.grid(row=13, column=1) - b = tk.Button(self.agent.kanu.container, text="Go back", command=lambda: self.go_back()) - b.grid(row=13, 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.system.tag_config("system", **self.get_system_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): - self.widget = widget - self.text = text - self.tooltip = None - self.widget.bind("", self.show_tooltip) - self.widget.bind("", self.hide_tooltip) - - def show_tooltip(self, event): - x, y, _, _ = self.widget.bbox("insert") - x += self.widget.winfo_rootx() + 25 - y += self.widget.winfo_rooty() + 25 - self.tooltip = tk.Toplevel(self.widget) - self.tooltip.wm_overrideredirect(True) - self.tooltip.wm_geometry(f"+{x}+{y}") - l = tk.Label(self.tooltip, text=self.text, background="#ffffe0", relief="solid", borderwidth=1, wraplength=400) - l.pack() - - def hide_tooltip(self, event): - if self.tooltip: - self.tooltip.destroy() - self.tooltip = None \ No newline at end of file + return len(encoding.encode(text)) \ No newline at end of file From 6627b1fae533ccf32703762d19bbe25580751171 Mon Sep 17 00:00:00 2001 From: "Seung-been \"Steven\" Lee" Date: Thu, 15 Jun 2023 15:46:48 +0900 Subject: [PATCH 13/25] Update README.md --- README.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/README.md b/README.md index 6404db6..c8b1fab 100644 --- a/README.md +++ b/README.md @@ -50,12 +50,7 @@ openai # Required. ![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 +DocGPT currently supports the following document formats: `.csv`, `.doc`, `.docx`, `.pdf`, and `.txt`. The following packages are required to run DocGPT: From 4dad76308fa16b8b7f854c1ef48a2cdd4d82cbe2 Mon Sep 17 00:00:00 2001 From: "Seung-been \"Steven\" Lee" Date: Thu, 15 Jun 2023 16:02:30 +0900 Subject: [PATCH 14/25] Update docgpt.py --- kanu/docgpt.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/kanu/docgpt.py b/kanu/docgpt.py index 72a5a32..f93e87e 100644 --- a/kanu/docgpt.py +++ b/kanu/docgpt.py @@ -10,7 +10,6 @@ from langchain.prompts import PromptTemplate from langchain.memory import ConversationBufferMemory from langchain.callbacks import get_openai_callback - from langchain.document_loaders import ( TextLoader, PDFMinerLoader, @@ -30,7 +29,16 @@ } class DocGPT: - def __init__(self, kanu, openai_key, model, temperature, prompt, default_chunk_size, default_chunk_overlap): + def __init__( + self, + kanu, + openai_key, + model, + temperature, + prompt, + default_chunk_size, + default_chunk_overlap + ): self.kanu = kanu self.model = model self.temperature = temperature From 636518522169a48a220bf7a57f9b26d6e3505418 Mon Sep 17 00:00:00 2001 From: Seung-been Lee Date: Fri, 16 Jun 2023 20:48:09 +0900 Subject: [PATCH 15/25] Auto-resizing chat session based on window size --- CHANGELOG.md | 1 + kanu/chatgpt.py | 38 ++++++++++++++++++++++---------------- kanu/docgpt.py | 38 +++++++++++++++++++++----------------- kanu/gui.py | 2 +- 4 files changed, 45 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0afb2b7..a4453c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## 0.7.0 (in development) * Implement token counter and price monitor in chat window. * Enable the Enter key for sending messages in chat window. +* Enable automatic chat session resizing based on window size. ## 0.6.0 (2023-06-12) * Enable users to chat with .csv documents. diff --git a/kanu/chatgpt.py b/kanu/chatgpt.py index 46cb085..2d21559 100644 --- a/kanu/chatgpt.py +++ b/kanu/chatgpt.py @@ -19,31 +19,37 @@ def __init__(self, kanu, openai_key, model, temperature, prompt): def run(self): self.kanu.container.pack_forget() self.kanu.container = tk.Frame(self.kanu.root) - self.kanu.container.pack() + self.kanu.container.pack(fill="both", expand=True) self.kanu.container.bind_all("", lambda event: self.send_message()) self.kanu.container.focus_set() l = tk.Label(self.kanu.container, text="ChatGPT") - l.grid(row=0, column=0, columnspan=4) - self.system = tk.Text(self.kanu.container, width=80, height=7) + l.grid(row=0, column=0, columnspan=4, sticky="ew") + self.system = tk.Text(self.kanu.container, height=7) self.system.tag_configure("system", **self.settings.get_system_kwargs()) self.system.insert(tk.END, "System: A new chat session has been created.\n", "system") - self.system.grid(row=1, column=0, columnspan=4) - self.session = tk.Text(self.kanu.container, width=80, height=20) - self.session.grid(row=2, column=0, columnspan=4) + self.system.grid(row=1, column=0, columnspan=4, sticky="ew") + self.session = tk.Text(self.kanu.container) + self.session.grid(row=2, column=0, columnspan=4, sticky="nsew") self.session.tag_config("user", **self.settings.get_user_kwargs()) self.session.tag_config("bot", **self.settings.get_bot_kwargs()) self.user_input = tk.StringVar() - self.chatbox = tk.Entry(self.kanu.container, width=62, textvariable=self.user_input) - self.chatbox.grid(row=3, column=0, columnspan=4) + self.chatbox = tk.Entry(self.kanu.container, textvariable=self.user_input) + self.chatbox.grid(row=3, column=0, columnspan=4, sticky="ew") self.messages = [] - b = tk.Button(self.kanu.container, text="Send", command=lambda: self.send_message()) - b.grid(row=4, column=0) - b = tk.Button(self.kanu.container, text="Clear", command=lambda: self.clear_session()) - b.grid(row=4, column=1) - b = tk.Button(self.kanu.container, text="Go back", command=lambda: self.kanu.config_chatgpt()) - b.grid(row=4, column=2) - b = tk.Button(self.kanu.container, text="Settings", command=lambda: self.settings.page()) - b.grid(row=4, column=3) + button_frame = tk.Frame(self.kanu.container) + button_frame.grid(row=4, column=0, sticky="ew") + b = tk.Button(button_frame, text="Send", command=lambda: self.send_message()) + b.grid(row=0, column=0, sticky="ew") + b = tk.Button(button_frame, text="Clear", command=lambda: self.clear_session()) + b.grid(row=0, column=1, sticky="ew") + b = tk.Button(button_frame, text="Go back", command=lambda: self.kanu.config_chatgpt()) + b.grid(row=0, column=2, sticky="ew") + b = tk.Button(button_frame, text="Settings", command=lambda: self.settings.page()) + b.grid(row=0, column=3, sticky="ew") + self.kanu.container.grid_columnconfigure(0, weight=1) + self.kanu.container.grid_rowconfigure(2, weight=1) + for i in range(4): + button_frame.grid_columnconfigure(i, weight=1) def send_message(self): if not self.messages: diff --git a/kanu/docgpt.py b/kanu/docgpt.py index f93e87e..01cfebe 100644 --- a/kanu/docgpt.py +++ b/kanu/docgpt.py @@ -118,34 +118,38 @@ def query(self): ) self.kanu.container.pack_forget() self.kanu.container = tk.Frame(self.kanu.root) - self.kanu.container.pack() + self.kanu.container.pack(fill="both", expand=True) l = tk.Label(self.kanu.container, text="DocGPT") - l.grid(row=0, column=0, columnspan=4) - self.system = tk.Text(self.kanu.container, width=80, height=7) + l.grid(row=0, column=0, columnspan=4, sticky="ew") + self.system = tk.Text(self.kanu.container, height=7) self.system.tag_configure("system", **self.settings.get_system_kwargs()) if self.existing: self.system.insert(tk.END, "System: Using existing database. Embedding was skipped and no tokens were used.\n", "system") else: self.system.insert(tk.END, f"System: Creating new database. Embedding used {self.tokens:,} tokens or ${self.price:.6f}.\n", "system") self.system.insert(tk.END, "System: A new chat session has been created.\n", "system") - self.system.grid(row=1, column=0, columnspan=4) - self.session = tk.Text(self.kanu.container, width=80, height=20) - self.session.grid(row=2, column=0, columnspan=4) + self.system.grid(row=1, column=0, columnspan=4, sticky="ew") + self.session = tk.Text(self.kanu.container) + self.session.grid(row=2, column=0, columnspan=4, sticky="nsew") self.session.tag_config("user", **self.settings.get_user_kwargs()) self.session.tag_config("bot", **self.settings.get_bot_kwargs()) - self.user_input = tk.StringVar() self.chatbox = tk.Entry(self.kanu.container, width=62, textvariable=self.user_input) - self.chatbox.grid(row=3, column=0, columnspan=4) - - b = tk.Button(self.kanu.container, text="Send", command=lambda: self.send_message()) - b.grid(row=4, column=0) - b = tk.Button(self.kanu.container, text="Clear", command=lambda: self.clear_session()) - b.grid(row=4, column=1) - b = tk.Button(self.kanu.container, text="Go back", command=lambda: self.run()) - b.grid(row=4, column=2) - b = tk.Button(self.kanu.container, text="Settings", command=lambda: self.settings.page()) - b.grid(row=4, column=3) + self.chatbox.grid(row=3, column=0, columnspan=4, sticky="ew") + button_frame = tk.Frame(self.kanu.container) + button_frame.grid(row=4, column=0, sticky="ew") + b = tk.Button(button_frame, text="Send", command=lambda: self.send_message()) + b.grid(row=0, column=0, sticky="ew") + b = tk.Button(button_frame, text="Clear", command=lambda: self.clear_session()) + b.grid(row=0, column=1, sticky="ew") + b = tk.Button(button_frame, text="Go back", command=lambda: self.kanu.config_chatgpt()) + b.grid(row=0, column=2, sticky="ew") + b = tk.Button(button_frame, text="Settings", command=lambda: self.settings.page()) + b.grid(row=0, column=3, sticky="ew") + self.kanu.container.grid_columnconfigure(0, weight=1) + self.kanu.container.grid_rowconfigure(2, weight=1) + for i in range(4): + button_frame.grid_columnconfigure(i, weight=1) def send_message(self): self.session.insert(tk.END, "You: " + self.user_input.get() + "\n", "user") diff --git a/kanu/gui.py b/kanu/gui.py index be33147..8bde684 100644 --- a/kanu/gui.py +++ b/kanu/gui.py @@ -116,7 +116,7 @@ def page(self): def go_back(self): self.agent.kanu.container.pack_forget() self.agent.kanu.container = self.agent.previous - self.agent.kanu.container.pack() + self.agent.kanu.container.pack(fill="both", expand=True) def apply(self): self.agent.session.tag_config("user", **self.get_user_kwargs()) From 2823e5830329a154cbfa11f4c96ab07a6b1f34e1 Mon Sep 17 00:00:00 2001 From: Seung-been Lee Date: Fri, 16 Jun 2023 21:21:50 +0900 Subject: [PATCH 16/25] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c8b1fab..d89db61 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,8 @@ There are currently two chatbots available in KANU: Other features of KANU inclde: -- 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 +- Customize chat settings (e.g. font size and background color) - Display token counter and price monitor in chat window ## Installation From 611d5ea588c095cfe530b4c117946afe61a89bbf Mon Sep 17 00:00:00 2001 From: Seung-been Lee Date: Fri, 16 Jun 2023 21:27:01 +0900 Subject: [PATCH 17/25] Auto-resizing chat session based on window size --- kanu/gui.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/kanu/gui.py b/kanu/gui.py index 8bde684..f61fe23 100644 --- a/kanu/gui.py +++ b/kanu/gui.py @@ -124,7 +124,7 @@ def apply(self): self.agent.system.tag_config("system", **self.get_system_kwargs()) self.agent.kanu.container.pack_forget() self.agent.kanu.container = self.agent.previous - self.agent.kanu.container.pack() + self.agent.kanu.container.pack(fill="both", expand=True) def reset(self): self.user_background_color = tk.StringVar(self.agent.kanu.container, value=self.default_user_background_color) @@ -135,6 +135,10 @@ def reset(self): 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.system_background_color = tk.StringVar(self.agent.kanu.container, value=self.default_system_background_color) + self.system_foreground_color = tk.StringVar(self.agent.kanu.container, value=self.default_system_foreground_color) + self.system_font_family = tk.StringVar(self.agent.kanu.container, value=self.default_system_font_family) + self.system_font_size = tk.IntVar(self.agent.kanu.container, value=self.default_system_font_size) self.apply() class Tooltip: From 06fa55a13469bffe3faf5999bf0542470f397034 Mon Sep 17 00:00:00 2001 From: Seung-been Lee Date: Fri, 16 Jun 2023 22:48:11 +0900 Subject: [PATCH 18/25] Add new GPT models --- kanu/__main__.py | 16 +++++++--------- kanu/chatgpt.py | 6 +++--- kanu/docgpt.py | 8 ++++---- kanu/utils.py | 47 +++++++++-------------------------------------- 4 files changed, 23 insertions(+), 54 deletions(-) diff --git a/kanu/__main__.py b/kanu/__main__.py index 1c8ec2b..42c5e36 100644 --- a/kanu/__main__.py +++ b/kanu/__main__.py @@ -1,11 +1,13 @@ import configparser import tkinter as tk +from tkinter import ttk from tkinter import filedialog import importlib.util from .version import __version__ from .gui import Tooltip +GPT_MODELS = ["gpt-3.5-turbo", "gpt-3.5-turbo-16k", "gpt-4", "gpt-4-32k"] CHATGPT_PROMPT = """You are a helpful assistant.""" DOCGPT_PROMPT = """Use the following pieces of context to answer the question at the end. If you don't know the answer, just say that you don't know, don't try to make up an answer. @@ -51,13 +53,11 @@ def config_chatgpt(self): b.grid(row=4, column=1) m = tk.Message(self.container, width=300, text="Option 2. Configure manually") m.grid(row=5, column=0, columnspan=2) - self.model = tk.StringVar(self.container, value="gpt-3.5-turbo") l = tk.Label(self.container, text="Model:") l.grid(row=6, column=0, columnspan=2) - b = tk.Radiobutton(self.container, variable=self.model, text="gpt-3.5-turbo", value="gpt-3.5-turbo") - b.grid(row=7, column=0) - b = tk.Radiobutton(self.container, variable=self.model, text="gpt-4", value="gpt-4") - b.grid(row=7, column=1) + self.model = tk.StringVar(self.container, value="gpt-3.5-turbo") + om = ttk.OptionMenu(self.container, self.model, *GPT_MODELS) + om.grid(row=7, column=0, columnspan=2) l = tk.Label(self.container, text="System message ⓘ:") Tooltip(l, "The system message helps set the behavior of the chatbot.") l.grid(row=8, column=0, columnspan=2) @@ -127,10 +127,8 @@ def config_docgpt(self): self.model = tk.StringVar(self.container, value="gpt-3.5-turbo") l = tk.Label(self.container, text="Model:") l.grid(row=12, column=0, columnspan=2) - rb = tk.Radiobutton(self.container, variable=self.model, text="gpt-3.5-turbo", value="gpt-3.5-turbo") - rb.grid(row=13, column=0) - rb = tk.Radiobutton(self.container, variable=self.model, text="gpt-4", value="gpt-4") - rb.grid(row=13, column=1) + om = ttk.OptionMenu(self.container, self.model, *GPT_MODELS) + om.grid(row=13, column=0, columnspan=2) l = tk.Label(self.container, text="System message ⓘ:") Tooltip(l, "The system message helps set the behavior of the chatbot.") l.grid(row=14, column=0, columnspan=2) diff --git a/kanu/chatgpt.py b/kanu/chatgpt.py index 2d21559..f599614 100644 --- a/kanu/chatgpt.py +++ b/kanu/chatgpt.py @@ -26,7 +26,7 @@ def run(self): l.grid(row=0, column=0, columnspan=4, sticky="ew") self.system = tk.Text(self.kanu.container, height=7) self.system.tag_configure("system", **self.settings.get_system_kwargs()) - self.system.insert(tk.END, "System: A new chat session has been created.\n", "system") + self.system.insert(tk.END, f"System: A new chat session has been created using {self.model}.\n", "system") self.system.grid(row=1, column=0, columnspan=4, sticky="ew") self.session = tk.Text(self.kanu.container) self.session.grid(row=2, column=0, columnspan=4, sticky="nsew") @@ -72,8 +72,8 @@ def calculate_usage(self, response): total_tokens = response["usage"]["total_tokens"] prompt_tokens = response["usage"]["prompt_tokens"] completion_tokens = response["usage"]["completion_tokens"] - prompt_price = tokens2price(prompt_tokens, self.model, "prompt") - completion_price = tokens2price(completion_tokens, self.model, "completion") + prompt_price = tokens2price(self.model, "prompt", prompt_tokens) + completion_price = tokens2price(self.model, "completion", completion_tokens) self.price += prompt_price + completion_price self.tokens += total_tokens message = f"System: Used {prompt_tokens:,} prompt + {completion_tokens:,} completion = {total_tokens:,} tokens (total: {self.tokens:,} or ${self.price:.6f})." diff --git a/kanu/docgpt.py b/kanu/docgpt.py index 01cfebe..ca61b2b 100644 --- a/kanu/docgpt.py +++ b/kanu/docgpt.py @@ -127,7 +127,7 @@ def query(self): self.system.insert(tk.END, "System: Using existing database. Embedding was skipped and no tokens were used.\n", "system") else: self.system.insert(tk.END, f"System: Creating new database. Embedding used {self.tokens:,} tokens or ${self.price:.6f}.\n", "system") - self.system.insert(tk.END, "System: A new chat session has been created.\n", "system") + self.system.insert(tk.END, f"System: A new chat session has been created using {self.model}.\n", "system") self.system.grid(row=1, column=0, columnspan=4, sticky="ew") self.session = tk.Text(self.kanu.container) self.session.grid(row=2, column=0, columnspan=4, sticky="nsew") @@ -161,8 +161,8 @@ def send_message(self): self.chatbox.delete(0, tk.END) def calculate_usage(self, cb): - prompt_price = tokens2price(cb.prompt_tokens, self.model, "prompt") - completion_price = tokens2price(cb.completion_tokens, self.model, "completion") + prompt_price = tokens2price(self.model, "prompt", cb.prompt_tokens) + completion_price = tokens2price(self.model, "completion", cb.completion_tokens) self.price += prompt_price + completion_price self.tokens += cb.total_tokens message = f"System: Used {cb.prompt_tokens:,} prompt + {cb.completion_tokens:,} completion = {cb.total_tokens:,} tokens (total: {self.tokens:,} or ${self.price:.6f})." @@ -185,7 +185,7 @@ def go_with_option1(self): texts = text_splitter.split_documents(documents) for text in texts: self.tokens += text2tokens("text-embedding-ada-002", text.page_content) - self.price = tokens2price(self.tokens, "text-embedding-ada-002", "embedding") + self.price = tokens2price("text-embedding-ada-002", "embedding", self.tokens) db = Chroma.from_documents(texts, OpenAIEmbeddings(model="text-embedding-ada-002"), persist_directory=self.database_directory) db.add_documents(texts) db.persist() diff --git a/kanu/utils.py b/kanu/utils.py index b77d684..20bc16d 100644 --- a/kanu/utils.py +++ b/kanu/utils.py @@ -2,44 +2,15 @@ from tkinter import font # https://openai.com/pricing#language-models -def tokens2price(tokens, model, task): - if model == "gpt-3.5-turbo": - if task not in ["prompt", "completion"]: - raise ValueError(f"Invalid task for {model}: {task}") - if tokens <= 4096: - if task == "completion": - return 0.002 / 1000 * tokens - else: - return 0.0015 / 1000 * tokens - elif tokens <= 16384: - if task == "completion": - return 0.004 / 1000 * tokens - else: - return 0.003 / 1000 * tokens - else: - raise ValueError(f"Tokens too large for {model}: {tokens}") - elif model == "text-embedding-ada-002": - if task == "embedding": - return 0.0001 / 1000 * tokens - else: - raise ValueError(f"Invalid task for {model}: {task}") - elif model == "gpt-4": - if task not in ["prompt", "completion"]: - raise ValueError(f"Invalid task for {model}: {task}") - if tokens <= 8192: - if task == "completion": - return 0.06 / 1000 * tokens - else: - return 0.03 / 1000 * tokens - elif tokens <= 32768: - if task == "completion": - return 0.12 / 1000 * tokens - else: - return 0.06 / 1000 * tokens - else: - raise ValueError(f"Tokens too large for {model}: {tokens}") - else: - raise ValueError(f"Invalid model: {model}") +def tokens2price(model, task, tokens): + models = { + "gpt-3.5-turbo" : {"prompt": 0.0015, "completion": 0.002}, + "gpt-3.5-turbo-16k" : {"prompt": 0.003, "completion": 0.004}, + "gpt-4" : {"prompt": 0.03, "completion": 0.06}, + "gpt-4-32k" : {"prompt": 0.06, "completion": 0.12}, + "text-embedding-ada-002" : {"embedding": 0.0001}, + } + return models[model][task] / 1000 * tokens def text2tokens(model, text): import tiktoken From 06deef8ae91ba2b949c5e504a220f3fcdace9cf9 Mon Sep 17 00:00:00 2001 From: Seung-been Lee Date: Sat, 17 Jun 2023 11:31:50 +0900 Subject: [PATCH 19/25] Refactor code for improved maintainability --- kanu/chatgpt.py | 37 +++---------------------------------- kanu/docgpt.py | 38 +++----------------------------------- kanu/gui.py | 45 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 69 deletions(-) diff --git a/kanu/chatgpt.py b/kanu/chatgpt.py index f599614..95448f5 100644 --- a/kanu/chatgpt.py +++ b/kanu/chatgpt.py @@ -2,7 +2,7 @@ import openai -from .gui import Settings +from .gui import Settings, Conversation from .utils import tokens2price class ChatGPT: @@ -13,43 +13,12 @@ def __init__(self, kanu, openai_key, model, temperature, prompt): self.prompt = prompt openai.api_key = openai_key self.settings = Settings(self) + self.conversation = Conversation(self) self.tokens = 0 self.price = 0 def run(self): - self.kanu.container.pack_forget() - self.kanu.container = tk.Frame(self.kanu.root) - self.kanu.container.pack(fill="both", expand=True) - self.kanu.container.bind_all("", lambda event: self.send_message()) - self.kanu.container.focus_set() - l = tk.Label(self.kanu.container, text="ChatGPT") - l.grid(row=0, column=0, columnspan=4, sticky="ew") - self.system = tk.Text(self.kanu.container, height=7) - self.system.tag_configure("system", **self.settings.get_system_kwargs()) - self.system.insert(tk.END, f"System: A new chat session has been created using {self.model}.\n", "system") - self.system.grid(row=1, column=0, columnspan=4, sticky="ew") - self.session = tk.Text(self.kanu.container) - self.session.grid(row=2, column=0, columnspan=4, sticky="nsew") - self.session.tag_config("user", **self.settings.get_user_kwargs()) - self.session.tag_config("bot", **self.settings.get_bot_kwargs()) - self.user_input = tk.StringVar() - self.chatbox = tk.Entry(self.kanu.container, textvariable=self.user_input) - self.chatbox.grid(row=3, column=0, columnspan=4, sticky="ew") - self.messages = [] - button_frame = tk.Frame(self.kanu.container) - button_frame.grid(row=4, column=0, sticky="ew") - b = tk.Button(button_frame, text="Send", command=lambda: self.send_message()) - b.grid(row=0, column=0, sticky="ew") - b = tk.Button(button_frame, text="Clear", command=lambda: self.clear_session()) - b.grid(row=0, column=1, sticky="ew") - b = tk.Button(button_frame, text="Go back", command=lambda: self.kanu.config_chatgpt()) - b.grid(row=0, column=2, sticky="ew") - b = tk.Button(button_frame, text="Settings", command=lambda: self.settings.page()) - b.grid(row=0, column=3, sticky="ew") - self.kanu.container.grid_columnconfigure(0, weight=1) - self.kanu.container.grid_rowconfigure(2, weight=1) - for i in range(4): - button_frame.grid_columnconfigure(i, weight=1) + self.conversation.page() def send_message(self): if not self.messages: diff --git a/kanu/docgpt.py b/kanu/docgpt.py index ca61b2b..f2cdc87 100644 --- a/kanu/docgpt.py +++ b/kanu/docgpt.py @@ -17,7 +17,7 @@ CSVLoader, ) -from .gui import Tooltip, Settings +from .gui import Tooltip, Settings, Conversation from .utils import tokens2price, text2tokens DOCUMENT_LOADERS = { @@ -47,6 +47,7 @@ def __init__( self.default_chunk_overlap = default_chunk_overlap os.environ["OPENAI_API_KEY"] = openai_key self.settings = Settings(self) + self.conversation = Conversation(self) self.tokens = 0 self.price = 0 @@ -116,40 +117,7 @@ def query(self): chain_type="stuff", combine_docs_chain_kwargs={"prompt": PromptTemplate(template=self.prompt, input_variables=["context", "question"])} ) - self.kanu.container.pack_forget() - self.kanu.container = tk.Frame(self.kanu.root) - self.kanu.container.pack(fill="both", expand=True) - l = tk.Label(self.kanu.container, text="DocGPT") - l.grid(row=0, column=0, columnspan=4, sticky="ew") - self.system = tk.Text(self.kanu.container, height=7) - self.system.tag_configure("system", **self.settings.get_system_kwargs()) - if self.existing: - self.system.insert(tk.END, "System: Using existing database. Embedding was skipped and no tokens were used.\n", "system") - else: - self.system.insert(tk.END, f"System: Creating new database. Embedding used {self.tokens:,} tokens or ${self.price:.6f}.\n", "system") - self.system.insert(tk.END, f"System: A new chat session has been created using {self.model}.\n", "system") - self.system.grid(row=1, column=0, columnspan=4, sticky="ew") - self.session = tk.Text(self.kanu.container) - self.session.grid(row=2, column=0, columnspan=4, sticky="nsew") - self.session.tag_config("user", **self.settings.get_user_kwargs()) - self.session.tag_config("bot", **self.settings.get_bot_kwargs()) - self.user_input = tk.StringVar() - self.chatbox = tk.Entry(self.kanu.container, width=62, textvariable=self.user_input) - self.chatbox.grid(row=3, column=0, columnspan=4, sticky="ew") - button_frame = tk.Frame(self.kanu.container) - button_frame.grid(row=4, column=0, sticky="ew") - b = tk.Button(button_frame, text="Send", command=lambda: self.send_message()) - b.grid(row=0, column=0, sticky="ew") - b = tk.Button(button_frame, text="Clear", command=lambda: self.clear_session()) - b.grid(row=0, column=1, sticky="ew") - b = tk.Button(button_frame, text="Go back", command=lambda: self.kanu.config_chatgpt()) - b.grid(row=0, column=2, sticky="ew") - b = tk.Button(button_frame, text="Settings", command=lambda: self.settings.page()) - b.grid(row=0, column=3, sticky="ew") - self.kanu.container.grid_columnconfigure(0, weight=1) - self.kanu.container.grid_rowconfigure(2, weight=1) - for i in range(4): - button_frame.grid_columnconfigure(i, weight=1) + self.conversation.page() def send_message(self): self.session.insert(tk.END, "You: " + self.user_input.get() + "\n", "user") diff --git a/kanu/gui.py b/kanu/gui.py index f61fe23..7a2c13d 100644 --- a/kanu/gui.py +++ b/kanu/gui.py @@ -1,6 +1,51 @@ import tkinter as tk from tkinter import font +class Conversation: + def __init__(self, agent): + self.agent = agent + + 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(fill="both", expand=True) + self.agent.kanu.container.bind_all("", lambda event: self.agent.send_message()) + self.agent.kanu.container.focus_set() + l = tk.Label(self.agent.kanu.container, text=self.agent.__class__.__name__) + l.grid(row=0, column=0, columnspan=4, sticky="ew") + self.agent.system = tk.Text(self.agent.kanu.container, height=7) + self.agent.system.tag_configure("system", **self.agent.settings.get_system_kwargs()) + if self.agent.__class__.__name__ == "DocGPT": + if self.agent.existing: + self.agent.system.insert(tk.END, "System: Using existing database. Embedding was skipped and no tokens were used.\n", "system") + else: + self.agent.system.insert(tk.END, f"System: Creating new database. Embedding used {self.agent.tokens:,} tokens or ${self.agent.price:.6f}.\n", "system") + self.agent.system.insert(tk.END, f"System: A new chat session has been created using {self.agent.model}.\n", "system") + self.agent.system.grid(row=1, column=0, columnspan=4, sticky="ew") + self.agent.session = tk.Text(self.agent.kanu.container) + self.agent.session.grid(row=2, column=0, columnspan=4, sticky="nsew") + self.agent.session.tag_config("user", **self.agent.settings.get_user_kwargs()) + self.agent.session.tag_config("bot", **self.agent.settings.get_bot_kwargs()) + self.agent.user_input = tk.StringVar() + self.agent.chatbox = tk.Entry(self.agent.kanu.container, textvariable=self.agent.user_input) + self.agent.chatbox.grid(row=3, column=0, columnspan=4, sticky="ew") + self.agent.messages = [] + button_frame = tk.Frame(self.agent.kanu.container) + button_frame.grid(row=4, column=0, sticky="ew") + b = tk.Button(button_frame, text="Send", command=lambda: self.agent.send_message()) + b.grid(row=0, column=0, sticky="ew") + b = tk.Button(button_frame, text="Clear", command=lambda: self.agent.clear_session()) + b.grid(row=0, column=1, sticky="ew") + b = tk.Button(button_frame, text="Go back", command=lambda: self.agent.kanu.config_chatgpt()) + b.grid(row=0, column=2, sticky="ew") + b = tk.Button(button_frame, text="Settings", command=lambda: self.agent.settings.page()) + b.grid(row=0, column=3, sticky="ew") + self.agent.kanu.container.grid_columnconfigure(0, weight=1) + self.agent.kanu.container.grid_rowconfigure(2, weight=1) + for i in range(4): + button_frame.grid_columnconfigure(i, weight=1) + class Settings: def __init__(self, agent): self.agent = agent From d0a55b090e7a908fd0d0d9eecd5f1b1419d731f9 Mon Sep 17 00:00:00 2001 From: Seung-been Lee Date: Sat, 17 Jun 2023 16:57:38 +0900 Subject: [PATCH 20/25] Fix bug in gui.Conversation --- kanu/gui.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/kanu/gui.py b/kanu/gui.py index 7a2c13d..a42154a 100644 --- a/kanu/gui.py +++ b/kanu/gui.py @@ -4,6 +4,11 @@ class Conversation: def __init__(self, agent): self.agent = agent + self.name = self.agent.__class__.__name__ + if self.name == "ChatGPT": + self.config = self.agent.kanu.config_chatgpt + else: + self.config = self.agent.kanu.config_docgpt def page(self): self.agent.previous = self.agent.kanu.container @@ -12,11 +17,11 @@ def page(self): self.agent.kanu.container.pack(fill="both", expand=True) self.agent.kanu.container.bind_all("", lambda event: self.agent.send_message()) self.agent.kanu.container.focus_set() - l = tk.Label(self.agent.kanu.container, text=self.agent.__class__.__name__) + l = tk.Label(self.agent.kanu.container, text=self.name) l.grid(row=0, column=0, columnspan=4, sticky="ew") self.agent.system = tk.Text(self.agent.kanu.container, height=7) self.agent.system.tag_configure("system", **self.agent.settings.get_system_kwargs()) - if self.agent.__class__.__name__ == "DocGPT": + if self.name == "DocGPT": if self.agent.existing: self.agent.system.insert(tk.END, "System: Using existing database. Embedding was skipped and no tokens were used.\n", "system") else: @@ -37,7 +42,7 @@ def page(self): b.grid(row=0, column=0, sticky="ew") b = tk.Button(button_frame, text="Clear", command=lambda: self.agent.clear_session()) b.grid(row=0, column=1, sticky="ew") - b = tk.Button(button_frame, text="Go back", command=lambda: self.agent.kanu.config_chatgpt()) + b = tk.Button(button_frame, text="Go back", command=lambda: self.config()) b.grid(row=0, column=2, sticky="ew") b = tk.Button(button_frame, text="Settings", command=lambda: self.agent.settings.page()) b.grid(row=0, column=3, sticky="ew") From d834e05a69b91bc9b38e5e97c63528519aa67879 Mon Sep 17 00:00:00 2001 From: Seung-been Lee Date: Sat, 17 Jun 2023 17:09:31 +0900 Subject: [PATCH 21/25] Fix bug in gui.Conversation --- kanu/gui.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/kanu/gui.py b/kanu/gui.py index a42154a..fb39249 100644 --- a/kanu/gui.py +++ b/kanu/gui.py @@ -6,9 +6,9 @@ def __init__(self, agent): self.agent = agent self.name = self.agent.__class__.__name__ if self.name == "ChatGPT": - self.config = self.agent.kanu.config_chatgpt + self.go_back = self.agent.kanu.config_chatgpt else: - self.config = self.agent.kanu.config_docgpt + self.go_back = self.agent.run def page(self): self.agent.previous = self.agent.kanu.container @@ -42,7 +42,7 @@ def page(self): b.grid(row=0, column=0, sticky="ew") b = tk.Button(button_frame, text="Clear", command=lambda: self.agent.clear_session()) b.grid(row=0, column=1, sticky="ew") - b = tk.Button(button_frame, text="Go back", command=lambda: self.config()) + b = tk.Button(button_frame, text="Go back", command=lambda: self.go_back()) b.grid(row=0, column=2, sticky="ew") b = tk.Button(button_frame, text="Settings", command=lambda: self.agent.settings.page()) b.grid(row=0, column=3, sticky="ew") From 1c78126f0fecaeeac16075346342ce9ac707b3ef Mon Sep 17 00:00:00 2001 From: Seung-been Lee Date: Sat, 17 Jun 2023 17:30:04 +0900 Subject: [PATCH 22/25] Specify directory paths for DocGPT in config file --- kanu/__main__.py | 13 ++++++++++++- kanu/docgpt.py | 33 +++++++++++++++++++++++++-------- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/kanu/__main__.py b/kanu/__main__.py index 42c5e36..e65723c 100644 --- a/kanu/__main__.py +++ b/kanu/__main__.py @@ -159,7 +159,18 @@ def parse_docgpt_config(self): 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"]) + 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"], + config["OPTIONAL"]["new_database_directory"], + config["OPTIONAL"]["document_directory"], + config["OPTIONAL"]["existing_database_directory"], + ) def template_docgpt_config(self): file_path = filedialog.asksaveasfilename() diff --git a/kanu/docgpt.py b/kanu/docgpt.py index f2cdc87..288094f 100644 --- a/kanu/docgpt.py +++ b/kanu/docgpt.py @@ -37,7 +37,10 @@ def __init__( temperature, prompt, default_chunk_size, - default_chunk_overlap + default_chunk_overlap, + new_database_directory=None, + document_directory=None, + existing_database_directory=None, ): self.kanu = kanu self.model = model @@ -50,6 +53,9 @@ def __init__( self.conversation = Conversation(self) self.tokens = 0 self.price = 0 + self.new_database_directory = new_database_directory + self.document_directory = document_directory + self.existing_database_directory = existing_database_directory def run(self): self.kanu.container.pack_forget() @@ -99,13 +105,22 @@ def run(self): l = tk.Label(self.kanu.container, text="Database ⓘ:") Tooltip(l, "Directory where the database is stored.") l.grid(row=9, column=0) - self.old_database_label = tk.Label(self.kanu.container, text="Not selected", fg="red") - self.old_database_label.grid(row=9, column=1) - b = tk.Button(self.kanu.container, text="Browse", command=self.specify_old_database_directory) + self.existing_database_label = tk.Label(self.kanu.container, text="Not selected", fg="red") + self.existing_database_label.grid(row=9, column=1) + b = tk.Button(self.kanu.container, text="Browse", command=self.specify_existing_database_directory) b.grid(row=9, 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=10, column=0, columnspan=3) self.option2_button["state"] = tk.DISABLED + if self.new_database_directory is not None: + self.new_database_label.configure(text=os.path.basename(self.new_database_directory), fg="lime green") + if self.document_directory is not None: + self.document_label.configure(text=os.path.basename(self.document_directory), fg="lime green") + if self.new_database_label["text"] != "Not selected" and self.document_label["text"] != "Not selected": + self.option1_button["state"] = tk.NORMAL + if self.existing_database_directory is not None: + self.existing_database_label.configure(text=os.path.basename(self.existing_database_directory), fg="lime green") + self.option2_button["state"] = tk.NORMAL def query(self): self.memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True) @@ -137,6 +152,7 @@ def calculate_usage(self, cb): return message def go_with_option1(self): + self.database_directory = self.new_database_directory self.tokens = self.price = 0 documents = [] for root, dirs, files in os.walk(self.document_directory): @@ -162,6 +178,7 @@ def go_with_option1(self): self.query() def go_with_option2(self): + self.database_directory = self.existing_database_directory self.tokens = self.price = 0 self.existing = True self.query() @@ -179,17 +196,17 @@ def specify_new_database_directory(self): directory_path = filedialog.askdirectory() if not directory_path: return - self.database_directory = directory_path + self.new_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): + def specify_existing_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.existing_database_directory = directory_path + self.existing_database_label.configure(text=os.path.basename(directory_path), fg="lime green") self.option2_button["state"] = tk.NORMAL def clear_session(self): From ffd02f179ecd5a34724b8920ddd3fdd9afd8a80e Mon Sep 17 00:00:00 2001 From: Seung-been Lee Date: Sat, 17 Jun 2023 17:38:24 +0900 Subject: [PATCH 23/25] Specify directory paths for DocGPT in config file --- kanu/__main__.py | 1 + kanu/docgpt.py | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/kanu/__main__.py b/kanu/__main__.py index e65723c..13082f4 100644 --- a/kanu/__main__.py +++ b/kanu/__main__.py @@ -179,6 +179,7 @@ def template_docgpt_config(self): config = configparser.ConfigParser() config["DEFAULT"] = {"model": "gpt-3.5-turbo", "temperature": "0.5", "prompt": DOCGPT_PROMPT, "chunk_size": 1000, "chunk_overlap": 50} config["USER"] = {"openai_key": ""} + config["OPTIONAL"] = {"new_database_directory": "", "document_directory": "", "existing_database_directory": ""} with open(file_path, "w") as f: config.write(f) diff --git a/kanu/docgpt.py b/kanu/docgpt.py index 288094f..00b6aa4 100644 --- a/kanu/docgpt.py +++ b/kanu/docgpt.py @@ -38,9 +38,9 @@ def __init__( prompt, default_chunk_size, default_chunk_overlap, - new_database_directory=None, - document_directory=None, - existing_database_directory=None, + new_database_directory="", + document_directory="", + existing_database_directory="", ): self.kanu = kanu self.model = model @@ -112,13 +112,13 @@ def run(self): self.option2_button = tk.Button(self.kanu.container, text="Go with Option 2", command=self.go_with_option2) self.option2_button.grid(row=10, column=0, columnspan=3) self.option2_button["state"] = tk.DISABLED - if self.new_database_directory is not None: + if self.new_database_directory: self.new_database_label.configure(text=os.path.basename(self.new_database_directory), fg="lime green") - if self.document_directory is not None: + if self.document_directory: self.document_label.configure(text=os.path.basename(self.document_directory), fg="lime green") if self.new_database_label["text"] != "Not selected" and self.document_label["text"] != "Not selected": self.option1_button["state"] = tk.NORMAL - if self.existing_database_directory is not None: + if self.existing_database_directory: self.existing_database_label.configure(text=os.path.basename(self.existing_database_directory), fg="lime green") self.option2_button["state"] = tk.NORMAL From 9c619dcdd1c5246a3015a31c99b6fd11cf881cce Mon Sep 17 00:00:00 2001 From: Seung-been Lee Date: Sat, 17 Jun 2023 19:32:03 +0900 Subject: [PATCH 24/25] Enable users to download chat session as text file --- CHANGELOG.md | 1 + kanu/gui.py | 25 +++++++++++++++++++------ 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a4453c7..9306108 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * Implement token counter and price monitor in chat window. * Enable the Enter key for sending messages in chat window. * Enable automatic chat session resizing based on window size. +* Enable users to download chat session as a text file. ## 0.6.0 (2023-06-12) * Enable users to chat with .csv documents. diff --git a/kanu/gui.py b/kanu/gui.py index fb39249..83a62d3 100644 --- a/kanu/gui.py +++ b/kanu/gui.py @@ -1,5 +1,5 @@ import tkinter as tk -from tkinter import font +from tkinter import font, filedialog class Conversation: def __init__(self, agent): @@ -18,7 +18,7 @@ def page(self): self.agent.kanu.container.bind_all("", lambda event: self.agent.send_message()) self.agent.kanu.container.focus_set() l = tk.Label(self.agent.kanu.container, text=self.name) - l.grid(row=0, column=0, columnspan=4, sticky="ew") + l.grid(row=0, column=0, sticky="ew") self.agent.system = tk.Text(self.agent.kanu.container, height=7) self.agent.system.tag_configure("system", **self.agent.settings.get_system_kwargs()) if self.name == "DocGPT": @@ -27,14 +27,14 @@ def page(self): else: self.agent.system.insert(tk.END, f"System: Creating new database. Embedding used {self.agent.tokens:,} tokens or ${self.agent.price:.6f}.\n", "system") self.agent.system.insert(tk.END, f"System: A new chat session has been created using {self.agent.model}.\n", "system") - self.agent.system.grid(row=1, column=0, columnspan=4, sticky="ew") + self.agent.system.grid(row=1, column=0, sticky="ew") self.agent.session = tk.Text(self.agent.kanu.container) - self.agent.session.grid(row=2, column=0, columnspan=4, sticky="nsew") + self.agent.session.grid(row=2, column=0, sticky="nsew") self.agent.session.tag_config("user", **self.agent.settings.get_user_kwargs()) self.agent.session.tag_config("bot", **self.agent.settings.get_bot_kwargs()) self.agent.user_input = tk.StringVar() self.agent.chatbox = tk.Entry(self.agent.kanu.container, textvariable=self.agent.user_input) - self.agent.chatbox.grid(row=3, column=0, columnspan=4, sticky="ew") + self.agent.chatbox.grid(row=3, column=0, sticky="ew") self.agent.messages = [] button_frame = tk.Frame(self.agent.kanu.container) button_frame.grid(row=4, column=0, sticky="ew") @@ -46,11 +46,24 @@ def page(self): b.grid(row=0, column=2, sticky="ew") b = tk.Button(button_frame, text="Settings", command=lambda: self.agent.settings.page()) b.grid(row=0, column=3, sticky="ew") + b = tk.Button(button_frame, text="Save", command=lambda: self.save()) + b.grid(row=0, column=4, sticky="ew") self.agent.kanu.container.grid_columnconfigure(0, weight=1) self.agent.kanu.container.grid_rowconfigure(2, weight=1) - for i in range(4): + for i in range(5): button_frame.grid_columnconfigure(i, weight=1) + def save(self): + file_path = filedialog.asksaveasfilename() + if not file_path: + return + data = "[System]\n" + data += self.agent.system.get("1.0", tk.END).rstrip() + data += "\n\n[Session]\n" + data += self.agent.session.get("1.0", tk.END).rstrip() + with open(file_path, 'w') as f: + f.write(data) + class Settings: def __init__(self, agent): self.agent = agent From d608475e6c5aa05728b7a70f355796acb92d8a73 Mon Sep 17 00:00:00 2001 From: "Seung-been \"Steven\" Lee" Date: Mon, 19 Jun 2023 09:24:35 +0900 Subject: [PATCH 25/25] Update files --- CHANGELOG.md | 2 +- images/chatgpt.gif | Bin 61824 -> 66657 bytes images/docgpt.gif | Bin 101673 -> 113444 bytes 3 files changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9306108..e1e4525 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # CHANGELOG -## 0.7.0 (in development) +## 0.7.0 (2023-06-19) * Implement token counter and price monitor in chat window. * Enable the Enter key for sending messages in chat window. * Enable automatic chat session resizing based on window size. diff --git a/images/chatgpt.gif b/images/chatgpt.gif index acc9210cb7d3c3a9fb95c32f940f0975afeadddd..c71fd08834247f519aa7b7e7d594febfa3fa77e9 100644 GIT binary patch literal 66657 zcmeF&byOSO!!P;?F2S{Ekm69F6xZVJQrulqyl8MKP@q_`Qrs;#6u08g;##~dP^3tq zkdr>od*0u>?mG96yVm{lY-W;g_Ut|EnIu3q^HEe*5Eii-KuJcq1^^HUgpZF;N=iyi zP0hr_#KpxWARr(nCMGK@tE#N1s;a83sjjD|Yhq$zY^blJrD17qYHMrj;^N}$Xy@(i z9TXH8=6{ju|dsHe;)h0>3FCvsKN1d)pn{bas7upmN|$EFoMyoA#*C>(9a5*l+@{P>qsvfj%G_wgm~Hhq-C3~4 zm7~Iewb7Qb(S^CujlI#6rO}(SF@UG@6<1?0U#$-x+>*Xefp$=p?vpy*m>&I@0sWjQ z{iHShqz2QV8sn@c)1)rrlnG>9pX0MO^JinmDYK_n(vnlQOj9n%g?-kBY1V~l&V~K6 z9ov)_%ak|gR1oJ#Am^+X%bYjIToBjnE7r-EPfI)u5c=8|x>`8-TmW2D&g5=qVQZ4|6Iber38Vgw*ph?VpBO1 zlkWuQQUvGTip=IptfoCZ$PxUO#XVUlIr~BKq(t| zyj-v`L#8E10UoIq6&~_2+j25rW-3#5Hb-_UOK+;^`Bat4NSW$vvFu!p%3OuQ=SKC3 zTHUDz{m)Ih%N<6?H3~l}RZi=aPv8nC9SSGS$|qf_XHD|woeJm8%I96G=WRL@^4e3}xb^oz+w&>giz%neS?8-c$BP;7&E6N0k&y`r39n-!)6>&) za&odWQqz(XKjddue8?^?F0QPstS&Ex!{OcC-5=YU;B}Q3AM%Dfn`Rs1-b`nW4s