Linting code

This commit is contained in:
jeffser 2024-08-04 21:43:23 -06:00
parent 1a6e74271c
commit 4b6cd17d0a
2 changed files with 138 additions and 92 deletions

View File

@ -20,6 +20,7 @@
Main script run at launch, handles actions, about dialog and the app itself (not the window) Main script run at launch, handles actions, about dialog and the app itself (not the window)
""" """
import gi
gi.require_version('Gtk', '4.0') gi.require_version('Gtk', '4.0')
gi.require_version('Adw', '1') gi.require_version('Adw', '1')
from gi.repository import Gtk, Gio, Adw, GLib from gi.repository import Gtk, Gio, Adw, GLib
@ -29,7 +30,6 @@ from .internal import cache_dir, data_dir
import sys import sys
import logging import logging
import gi
import os import os
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

View File

@ -38,7 +38,6 @@ from .internal import config_dir, data_dir, cache_dir, source_dir
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@Gtk.Template(resource_path='/com/jeffser/Alpaca/window.ui') @Gtk.Template(resource_path='/com/jeffser/Alpaca/window.ui')
class AlpacaWindow(Adw.ApplicationWindow): class AlpacaWindow(Adw.ApplicationWindow):
app_dir = os.getenv("FLATPAK_DEST") app_dir = os.getenv("FLATPAK_DEST")
@ -140,7 +139,8 @@ class AlpacaWindow(Adw.ApplicationWindow):
@Gtk.Template.Callback() @Gtk.Template.Callback()
def verify_if_image_can_be_used(self, pspec=None, user_data=None): def verify_if_image_can_be_used(self, pspec=None, user_data=None):
logger.debug("Verifying if image can be used") logger.debug("Verifying if image can be used")
if self.model_drop_down.get_selected_item() == None: return True if self.model_drop_down.get_selected_item() == None:
return True
selected = self.convert_model_name(self.model_drop_down.get_selected_item().get_string(), 1).split(":")[0] selected = self.convert_model_name(self.model_drop_down.get_selected_item().get_string(), 1).split(":")[0]
if selected in [key for key, value in self.available_models.items() if value["image"]]: if selected in [key for key, value in self.available_models.items() if value["image"]]:
for name, content in self.attachments.items(): for name, content in self.attachments.items():
@ -155,7 +155,8 @@ class AlpacaWindow(Adw.ApplicationWindow):
@Gtk.Template.Callback() @Gtk.Template.Callback()
def stop_message(self, button=None): def stop_message(self, button=None):
if self.loading_spinner: self.chat_container.remove(self.loading_spinner) if self.loading_spinner:
self.chat_container.remove(self.loading_spinner)
self.toggle_ui_sensitive(True) self.toggle_ui_sensitive(True)
self.switch_send_stop_button() self.switch_send_stop_button()
self.bot_message = None self.bot_message = None
@ -179,8 +180,10 @@ class AlpacaWindow(Adw.ApplicationWindow):
self.save_history() self.save_history()
self.show_toast(_("Message edited successfully"), self.main_overlay) self.show_toast(_("Message edited successfully"), self.main_overlay)
if self.bot_message or self.get_focus() not in (self.message_text_view, self.send_button): return if self.bot_message or self.get_focus() not in (self.message_text_view, self.send_button):
if not self.message_text_view.get_buffer().get_text(self.message_text_view.get_buffer().get_start_iter(), self.message_text_view.get_buffer().get_end_iter(), False): return return
if not self.message_text_view.get_buffer().get_text(self.message_text_view.get_buffer().get_start_iter(), self.message_text_view.get_buffer().get_end_iter(), False):
return
current_chat_row = self.chat_list_box.get_selected_row() current_chat_row = self.chat_list_box.get_selected_row()
self.chat_list_box.unselect_all() self.chat_list_box.unselect_all()
self.chat_list_box.remove(current_chat_row) self.chat_list_box.remove(current_chat_row)
@ -193,18 +196,19 @@ class AlpacaWindow(Adw.ApplicationWindow):
if current_model is None: if current_model is None:
self.show_toast(_("Please select a model before chatting"), self.main_overlay) self.show_toast(_("Please select a model before chatting"), self.main_overlay)
return return
id = self.generate_uuid() message_id = self.generate_uuid()
attached_images = [] attached_images = []
attached_files = {} attached_files = {}
can_use_images = self.verify_if_image_can_be_used() can_use_images = self.verify_if_image_can_be_used()
for name, content in self.attachments.items(): for name, content in self.attachments.items():
if content["type"] == 'image' and can_use_images: attached_images.append(name) if content["type"] == 'image' and can_use_images:
attached_images.append(name)
else: else:
attached_files[name] = content['type'] attached_files[name] = content['type']
if not os.path.exists(os.path.join(self.data_dir, "chats", self.chats['selected_chat'], id)): if not os.path.exists(os.path.join(self.data_dir, "chats", self.chats['selected_chat'], message_id)):
os.makedirs(os.path.join(self.data_dir, "chats", self.chats['selected_chat'], id)) os.makedirs(os.path.join(self.data_dir, "chats", self.chats['selected_chat'], message_id))
shutil.copy(content['path'], os.path.join(self.data_dir, "chats", self.chats['selected_chat'], id, name)) shutil.copy(content['path'], os.path.join(self.data_dir, "chats", self.chats['selected_chat'], message_id, name))
content["button"].get_parent().remove(content["button"]) content["button"].get_parent().remove(content["button"])
self.attachments = {} self.attachments = {}
self.attachment_box.set_visible(False) self.attachment_box.set_visible(False)
@ -213,16 +217,16 @@ class AlpacaWindow(Adw.ApplicationWindow):
current_datetime = datetime.now() current_datetime = datetime.now()
self.chats["chats"][self.chats["selected_chat"]]["messages"][id] = { self.chats["chats"][self.chats["selected_chat"]]["messages"][message_id] = {
"role": "user", "role": "user",
"model": "User", "model": "User",
"date": current_datetime.strftime("%Y/%m/%d %H:%M:%S"), "date": current_datetime.strftime("%Y/%m/%d %H:%M:%S"),
"content": self.message_text_view.get_buffer().get_text(self.message_text_view.get_buffer().get_start_iter(), self.message_text_view.get_buffer().get_end_iter(), False) "content": self.message_text_view.get_buffer().get_text(self.message_text_view.get_buffer().get_start_iter(), self.message_text_view.get_buffer().get_end_iter(), False)
} }
if len(attached_images) > 0: if len(attached_images) > 0:
self.chats["chats"][self.chats["selected_chat"]]["messages"][id]['images'] = attached_images self.chats["chats"][self.chats["selected_chat"]]["messages"][message_id]['images'] = attached_images
if len(attached_files.keys()) > 0: if len(attached_files.keys()) > 0:
self.chats["chats"][self.chats["selected_chat"]]["messages"][id]['files'] = attached_files self.chats["chats"][self.chats["selected_chat"]]["messages"][message_id]['files'] = attached_files
data = { data = {
"model": current_model, "model": current_model,
"messages": self.convert_history_to_ollama(), "messages": self.convert_history_to_ollama(),
@ -235,12 +239,12 @@ class AlpacaWindow(Adw.ApplicationWindow):
#self.attachments[name] = {"path": file_path, "type": file_type, "content": content} #self.attachments[name] = {"path": file_path, "type": file_type, "content": content}
raw_message = self.message_text_view.get_buffer().get_text(self.message_text_view.get_buffer().get_start_iter(), self.message_text_view.get_buffer().get_end_iter(), False) raw_message = self.message_text_view.get_buffer().get_text(self.message_text_view.get_buffer().get_start_iter(), self.message_text_view.get_buffer().get_end_iter(), False)
formated_date = GLib.markup_escape_text(self.generate_datetime_format(current_datetime)) formated_date = GLib.markup_escape_text(self.generate_datetime_format(current_datetime))
self.show_message(raw_message, False, f"\n\n<small>{formated_date}</small>", attached_images, attached_files, id=id) self.show_message(raw_message, False, f"\n\n<small>{formated_date}</small>", attached_images, attached_files, message_id=message_id)
self.message_text_view.get_buffer().set_text("", 0) self.message_text_view.get_buffer().set_text("", 0)
self.loading_spinner = Gtk.Spinner(spinning=True, margin_top=12, margin_bottom=12, hexpand=True) self.loading_spinner = Gtk.Spinner(spinning=True, margin_top=12, margin_bottom=12, hexpand=True)
self.chat_container.append(self.loading_spinner) self.chat_container.append(self.loading_spinner)
bot_id=self.generate_uuid() bot_id=self.generate_uuid()
self.show_message("", True, id=bot_id) self.show_message("", True, message_id=bot_id)
thread = threading.Thread(target=self.run_message, args=(data['messages'], data['model'], bot_id)) thread = threading.Thread(target=self.run_message, args=(data['messages'], data['model'], bot_id))
thread.start() thread.start()
@ -259,8 +263,10 @@ class AlpacaWindow(Adw.ApplicationWindow):
@Gtk.Template.Callback() @Gtk.Template.Callback()
def welcome_carousel_page_changed(self, carousel, index): def welcome_carousel_page_changed(self, carousel, index):
logger.debug("Showing welcome carousel") logger.debug("Showing welcome carousel")
if index == 0: self.welcome_previous_button.set_sensitive(False) if index == 0:
else: self.welcome_previous_button.set_sensitive(True) self.welcome_previous_button.set_sensitive(False)
else:
self.welcome_previous_button.set_sensitive(True)
if index == carousel.get_n_pages()-1: if index == carousel.get_n_pages()-1:
self.welcome_next_button.set_label(_("Close")) self.welcome_next_button.set_label(_("Close"))
self.welcome_next_button.set_tooltip_text(_("Close")) self.welcome_next_button.set_tooltip_text(_("Close"))
@ -274,7 +280,8 @@ class AlpacaWindow(Adw.ApplicationWindow):
@Gtk.Template.Callback() @Gtk.Template.Callback()
def welcome_next_button_activate(self, button): def welcome_next_button_activate(self, button):
if button.get_label() == "Next": self.welcome_carousel.scroll_to(self.welcome_carousel.get_nth_page(self.welcome_carousel.get_position()+1), True) if button.get_label() == "Next":
self.welcome_carousel.scroll_to(self.welcome_carousel.get_nth_page(self.welcome_carousel.get_position()+1), True)
else: else:
self.welcome_dialog.force_close() self.welcome_dialog.force_close()
if not self.verify_connection(): if not self.verify_connection():
@ -342,8 +349,10 @@ class AlpacaWindow(Adw.ApplicationWindow):
@Gtk.Template.Callback() @Gtk.Template.Callback()
def model_spin_changed(self, spin): def model_spin_changed(self, spin):
value = spin.get_value() value = spin.get_value()
if spin.get_name() != "temperature": value = round(value) if spin.get_name() != "temperature":
else: value = round(value, 1) value = round(value)
else:
value = round(value, 1)
if self.model_tweaks[spin.get_name()] is not None and self.model_tweaks[spin.get_name()] != value: if self.model_tweaks[spin.get_name()] is not None and self.model_tweaks[spin.get_name()] != value:
self.model_tweaks[spin.get_name()] = value self.model_tweaks[spin.get_name()] = value
self.save_server_config() self.save_server_config()
@ -391,11 +400,15 @@ class AlpacaWindow(Adw.ApplicationWindow):
def override_changed(self, entry): def override_changed(self, entry):
name = entry.get_name() name = entry.get_name()
value = entry.get_text() value = entry.get_text()
if (not value and name not in local_instance.overrides) or (value and value in local_instance.overrides and local_instance.overrides[name] == value): return if (not value and name not in local_instance.overrides) or (value and value in local_instance.overrides and local_instance.overrides[name] == value):
if not value: del local_instance.overrides[name] return
else: local_instance.overrides[name] = value if not value:
del local_instance.overrides[name]
else:
local_instance.overrides[name] = value
self.save_server_config() self.save_server_config()
if not self.run_remote: local_instance.reset() if not self.run_remote:
local_instance.reset()
@Gtk.Template.Callback() @Gtk.Template.Callback()
def link_button_handler(self, button): def link_button_handler(self, button):
@ -413,7 +426,8 @@ class AlpacaWindow(Adw.ApplicationWindow):
for i, key in enumerate(self.available_models.keys()): for i, key in enumerate(self.available_models.keys()):
row = self.available_model_list_box.get_row_at_index(i) row = self.available_model_list_box.get_row_at_index(i)
row.set_visible(re.search(entry.get_text(), '{} {} {}'.format(row.get_title(), (_("image") if self.available_models[key]['image'] else " "), row.get_subtitle()), re.IGNORECASE)) row.set_visible(re.search(entry.get_text(), '{} {} {}'.format(row.get_title(), (_("image") if self.available_models[key]['image'] else " "), row.get_subtitle()), re.IGNORECASE))
if row.get_visible(): results += 1 if row.get_visible():
results += 1
if entry.get_text() and results == 0: if entry.get_text() and results == 0:
self.available_model_list_box.set_visible(False) self.available_model_list_box.set_visible(False)
self.no_results_page.set_visible(True) self.no_results_page.set_visible(True)
@ -422,12 +436,15 @@ class AlpacaWindow(Adw.ApplicationWindow):
self.no_results_page.set_visible(False) self.no_results_page.set_visible(False)
def convert_model_name(self, name:str, mode:int) -> str: # mode=0 name:tag -> Name (tag) | mode=1 Name (tag) -> name:tag def convert_model_name(self, name:str, mode:int) -> str: # mode=0 name:tag -> Name (tag) | mode=1 Name (tag) -> name:tag
if mode == 0: return "{} ({})".format(name.split(":")[0].replace("-", " ").title(), name.split(":")[1]) if mode == 0:
if mode == 1: return "{}:{}".format(name.split(" (")[0].replace(" ", "-").lower(), name.split(" (")[1][:-1]) return "{} ({})".format(name.split(":")[0].replace("-", " ").title(), name.split(":")[1])
if mode == 1:
return "{}:{}".format(name.split(" (")[0].replace(" ", "-").lower(), name.split(" (")[1][:-1])
def check_alphanumeric(self, editable, text, length, position): def check_alphanumeric(self, editable, text, length, position):
new_text = ''.join([char for char in text if char.isalnum() or char in ['-', '.', ':']]) new_text = ''.join([char for char in text if char.isalnum() or char in ['-', '.', ':']])
if new_text != text: editable.stop_emission_by_name("insert-text") if new_text != text:
editable.stop_emission_by_name("insert-text")
def create_model(self, model:str, file:bool): def create_model(self, model:str, file:bool):
modelfile_buffer = self.create_model_modelfile.get_buffer() modelfile_buffer = self.create_model_modelfile.get_buffer()
@ -466,31 +483,33 @@ class AlpacaWindow(Adw.ApplicationWindow):
logger.info(f"{title}, {body}") logger.info(f"{title}, {body}")
notification = Gio.Notification.new(title) notification = Gio.Notification.new(title)
notification.set_body(body) notification.set_body(body)
if icon: notification.set_icon(icon) if icon:
notification.set_icon(icon)
self.get_application().send_notification(None, notification) self.get_application().send_notification(None, notification)
def delete_message(self, message_element): def delete_message(self, message_element):
logger.debug("Deleting message") logger.debug("Deleting message")
id = message_element.get_name() message_id = message_element.get_name()
del self.chats["chats"][self.chats["selected_chat"]]["messages"][id] del self.chats["chats"][self.chats["selected_chat"]]["messages"][message_id]
self.chat_container.remove(message_element) self.chat_container.remove(message_element)
if os.path.exists(os.path.join(self.data_dir, "chats", self.chats['selected_chat'], id)): if os.path.exists(os.path.join(self.data_dir, "chats", self.chats['selected_chat'], message_id)):
shutil.rmtree(os.path.join(self.data_dir, "chats", self.chats['selected_chat'], id)) shutil.rmtree(os.path.join(self.data_dir, "chats", self.chats['selected_chat'], message_id))
self.save_history() self.save_history()
def copy_message(self, message_element): def copy_message(self, message_element):
logger.debug("Copying message") logger.debug("Copying message")
id = message_element.get_name() message_id = message_element.get_name()
clipboard = Gdk.Display().get_default().get_clipboard() clipboard = Gdk.Display().get_default().get_clipboard()
clipboard.set(self.chats["chats"][self.chats["selected_chat"]]["messages"][id]["content"]) clipboard.set(self.chats["chats"][self.chats["selected_chat"]]["messages"][message_id]["content"])
self.show_toast(_("Message copied to the clipboard"), self.main_overlay) self.show_toast(_("Message copied to the clipboard"), self.main_overlay)
def edit_message(self, message_element, text_view, button_container): def edit_message(self, message_element, text_view, button_container):
logger.debug("Editing message") logger.debug("Editing message")
if self.editing_message: self.send_message() if self.editing_message:
self.send_message()
button_container.set_visible(False) button_container.set_visible(False)
id = message_element.get_name() message_id = message_element.get_name()
text_buffer = text_view.get_buffer() text_buffer = text_view.get_buffer()
end_iter = text_buffer.get_end_iter() end_iter = text_buffer.get_end_iter()
@ -504,7 +523,7 @@ class AlpacaWindow(Adw.ApplicationWindow):
text_view.set_css_classes(["view", "editing_message_textview"]) text_view.set_css_classes(["view", "editing_message_textview"])
text_view.set_cursor_visible(True) text_view.set_cursor_visible(True)
self.editing_message = {"text_view": text_view, "id": id, "button_container": button_container, "footer": footer} self.editing_message = {"text_view": text_view, "id": message_id, "button_container": button_container, "footer": footer}
def preview_file(self, file_path, file_type, presend_name): def preview_file(self, file_path, file_type, presend_name):
logger.debug(f"Previewing file: {file_path}") logger.debug(f"Previewing file: {file_path}")
@ -547,22 +566,24 @@ class AlpacaWindow(Adw.ApplicationWindow):
def convert_history_to_ollama(self): def convert_history_to_ollama(self):
messages = [] messages = []
for id, message in self.chats["chats"][self.chats["selected_chat"]]["messages"].items(): for message_id, message in self.chats["chats"][self.chats["selected_chat"]]["messages"].items():
new_message = message.copy() new_message = message.copy()
if 'files' in message and len(message['files']) > 0: if 'files' in message and len(message['files']) > 0:
del new_message['files'] del new_message['files']
new_message['content'] = '' new_message['content'] = ''
for name, file_type in message['files'].items(): for name, file_type in message['files'].items():
file_path = os.path.join(self.data_dir, "chats", self.chats['selected_chat'], id, name) file_path = os.path.join(self.data_dir, "chats", self.chats['selected_chat'], message_id, name)
file_data = self.get_content_of_file(file_path, file_type) file_data = self.get_content_of_file(file_path, file_type)
if file_data: new_message['content'] += f"```[{name}]\n{file_data}\n```" if file_data:
new_message['content'] += f"```[{name}]\n{file_data}\n```"
new_message['content'] += message['content'] new_message['content'] += message['content']
if 'images' in message and len(message['images']) > 0: if 'images' in message and len(message['images']) > 0:
new_message['images'] = [] new_message['images'] = []
for name in message['images']: for name in message['images']:
file_path = os.path.join(self.data_dir, "chats", self.chats['selected_chat'], id, name) file_path = os.path.join(self.data_dir, "chats", self.chats['selected_chat'], message_id, name)
image_data = self.get_content_of_file(file_path, 'image') image_data = self.get_content_of_file(file_path, 'image')
if image_data: new_message['images'].append(image_data) if image_data:
new_message['images'].append(image_data)
messages.append(new_message) messages.append(new_message)
return messages return messages
@ -581,14 +602,15 @@ Generate a title following these rules:
```""" ```"""
current_model = self.convert_model_name(self.model_drop_down.get_selected_item().get_string(), 1) current_model = self.convert_model_name(self.model_drop_down.get_selected_item().get_string(), 1)
data = {"model": current_model, "prompt": prompt, "stream": False} data = {"model": current_model, "prompt": prompt, "stream": False}
if 'images' in message: data["images"] = message['images'] if 'images' in message:
data["images"] = message['images']
response = connection_handler.simple_post(f"{connection_handler.url}/api/generate", data=json.dumps(data)) response = connection_handler.simple_post(f"{connection_handler.url}/api/generate", data=json.dumps(data))
new_chat_name = json.loads(response.text)["response"].strip().removeprefix("Title: ").removeprefix("title: ").strip('\'"').replace('\n', ' ').title().replace('\'S', '\'s') new_chat_name = json.loads(response.text)["response"].strip().removeprefix("Title: ").removeprefix("title: ").strip('\'"').replace('\n', ' ').title().replace('\'S', '\'s')
new_chat_name = new_chat_name[:50] + (new_chat_name[50:] and '...') new_chat_name = new_chat_name[:50] + (new_chat_name[50:] and '...')
self.rename_chat(label_element.get_name(), new_chat_name, label_element) self.rename_chat(label_element.get_name(), new_chat_name, label_element)
def show_message(self, msg:str, bot:bool, footer:str=None, images:list=None, files:dict=None, id:str=None): def show_message(self, msg:str, bot:bool, footer:str=None, images:list=None, files:dict=None, message_id:str=None):
message_text = Gtk.TextView( message_text = Gtk.TextView(
editable=False, editable=False,
focusable=True, focusable=True,
@ -603,7 +625,8 @@ Generate a title following these rules:
) )
message_buffer = message_text.get_buffer() message_buffer = message_text.get_buffer()
message_buffer.insert(message_buffer.get_end_iter(), msg) message_buffer.insert(message_buffer.get_end_iter(), msg)
if footer is not None: message_buffer.insert_markup(message_buffer.get_end_iter(), footer, len(footer.encode('utf-8'))) if footer is not None:
message_buffer.insert_markup(message_buffer.get_end_iter(), footer, len(footer.encode('utf-8')))
delete_button = Gtk.Button( delete_button = Gtk.Button(
icon_name = "user-trash-symbolic", icon_name = "user-trash-symbolic",
@ -656,7 +679,7 @@ Generate a title following these rules:
child=image_container child=image_container
) )
for image in images: for image in images:
path = os.path.join(self.data_dir, "chats", self.chats['selected_chat'], id, image) path = os.path.join(self.data_dir, "chats", self.chats['selected_chat'], message_id, image)
try: try:
if not os.path.isfile(path): if not os.path.isfile(path):
raise FileNotFoundError("'{}' was not found or is a directory".format(path)) raise FileNotFoundError("'{}' was not found or is a directory".format(path))
@ -665,7 +688,7 @@ Generate a title following these rules:
button = Gtk.Button( button = Gtk.Button(
child=image_element, child=image_element,
css_classes=["flat", "chat_image_button"], css_classes=["flat", "chat_image_button"],
name=os.path.join(self.data_dir, "chats", "{selected_chat}", id, image), name=os.path.join(self.data_dir, "chats", "{selected_chat}", message_id, image),
tooltip_text=_("Image") tooltip_text=_("Image")
) )
button.connect("clicked", lambda button, file_path=path: self.preview_file(file_path, 'image', None)) button.connect("clicked", lambda button, file_path=path: self.preview_file(file_path, 'image', None))
@ -728,19 +751,19 @@ Generate a title following these rules:
tooltip_text=name, tooltip_text=name,
child=button_content child=button_content
) )
file_path = os.path.join(self.data_dir, "chats", "{selected_chat}", id, name) file_path = os.path.join(self.data_dir, "chats", "{selected_chat}", message_id, name)
button.connect("clicked", lambda button, file_path=file_path, file_type=file_type: self.preview_file(file_path, file_type, None)) button.connect("clicked", lambda button, file_path=file_path, file_type=file_type: self.preview_file(file_path, file_type, None))
file_container.append(button) file_container.append(button)
message_box.append(file_scroller) message_box.append(file_scroller)
message_box.append(message_text) message_box.append(message_text)
overlay = Gtk.Overlay(css_classes=["message"], name=id) overlay = Gtk.Overlay(css_classes=["message"], name=message_id)
overlay.set_child(message_box) overlay.set_child(message_box)
delete_button.connect("clicked", lambda button, element=overlay: self.delete_message(element)) delete_button.connect("clicked", lambda button, element=overlay: self.delete_message(element))
copy_button.connect("clicked", lambda button, element=overlay: self.copy_message(element)) copy_button.connect("clicked", lambda button, element=overlay: self.copy_message(element))
edit_button.connect("clicked", lambda button, element=overlay, textview=message_text, button_container=button_container: self.edit_message(element, textview, button_container)) edit_button.connect("clicked", lambda button, element=overlay, textview=message_text, button_container=button_container: self.edit_message(element, textview, button_container))
regenerate_button.connect('clicked', lambda button, id=id, bot_message_box=message_box, bot_message_button_container=button_container : self.regenerate_message(id, bot_message_box, bot_message_button_container)) regenerate_button.connect('clicked', lambda button, message_id=message_id, bot_message_box=message_box, bot_message_button_container=button_container : self.regenerate_message(message_id, bot_message_box, bot_message_button_container))
button_container.append(delete_button) button_container.append(delete_button)
button_container.append(copy_button) button_container.append(copy_button)
button_container.append(regenerate_button if bot else edit_button) button_container.append(regenerate_button if bot else edit_button)
@ -947,19 +970,22 @@ Generate a title following these rules:
def generate_datetime_format(self, dt:datetime) -> str: def generate_datetime_format(self, dt:datetime) -> str:
date = GLib.DateTime.new(GLib.DateTime.new_now_local().get_timezone(), dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second) date = GLib.DateTime.new(GLib.DateTime.new_now_local().get_timezone(), dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second)
current_date = GLib.DateTime.new_now_local() current_date = GLib.DateTime.new_now_local()
if date.format("%Y/%m/%d") == current_date.format("%Y/%m/%d"): return date.format("%H:%M %p") if date.format("%Y/%m/%d") == current_date.format("%Y/%m/%d"):
elif date.format("%Y") == current_date.format("%Y"): return date.format("%b %d, %H:%M %p") return date.format("%H:%M %p")
else: return date.format("%b %d %Y, %H:%M %p") elif date.format("%Y") == current_date.format("%Y"):
return date.format("%b %d, %H:%M %p")
else:
return date.format("%b %d %Y, %H:%M %p")
def update_bot_message(self, data, id): def update_bot_message(self, data, message_id):
if self.bot_message is None: if self.bot_message is None:
self.save_history() self.save_history()
sys.exit() sys.exit()
vadjustment = self.chat_window.get_vadjustment() vadjustment = self.chat_window.get_vadjustment()
if id not in self.chats["chats"][self.chats["selected_chat"]]["messages"] or vadjustment.get_value() + 50 >= vadjustment.get_upper() - vadjustment.get_page_size(): if message_id not in self.chats["chats"][self.chats["selected_chat"]]["messages"] or vadjustment.get_value() + 50 >= vadjustment.get_upper() - vadjustment.get_page_size():
GLib.idle_add(vadjustment.set_value, vadjustment.get_upper()) GLib.idle_add(vadjustment.set_value, vadjustment.get_upper())
if 'done' in data and data['done']: if 'done' in data and data['done']:
formated_date = GLib.markup_escape_text(self.generate_datetime_format(datetime.strptime(self.chats["chats"][self.chats["selected_chat"]]["messages"][id]["date"], '%Y/%m/%d %H:%M:%S'))) formated_date = GLib.markup_escape_text(self.generate_datetime_format(datetime.strptime(self.chats["chats"][self.chats["selected_chat"]]["messages"][message_id]["date"], '%Y/%m/%d %H:%M:%S')))
text = f"\n\n{self.convert_model_name(data['model'], 0)}\n<small>{formated_date}</small>" text = f"\n\n{self.convert_model_name(data['model'], 0)}\n<small>{formated_date}</small>"
GLib.idle_add(self.bot_message.insert_markup, self.bot_message.get_end_iter(), text, len(text.encode('utf-8'))) GLib.idle_add(self.bot_message.insert_markup, self.bot_message.get_end_iter(), text, len(text.encode('utf-8')))
self.save_history() self.save_history()
@ -968,11 +994,11 @@ Generate a title following these rules:
first_paragraph = self.bot_message.get_text(self.bot_message.get_start_iter(), self.bot_message.get_end_iter(), False).split("\n")[0] first_paragraph = self.bot_message.get_text(self.bot_message.get_start_iter(), self.bot_message.get_end_iter(), False).split("\n")[0]
GLib.idle_add(self.show_notification, self.chats["selected_chat"], first_paragraph[:100] + (first_paragraph[100:] and '...'), Gio.ThemedIcon.new("chat-message-new-symbolic")) GLib.idle_add(self.show_notification, self.chats["selected_chat"], first_paragraph[:100] + (first_paragraph[100:] and '...'), Gio.ThemedIcon.new("chat-message-new-symbolic"))
else: else:
if not self.chats["chats"][self.chats["selected_chat"]]["messages"][id]["content"] and self.loading_spinner: if not self.chats["chats"][self.chats["selected_chat"]]["messages"][message_id]["content"] and self.loading_spinner:
GLib.idle_add(self.chat_container.remove, self.loading_spinner) GLib.idle_add(self.chat_container.remove, self.loading_spinner)
self.loading_spinner = None self.loading_spinner = None
GLib.idle_add(self.bot_message.insert, self.bot_message.get_end_iter(), data['message']['content']) GLib.idle_add(self.bot_message.insert, self.bot_message.get_end_iter(), data['message']['content'])
self.chats["chats"][self.chats["selected_chat"]]["messages"][id]['content'] += data['message']['content'] self.chats["chats"][self.chats["selected_chat"]]["messages"][message_id]['content'] += data['message']['content']
def toggle_ui_sensitive(self, status): def toggle_ui_sensitive(self, status):
for element in [self.chat_list_box, self.add_chat_button, self.secondary_menu_button]: for element in [self.chat_list_box, self.add_chat_button, self.secondary_menu_button]:
@ -982,10 +1008,10 @@ Generate a title following these rules:
self.stop_button.set_visible(self.send_button.get_visible()) self.stop_button.set_visible(self.send_button.get_visible())
self.send_button.set_visible(not self.send_button.get_visible()) self.send_button.set_visible(not self.send_button.get_visible())
def run_message(self, messages, model, id): def run_message(self, messages, model, message_id):
logger.debug("Running message") logger.debug("Running message")
self.bot_message_button_container.set_visible(False) self.bot_message_button_container.set_visible(False)
self.chats["chats"][self.chats["selected_chat"]]["messages"][id] = { self.chats["chats"][self.chats["selected_chat"]]["messages"][message_id] = {
"role": "assistant", "role": "assistant",
"model": model, "model": model,
"date": datetime.now().strftime("%Y/%m/%d %H:%M:%S"), "date": datetime.now().strftime("%Y/%m/%d %H:%M:%S"),
@ -994,8 +1020,9 @@ Generate a title following these rules:
if self.regenerate_button: if self.regenerate_button:
GLib.idle_add(self.chat_container.remove, self.regenerate_button) GLib.idle_add(self.chat_container.remove, self.regenerate_button)
try: try:
response = connection_handler.stream_post(f"{connection_handler.url}/api/chat", data=json.dumps({"model": model, "messages": messages}), callback=lambda data, id=id: self.update_bot_message(data, id)) response = connection_handler.stream_post(f"{connection_handler.url}/api/chat", data=json.dumps({"model": model, "messages": messages}), callback=lambda data, message_id=message_id: self.update_bot_message(data, message_id))
if response.status_code != 200: raise Exception('Network Error') if response.status_code != 200:
raise Exception('Network Error')
GLib.idle_add(self.add_code_blocks) GLib.idle_add(self.add_code_blocks)
except Exception as e: except Exception as e:
GLib.idle_add(self.connection_error) GLib.idle_add(self.connection_error)
@ -1008,7 +1035,7 @@ Generate a title following these rules:
halign=3 halign=3
) )
GLib.idle_add(self.chat_container.append, self.regenerate_button) GLib.idle_add(self.chat_container.append, self.regenerate_button)
self.regenerate_button.connect('clicked', lambda button, id=id, bot_message_box=self.bot_message_box, bot_message_button_container=self.bot_message_button_container : self.regenerate_message(id, bot_message_box, bot_message_button_container)) self.regenerate_button.connect('clicked', lambda button, message_id=message_id, bot_message_box=self.bot_message_box, bot_message_button_container=self.bot_message_button_container : self.regenerate_message(message_id, bot_message_box, bot_message_button_container))
finally: finally:
GLib.idle_add(self.switch_send_stop_button) GLib.idle_add(self.switch_send_stop_button)
GLib.idle_add(self.toggle_ui_sensitive, True) GLib.idle_add(self.toggle_ui_sensitive, True)
@ -1016,7 +1043,7 @@ Generate a title following these rules:
GLib.idle_add(self.chat_container.remove, self.loading_spinner) GLib.idle_add(self.chat_container.remove, self.loading_spinner)
self.loading_spinner = None self.loading_spinner = None
def regenerate_message(self, id, bot_message_box, bot_message_button_container): def regenerate_message(self, message_id, bot_message_box, bot_message_button_container):
self.bot_message_button_container = bot_message_button_container self.bot_message_button_container = bot_message_button_container
self.bot_message_view = Gtk.TextView( self.bot_message_view = Gtk.TextView(
editable=False, editable=False,
@ -1028,11 +1055,12 @@ Generate a title following these rules:
css_classes=["flat"] css_classes=["flat"]
) )
self.bot_message = self.bot_message_view.get_buffer() self.bot_message = self.bot_message_view.get_buffer()
for widget in list(bot_message_box): bot_message_box.remove(widget) for widget in list(bot_message_box):
bot_message_box.remove(widget)
bot_message_box.append(self.bot_message_view) bot_message_box.append(self.bot_message_view)
history = self.convert_history_to_ollama()[:list(self.chats["chats"][self.chats["selected_chat"]]["messages"].keys()).index(id)] history = self.convert_history_to_ollama()[:list(self.chats["chats"][self.chats["selected_chat"]]["messages"].keys()).index(message_id)]
if id in self.chats["chats"][self.chats["selected_chat"]]["messages"]: if message_id in self.chats["chats"][self.chats["selected_chat"]]["messages"]:
del self.chats["chats"][self.chats["selected_chat"]]["messages"][id] del self.chats["chats"][self.chats["selected_chat"]]["messages"][message_id]
data = { data = {
"model": self.convert_model_name(self.model_drop_down.get_selected_item().get_string(), 1), "model": self.convert_model_name(self.model_drop_down.get_selected_item().get_string(), 1),
"messages": history, "messages": history,
@ -1041,7 +1069,7 @@ Generate a title following these rules:
} }
self.switch_send_stop_button() self.switch_send_stop_button()
self.toggle_ui_sensitive(False) self.toggle_ui_sensitive(False)
thread = threading.Thread(target=self.run_message, args=(data['messages'], data['model'], id)) thread = threading.Thread(target=self.run_message, args=(data['messages'], data['model'], message_id))
thread.start() thread.start()
def pull_model_update(self, data, model_name): def pull_model_update(self, data, model_name):
@ -1086,7 +1114,8 @@ Generate a title following these rules:
GLib.idle_add(self.pulling_model_list_box.set_visible, False) GLib.idle_add(self.pulling_model_list_box.set_visible, False)
def pull_model(self, model): def pull_model(self, model):
if model in list(self.pulling_models.keys()) or model in self.local_models or ":" not in model: return if model in list(self.pulling_models.keys()) or model in self.local_models or ":" not in model:
return
logger.info("Pulling model") logger.info("Pulling model")
self.pulling_model_list_box.set_visible(True) self.pulling_model_list_box.set_visible(True)
#self.pulling_model_list_box.connect('row_selected', lambda list_box, row: dialogs.stop_pull_model(self, row.get_name()) if row else None) #It isn't working for some reason #self.pulling_model_list_box.connect('row_selected', lambda list_box, row: dialogs.stop_pull_model(self, row.get_name()) if row else None) #It isn't working for some reason
@ -1175,9 +1204,9 @@ Generate a title following these rules:
if message: if message:
formated_date = GLib.markup_escape_text(self.generate_datetime_format(datetime.strptime(message['date'] + (":00" if message['date'].count(":") == 1 else ""), '%Y/%m/%d %H:%M:%S'))) formated_date = GLib.markup_escape_text(self.generate_datetime_format(datetime.strptime(message['date'] + (":00" if message['date'].count(":") == 1 else ""), '%Y/%m/%d %H:%M:%S')))
if message['role'] == 'user': if message['role'] == 'user':
self.show_message(message['content'], False, f"\n\n<small>{formated_date}</small>", message['images'] if 'images' in message else None, message['files'] if 'files' in message else None, id=key) self.show_message(message['content'], False, f"\n\n<small>{formated_date}</small>", message['images'] if 'images' in message else None, message['files'] if 'files' in message else None, message_id=key)
else: else:
self.show_message(message['content'], True, f"\n\n{self.convert_model_name(message['model'], 0)}\n<small>{formated_date}</small>", id=key) self.show_message(message['content'], True, f"\n\n{self.convert_model_name(message['model'], 0)}\n<small>{formated_date}</small>", message_id=key)
self.add_code_blocks() self.add_code_blocks()
self.bot_message = None self.bot_message = None
@ -1187,8 +1216,10 @@ Generate a title following these rules:
try: try:
with open(os.path.join(self.data_dir, "chats", "chats.json"), "r", encoding="utf-8") as f: with open(os.path.join(self.data_dir, "chats", "chats.json"), "r", encoding="utf-8") as f:
self.chats = json.load(f) self.chats = json.load(f)
if len(list(self.chats["chats"].keys())) == 0: self.chats["chats"][_("New Chat")] = {"messages": {}} if len(list(self.chats["chats"].keys())) == 0:
if "selected_chat" not in self.chats or self.chats["selected_chat"] not in self.chats["chats"]: self.chats["selected_chat"] = list(self.chats["chats"].keys())[0] self.chats["chats"][_("New Chat")] = {"messages": {}}
if "selected_chat" not in self.chats or self.chats["selected_chat"] not in self.chats["chats"]:
self.chats["selected_chat"] = list(self.chats["chats"].keys())[0]
if "order" not in self.chats: if "order" not in self.chats:
self.chats["order"] = [] self.chats["order"] = []
for chat_name in self.chats["chats"].keys(): for chat_name in self.chats["chats"].keys():
@ -1218,7 +1249,7 @@ Generate a title following these rules:
chat_name = f"{'.'.join(chat_name.split('.')[:-1])} {i+1}.{chat_name.split('.')[-1]}" chat_name = f"{'.'.join(chat_name.split('.')[:-1])} {i+1}.{chat_name.split('.')[-1]}"
break break
else: else:
if f"{chat_name} {i+1}" not in compare_list: if f"{chat_name} {i+1}" not in compare_list:
chat_name = f"{chat_name} {i+1}" chat_name = f"{chat_name} {i+1}"
break break
return chat_name return chat_name
@ -1248,7 +1279,8 @@ Generate a title following these rules:
def rename_chat(self, old_chat_name, new_chat_name, label_element): def rename_chat(self, old_chat_name, new_chat_name, label_element):
logger.info(f"Renaming chat \"{old_chat_name}\" -> \"{new_chat_name}\"") logger.info(f"Renaming chat \"{old_chat_name}\" -> \"{new_chat_name}\"")
new_chat_name = self.generate_numbered_name(new_chat_name, self.chats["chats"].keys()) new_chat_name = self.generate_numbered_name(new_chat_name, self.chats["chats"].keys())
if self.chats["selected_chat"] == old_chat_name: self.chats["selected_chat"] = new_chat_name if self.chats["selected_chat"] == old_chat_name:
self.chats["selected_chat"] = new_chat_name
self.chats["chats"][new_chat_name] = self.chats["chats"][old_chat_name] self.chats["chats"][new_chat_name] = self.chats["chats"][old_chat_name]
self.chats["order"][self.chats["order"].index(old_chat_name)] = new_chat_name self.chats["order"][self.chats["order"].index(old_chat_name)] = new_chat_name
del self.chats["chats"][old_chat_name] del self.chats["chats"][old_chat_name]
@ -1318,9 +1350,12 @@ Generate a title following these rules:
gesture.connect("released", self.chat_click_handler) gesture.connect("released", self.chat_click_handler)
chat_row.add_controller(gesture) chat_row.add_controller(gesture)
if append: self.chat_list_box.append(chat_row) if append:
else: self.chat_list_box.prepend(chat_row) self.chat_list_box.append(chat_row)
if select: self.chat_list_box.select_row(chat_row) else:
self.chat_list_box.prepend(chat_row)
if select:
self.chat_list_box.select_row(chat_row)
def update_chat_list(self): def update_chat_list(self):
self.chat_list_box.remove_all() self.chat_list_box.remove_all()
@ -1346,8 +1381,10 @@ Generate a title following these rules:
connection_handler.bearer_token = None connection_handler.bearer_token = None
connection_handler.url = f"http://127.0.0.1:{local_instance.port}" connection_handler.url = f"http://127.0.0.1:{local_instance.port}"
local_instance.start() local_instance.start()
if self.verify_connection() == False: self.connection_error() if self.verify_connection() == False:
else: self.remote_connection_switch.set_active(False) self.connection_error()
else:
self.remote_connection_switch.set_active(False)
def connection_error(self): def connection_error(self):
logger.error("Connection error") logger.error("Connection error")
@ -1365,13 +1402,16 @@ Generate a title following these rules:
if self.run_remote: if self.run_remote:
connection_handler.bearer_token = self.remote_bearer_token connection_handler.bearer_token = self.remote_bearer_token
connection_handler.url = self.remote_url connection_handler.url = self.remote_url
if self.verify_connection() == False: self.connection_error() if self.verify_connection() == False:
else: local_instance.stop() self.connection_error()
else:
local_instance.stop()
else: else:
connection_handler.bearer_token = None connection_handler.bearer_token = None
connection_handler.url = f"http://127.0.0.1:{local_instance.port}" connection_handler.url = f"http://127.0.0.1:{local_instance.port}"
local_instance.start() local_instance.start()
if self.verify_connection() == False: self.connection_error() if self.verify_connection() == False:
self.connection_error()
def on_replace_contents(self, file, result): def on_replace_contents(self, file, result):
file.replace_contents_finish(result) file.replace_contents_finish(result)
@ -1379,7 +1419,8 @@ Generate a title following these rules:
def on_export_chat(self, file_dialog, result, chat_name): def on_export_chat(self, file_dialog, result, chat_name):
file = file_dialog.save_finish(result) file = file_dialog.save_finish(result)
if not file: return if not file:
return
json_data = json.dumps({chat_name: self.chats["chats"][chat_name]}, indent=4).encode("UTF-8") json_data = json.dumps({chat_name: self.chats["chats"][chat_name]}, indent=4).encode("UTF-8")
with tempfile.TemporaryDirectory() as temp_dir: with tempfile.TemporaryDirectory() as temp_dir:
@ -1413,7 +1454,8 @@ Generate a title following these rules:
def on_chat_imported(self, file_dialog, result): def on_chat_imported(self, file_dialog, result):
file = file_dialog.open_finish(result) file = file_dialog.open_finish(result)
if not file: return if not file:
return
stream = file.read(None) stream = file.read(None)
data_stream = Gio.DataInputStream.new(stream) data_stream = Gio.DataInputStream.new(stream)
tar_content = data_stream.read_bytes(1024 * 1024, None) tar_content = data_stream.read_bytes(1024 * 1024, None)
@ -1483,7 +1525,8 @@ Generate a title following these rules:
return f.read() return f.read()
elif file_type == 'pdf': elif file_type == 'pdf':
reader = PdfReader(file_path) reader = PdfReader(file_path)
if len(reader.pages) == 0: return None if len(reader.pages) == 0:
return None
text = "" text = ""
for i, page in enumerate(reader.pages): for i, page in enumerate(reader.pages):
text += f"\n- Page {i}\n{page.extract_text(extraction_mode='layout', layout_mode_space_vertically=False)}\n" text += f"\n- Page {i}\n{page.extract_text(extraction_mode='layout', layout_mode_space_vertically=False)}\n"
@ -1494,7 +1537,8 @@ Generate a title following these rules:
button = self.attachments[name]['button'] button = self.attachments[name]['button']
button.get_parent().remove(button) button.get_parent().remove(button)
del self.attachments[name] del self.attachments[name]
if len(self.attachments) == 0: self.attachment_box.set_visible(False) if len(self.attachments) == 0:
self.attachment_box.set_visible(False)
def attach_file(self, file_path, file_type): def attach_file(self, file_path, file_type):
logger.debug(f"Attaching file: {file_path}") logger.debug(f"Attaching file: {file_path}")
@ -1652,7 +1696,8 @@ Generate a title following these rules:
self.seed_spin.set_value(self.model_tweaks['seed']) self.seed_spin.set_value(self.model_tweaks['seed'])
self.keep_alive_spin.set_value(self.model_tweaks['keep_alive']) self.keep_alive_spin.set_value(self.model_tweaks['keep_alive'])
#Overrides #Overrides
if "ollama_overrides" in data: local_instance.overrides = data['ollama_overrides'] if "ollama_overrides" in data:
local_instance.overrides = data['ollama_overrides']
for element in [ for element in [
self.override_HSA_OVERRIDE_GFX_VERSION, self.override_HSA_OVERRIDE_GFX_VERSION,
self.override_CUDA_VISIBLE_DEVICES, self.override_CUDA_VISIBLE_DEVICES,
@ -1678,7 +1723,8 @@ Generate a title following these rules:
local_instance.start() local_instance.start()
connection_handler.url = f"http://127.0.0.1:{local_instance.port}" connection_handler.url = f"http://127.0.0.1:{local_instance.port}"
self.welcome_dialog.present(self) self.welcome_dialog.present(self)
if self.verify_connection() is False: self.connection_error() if self.verify_connection() is False:
self.connection_error()
self.update_list_available_models() self.update_list_available_models()
self.load_history() self.load_history()
self.update_chat_list() self.update_chat_list()