Last active
January 11, 2024 23:17
-
-
Save asctime/c55f4de1ce08f71c87170f9808b739ae to your computer and use it in GitHub Desktop.
Trivial ChatGPT GUI, with context, intent analysis and keyword enginies enabled; save & load chat; written in python3-gobject
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#! python3 | |
import sys, gi | |
gi.require_version('Gtk', '3.0') | |
from gi.repository import Gtk, Gdk, Pango | |
import re, openai, nltk, webbrowser | |
# Add support for sentiment analysis and keyword narrowing: | |
from nltk.corpus import stopwords | |
from nltk.tokenize import word_tokenize | |
from nltk.sentiment import SentimentIntensityAnalyzer | |
openai.api_key = '' | |
context_value=18 | |
token_value=2000 | |
# AI "randomness": creativity (1.0) vs consistency (0.0): | |
r_value=0.5 | |
# Instantiate a sentiment analyzer and tokens: | |
sia = SentimentIntensityAnalyzer() | |
stop_words = set(stopwords.words('english')) | |
# # # # Begin INTERNAL Class Definitions. # # # # | |
# Unfortunately new tag properties can't be created on-the-fly! | |
# We have to actually extend the TextTag base class for that... | |
class URLTag(Gtk.TextTag): | |
def __init__(self, url): | |
super().__init__() | |
self.set_property("underline", Pango.Underline.SINGLE) | |
self.set_property("foreground", "blue") | |
self.connect("event", self.url_event) | |
self.url = url | |
def url_event(self, tag, widget, event, iterator): | |
if event.type == Gdk.EventType.BUTTON_PRESS: | |
# print("\nOpen URL: " + self.url + "\n") | |
webbrowser.open(self.url) | |
class MainWindow(Gtk.Window): | |
def __init__(self): | |
# # # # __init__: G U I D E F I N I T I O N S S T A R T # # # # | |
Gtk.Window.__init__(self, title="OpenAI Python CLI") | |
self.set_border_width(10) | |
# Set the window size to 40% of the screen width and 60% of the screen height | |
window = Gtk.Window() | |
screen = Gdk.Display.get_default().get_monitor(0).get_geometry() | |
width = int(screen.width * 0.4) | |
height = int(screen.height * 0.6) | |
self.set_default_size(width, height) | |
self.vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6) | |
self.chat_history = [] | |
self.num_context_lines = context_value # number of previous chat messages to keep in context | |
# Add chat history scrolled window | |
self.scrolled_window = Gtk.ScrolledWindow() | |
self.scrolled_window.set_hexpand(True) | |
self.scrolled_window.set_vexpand(True) | |
self. vbox.pack_start(self.scrolled_window, True, True, 0) | |
self.history_textview = Gtk.TextView() | |
self.history_textview.set_wrap_mode(Gtk.WrapMode.WORD_CHAR) | |
self.history_textview.set_editable(False) | |
self.history_textview.set_cursor_visible(True) | |
self.history_textview.set_vexpand(True) | |
self.scrolled_window.add(self.history_textview) | |
if len(sys.argv) > 1: | |
try: | |
with open(sys.argv[1], "r") as file: | |
self.chat_history = file.readlines() | |
self.update_chat_history() | |
except(FileNotFoundError): | |
dialog = Gtk.MessageDialog(self, 0, Gtk.MessageType.INFO, | |
Gtk.ButtonsType.OK, "Error Loading Chat History File:") | |
dialog.format_secondary_text("Check "+sys.argv[1]) | |
dialog.run() | |
dialog.destroy() | |
# Add usage stats label | |
self.stats_label = Gtk.Label() | |
self.vbox.pack_start(self.stats_label, False, False, 0) | |
# Add query response entry | |
self.chat_entry = Gtk.Entry() | |
self.chat_entry.set_placeholder_text("Type your message here") | |
self.chat_entry.connect("activate", self.send_chat) | |
self.chat_entry.set_vexpand(False) | |
self.vbox.pack_end(self.chat_entry, False, False, 0) | |
# Add Send, Load, Save and New chat buttons | |
self.button_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6) | |
self.send_button = Gtk.Button() | |
self.send_image = Gtk.Image.new_from_icon_name("mail-send-symbolic", Gtk.IconSize.BUTTON) | |
self.send_button.set_image(self.send_image) | |
self.send_button.set_size_request(60, 30) | |
self.send_button.connect("clicked", self.send_chat) | |
self.send_button.set_tooltip_text("Send Chat") | |
self.save_button = Gtk.Button() | |
self.save_image = Gtk.Image.new_from_icon_name("document-save", Gtk.IconSize.BUTTON) | |
self.save_button.set_image(self.save_image) | |
self.save_button.set_size_request(60, 30) | |
self.save_button.connect("clicked", self.save_chat) | |
self.save_button.set_tooltip_text("Save Chat") | |
self.load_button = Gtk.Button() | |
self.load_image = Gtk.Image.new_from_icon_name("document-open", Gtk.IconSize.BUTTON) | |
self.load_button.set_image(self.load_image) | |
self.load_button.set_size_request(60, 30) | |
self.load_button.connect("clicked", self.load_chat) | |
self.load_button.set_tooltip_text("Load Chat") | |
self.new_button = Gtk.Button() | |
self.new_image = Gtk.Image.new_from_icon_name("document-new", Gtk.IconSize.BUTTON) | |
self.new_button.set_image(self.new_image) | |
self.new_button.set_size_request(60, 30) | |
self.new_button.connect("clicked", self.new_chat) | |
self.new_button.set_tooltip_text("New Chat") | |
self.button_box.pack_start(self.send_button, False, False, 0) | |
self.button_box.pack_start(self.save_button, False, False, 0) | |
self.button_box.pack_start(self.load_button, False, False, 0) | |
self.button_box.pack_start(self.new_button, False, False, 0) | |
self.vbox.pack_end(self.button_box, False, False, 0) | |
self.add(self.vbox) | |
# # # # G U I D E F I N I T I O N S E N D __init__ function # # # # | |
def autoscroll(self, *args): | |
adj = self.scrolled_window.get_vadjustment() | |
adj.set_value(adj.get_upper() - adj.get_page_size()) | |
def on_url_clicked(self, tag, widget, event, iter): | |
start, end = iter.get_line() | |
url = self.history_textview.get_buffer().get_text(start, end, False) | |
webbrowser.open(url) | |
def update_chat_history(self): | |
buffer = self.history_textview.get_buffer() | |
buffer.set_text("".join(self.chat_history)) | |
# add the URL tag to the tag table | |
tag_table = buffer.get_tag_table() | |
# use regex to find and tag URLs in the text | |
text = buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter(), False) | |
urls = re.findall(r'(https?://\S+)', text) | |
for url in urls: | |
start = text.find(url) | |
end = start + len(url) | |
url_tag = URLTag(url) | |
tag_table.add(url_tag) | |
buffer.apply_tag(url_tag, buffer.get_iter_at_offset(start), buffer.get_iter_at_offset(end)) | |
# Reliable auto-scrolling can only be done by sending a signal to the outer scrolled window: | |
self.history_textview.connect("size-allocate", self.autoscroll) | |
def update_stats(self): | |
# Calculate number of user and AI messages | |
num_user_messages = len([m for m in self.chat_history if m.startswith("User")]) | |
num_ai_messages = len([m for m in self.chat_history if m.startswith("ChatGPT")]) | |
# Update stats label | |
stats_text = f"User messages: {num_user_messages} | AI messages: {num_ai_messages}" | |
self.stats_label.set_text(stats_text) | |
def new_chat(self, widget): | |
self.chat_history = [] | |
self.update_chat_history() | |
def save_chat(self, widget): | |
dialog = Gtk.FileChooserDialog(title="Save Chat", parent=None, action=Gtk.FileChooserAction.SAVE) | |
dialog.add_buttons( | |
Gtk.STOCK_SAVE, | |
Gtk.ResponseType.OK, | |
Gtk.STOCK_CANCEL, | |
Gtk.ResponseType.CANCEL, | |
) | |
dialog.set_current_name("chat.chatgpt") | |
response = dialog.run() | |
if response == Gtk.ResponseType.OK: | |
filename = dialog.get_filename() | |
with open(filename, "w") as file: | |
file.write("".join(self.chat_history)) | |
dialog.destroy() | |
def load_chat(self, widget): | |
dialog = Gtk.FileChooserDialog(title="Open Chat", parent=None, action=Gtk.FileChooserAction.OPEN) | |
dialog.add_buttons( | |
Gtk.STOCK_OPEN, | |
Gtk.ResponseType.OK, | |
Gtk.STOCK_CANCEL, | |
Gtk.ResponseType.CANCEL, | |
) | |
response = dialog.run() | |
if response == Gtk.ResponseType.OK: | |
filename = dialog.get_filename() | |
with open(filename, "r") as file: | |
self.chat_history = file.readlines() | |
self.update_chat_history() | |
dialog.destroy() | |
def send_chat(self, widget): | |
message = self.chat_entry.get_text().strip() | |
if message == "": | |
return | |
# Add user message to chat history | |
self.chat_history.append(f"User: {message}\n") | |
# Get AI response | |
self.context = ''.join(self.chat_history[-context_value:]) | |
# Insert keyword token narrowing here: | |
self.tokens = word_tokenize(self.context + message) | |
self.tagged = nltk.pos_tag(self.tokens) | |
self.nouns = [word for word, pos in self.tagged if pos in ['NN', 'NNS', 'NNP', 'NNPS']] | |
self.keywords = [word for word in self.nouns if word.lower() not in stop_words] | |
self.response = openai.Completion.create( | |
#select from "text-davinci-003", "bert-base-uncased", "xlnet-base-cased": | |
engine="text-davinci-003", | |
prompt=self.context + message, | |
max_tokens=token_value, | |
temperature=r_value, | |
frequency_penalty=0, | |
presence_penalty=0, | |
top_p=1, | |
).choices[0].text.strip() | |
# Add AI response to chat history | |
if "ChatGPT:" in self.response: | |
self.chat_history.append(f"{self.response}\n") | |
else: | |
self.chat_history.append(f"ChatGPT: {self.response}\n") | |
# enable intent analysis using sentiment polarity and keywords library options: | |
self.response_sentiment = sia.polarity_scores(self.response)["compound"] | |
# Clear chat entry | |
self.chat_entry.set_text("") | |
# Update chat history and stats | |
self.update_chat_history() | |
self.update_stats() | |
win = MainWindow() | |
win.connect("destroy", Gtk.main_quit) | |
win.show_all() | |
Gtk.main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment