Skip to content

Instantly share code, notes, and snippets.

@asctime
Last active January 11, 2024 23:17
Show Gist options
  • Save asctime/c55f4de1ce08f71c87170f9808b739ae to your computer and use it in GitHub Desktop.
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
#! 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