New model manager (create model doesn't work yet)

This commit is contained in:
jeffser 2024-08-27 23:25:58 -06:00
parent bbf678cb75
commit d0735de129
7 changed files with 5334 additions and 5133 deletions

File diff suppressed because it is too large Load Diff

View File

@ -155,7 +155,7 @@ class chat(Gtk.ScrolledWindow):
message_element.set_text(message_data['content']) message_element.set_text(message_data['content'])
message_element.add_footer(datetime.datetime.strptime(message_data['date'] + (":00" if message_data['date'].count(":") == 1 else ""), '%Y/%m/%d %H:%M:%S')) message_element.add_footer(datetime.datetime.strptime(message_data['date'] + (":00" if message_data['date'].count(":") == 1 else ""), '%Y/%m/%d %H:%M:%S'))
else: else:
self.show_welcome_screen(len(window.model_selector.get_model_list()) > 0) self.show_welcome_screen(len(window.model_manager.get_model_list()) > 0)
def messages_to_dict(self) -> dict: def messages_to_dict(self) -> dict:
messages_dict = {} messages_dict = {}
@ -269,7 +269,7 @@ class chat_list(Gtk.ListBox):
tab = chat_tab(chat_window) tab = chat_tab(chat_window)
self.prepend(tab) self.prepend(tab)
self.tab_list.insert(0, tab) self.tab_list.insert(0, tab)
chat_window.show_welcome_screen(len(window.model_selector.get_model_list()) > 0) chat_window.show_welcome_screen(len(window.model_manager.get_model_list()) > 0)
window.chat_stack.add_child(chat_window) window.chat_stack.add_child(chat_window)
window.chat_list_box.select_row(tab) window.chat_list_box.select_row(tab)
return chat_window return chat_window
@ -399,5 +399,4 @@ class chat_list(Gtk.ListBox):
window.switch_send_stop_button(not row.chat_window.busy) window.switch_send_stop_button(not row.chat_window.busy)
if len(row.chat_window.messages) > 0: if len(row.chat_window.messages) > 0:
last_model_used = row.chat_window.messages[list(row.chat_window.messages)[-1]].model last_model_used = row.chat_window.messages[list(row.chat_window.messages)[-1]].model
window.model_selector.change_model(last_model_used) window.model_manager.change_model(last_model_used)

View File

@ -7,8 +7,9 @@ import gi
gi.require_version('Gtk', '4.0') gi.require_version('Gtk', '4.0')
gi.require_version('GtkSource', '5') gi.require_version('GtkSource', '5')
from gi.repository import Gtk, GObject, Gio, Adw, GtkSource, GLib, Gdk from gi.repository import Gtk, GObject, Gio, Adw, GtkSource, GLib, Gdk
import logging, os, datetime, re, shutil, threading import logging, os, datetime, re, shutil, threading, json, sys
from ..internal import config_dir, data_dir, cache_dir, source_dir from ..internal import config_dir, data_dir, cache_dir, source_dir
from .. import connection_handler, available_models_descriptions, dialogs
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -80,14 +81,9 @@ class model_selector_button(Gtk.MenuButton):
self.get_child().set_label(window.convert_model_name(model_name, 0)) self.get_child().set_label(window.convert_model_name(model_name, 0))
self.set_tooltip_text(window.convert_model_name(model_name, 0)) self.set_tooltip_text(window.convert_model_name(model_name, 0))
elif len(list(listbox)) == 0: elif len(list(listbox)) == 0:
self.get_child().set_label(_("Select a model")) self.get_child().set_label(_("Select a Model"))
self.set_tooltip_text(_("Select a Model")) self.set_tooltip_text(_("Select a Model"))
def get_model(self) -> str:
row = self.get_popover().model_list_box.get_selected_row()
if row:
return row.get_name()
def add_model(self, model_name:str): def add_model(self, model_name:str):
model_row = Gtk.ListBoxRow( model_row = Gtk.ListBoxRow(
child = Gtk.Label( child = Gtk.Label(
@ -103,8 +99,409 @@ class model_selector_button(Gtk.MenuButton):
self.get_popover().model_list_box.append(model_row) self.get_popover().model_list_box.append(model_row)
self.change_model(model_name) self.change_model(model_name)
def get_model_list(self) -> list: def remove_model(self, model_name:str):
return [model.get_name() for model in list(self.get_popover().model_list_box)] self.get_popover().model_list_box.remove(next((model for model in list(self.get_popover().model_list_box) if model.get_name() == model_name), None))
self.model_changed(self.get_popover().model_list_box)
print(self.get_popover().model_list_box.get_selected_row())
def clear_list(self): def clear_list(self):
self.get_popover().model_list_box.remove_all() self.get_popover().model_list_box.remove_all()
class pulling_model(Gtk.ListBoxRow):
__gtype_name__ = 'AlpacaPullingModel'
def __init__(self, model_name:str):
model_label = Gtk.Label(
css_classes=["heading"],
label=model_name.split(":")[0].replace("-", " ").title(),
hexpand=True,
halign=1
)
tag_label = Gtk.Label(
css_classes=["subtitle"],
label=model_name.split(":")[1]
)
self.prc_label = Gtk.Label(
css_classes=["subtitle", "numeric"],
label='50%',
hexpand=True,
halign=2
)
subtitle_box = Gtk.Box(
hexpand=True,
spacing=5,
orientation=0
)
subtitle_box.append(tag_label)
subtitle_box.append(self.prc_label)
self.progress_bar = Gtk.ProgressBar(
valign=2,
show_text=False,
css_classes=["horizontal"],
fraction=.5
)
description_box = Gtk.Box(
hexpand=True,
vexpand=True,
spacing=5,
orientation=1
)
description_box.append(model_label)
description_box.append(subtitle_box)
description_box.append(self.progress_bar)
delete_button = Gtk.Button(
icon_name = "media-playback-stop-symbolic",
vexpand = False,
valign = 3,
css_classes = ["destructive-action", "circular"],
tooltip_text = _("Stop Pulling '{}'").format(window.convert_model_name(model_name, 0))
)
delete_button.connect('clicked', lambda *_: dialogs.stop_pull_model(window, self))
container_box = Gtk.Box(
hexpand=True,
vexpand=True,
spacing=10,
orientation=0,
margin_top=10,
margin_bottom=10,
margin_start=10,
margin_end=10
)
container_box.append(description_box)
container_box.append(delete_button)
super().__init__(
child=container_box,
name=model_name
)
self.error = None
def update(self, data):
if not self.get_parent():
sys.exit()
if 'error' in data:
self.error = data['error']
if 'total' in data and 'completed' in data:
fraction = round(data['completed'] / data['total'], 4)
GLib.idle_add(self.prc_label.set_label, f"{fraction:05.2%}")
GLib.idle_add(self.progress_bar.set_fraction, fraction)
else:
GLib.idle_add(self.prc_label.set_label, data['status'])
GLib.idle_add(self.progress_bar.pulse)
class pulling_model_list(Gtk.ListBox):
__gtype_name__ = 'AlpacaPullingModelList'
def __init__(self):
super().__init__(
selection_mode=0,
css_classes=["boxed-list"],
visible=False
)
class local_model(Gtk.ListBoxRow):
__gtype_name__ = 'AlpacaLocalModel'
def __init__(self, model_name:str):
model_title = window.convert_model_name(model_name, 0)
model_label = Gtk.Label(
css_classes=["heading"],
label=model_title.split(" (")[0],
hexpand=True,
halign=1
)
tag_label = Gtk.Label(
css_classes=["subtitle"],
label=model_title.split(" (")[1][:-1],
hexpand=True,
halign=1
)
description_box = Gtk.Box(
hexpand=True,
vexpand=True,
spacing=5,
orientation=1
)
description_box.append(model_label)
description_box.append(tag_label)
delete_button = Gtk.Button(
icon_name = "user-trash-symbolic",
vexpand = False,
valign = 3,
css_classes = ["destructive-action", "circular"],
tooltip_text = _("Remove '{}'").format(window.convert_model_name(model_name, 0))
)
delete_button.connect('clicked', lambda *_, model_name=model_name: dialogs.delete_model(window, model_name))
container_box = Gtk.Box(
hexpand=True,
vexpand=True,
spacing=10,
orientation=0,
margin_top=10,
margin_bottom=10,
margin_start=10,
margin_end=10
)
container_box.append(description_box)
container_box.append(delete_button)
super().__init__(
child=container_box,
name=model_name
)
class local_model_list(Gtk.ListBox):
__gtype_name__ = 'AlpacaLocalModelList'
def __init__(self):
super().__init__(
selection_mode=0,
css_classes=["boxed-list"],
visible=False
)
def add_model(self, model_name:str):
model = local_model(model_name)
self.append(model)
if not self.get_visible():
self.set_visible(True)
def remove_model(self, model_name:str):
self.remove(next((model for model in list(self) if model.get_name() == model_name), None))
class available_model(Gtk.ListBoxRow):
__gtype_name__ = 'AlpacaAvailableModel'
def __init__(self, model_name:str, model_author:str, model_description:str, image_recognition:bool):
self.model_description = model_description
self.model_title = model_name.replace("-", " ").title()
self.model_author = model_author
self.image_recognition = image_recognition
model_label = Gtk.Label(
css_classes=["heading"],
label="<b>{}</b> <small>by {}</small>".format(self.model_title, self.model_author),
hexpand=True,
halign=1,
use_markup=True
)
description_label = Gtk.Label(
css_classes=["subtitle"],
label=self.model_description,
hexpand=True,
halign=1,
wrap=True,
wrap_mode=0,
)
image_recognition_indicator = Gtk.Button(
css_classes=["success", "pill", "image_recognition_indicator"],
child=Gtk.Label(
label=_("Image Recognition"),
css_classes=["subtitle"]
),
halign=1
)
description_box = Gtk.Box(
hexpand=True,
vexpand=True,
spacing=5,
orientation=1
)
description_box.append(model_label)
description_box.append(description_label)
if self.image_recognition: description_box.append(image_recognition_indicator)
container_box = Gtk.Box(
hexpand=True,
vexpand=True,
spacing=10,
orientation=0,
margin_top=10,
margin_bottom=10,
margin_start=10,
margin_end=10
)
next_icon = Gtk.Image.new_from_icon_name("go-next")
next_icon.update_property([4], [_("Enter download menu for {}").format(self.model_title)])
container_box.append(description_box)
container_box.append(next_icon)
super().__init__(
child=container_box,
name=model_name
)
gesture_click = Gtk.GestureClick.new()
gesture_click.connect("pressed", lambda *_: self.show_pull_menu())
event_controller_key = Gtk.EventControllerKey.new()
event_controller_key.connect("key-pressed", lambda controller, key, *_: self.show_pull_menu() if key in (Gdk.KEY_space, Gdk.KEY_Return) else None)
self.add_controller(gesture_click)
self.add_controller(event_controller_key)
def confirm_pull_model(self, model_name):
##TODO I really need that instance manager
threading.Thread(target=window.model_manager.pull_model, args=('http://0.0.0.0:11435', model_name)).start()
window.navigation_view_manage_models.pop()
def show_pull_menu(self):
with open(os.path.join(source_dir, 'available_models.json'), 'r', encoding="utf-8") as f:
data = json.load(f)
window.navigation_view_manage_models.push_by_tag('model_tags_page')
window.navigation_view_manage_models.find_page('model_tags_page').set_title(self.get_name().replace("-", " ").title())
window.model_link_button.set_name(data[self.get_name()]['url'])
window.model_link_button.set_tooltip_text(data[self.get_name()]['url'])
window.model_tag_list_box.remove_all()
tags = data[self.get_name()]['tags']
for tag_data in tags:
if f"{self.get_name()}:{tag_data[0]}" not in window.model_manager.get_model_list():
tag_row = Adw.ActionRow(
title = tag_data[0],
subtitle = tag_data[1],
name = f"{self.get_name()}:{tag_data[0]}"
)
download_icon = Gtk.Image.new_from_icon_name("folder-download-symbolic")
tag_row.add_suffix(download_icon)
download_icon.update_property([4], [_("Download {}:{}").format(self.get_name(), tag_data[0])])
gesture_click = Gtk.GestureClick.new()
gesture_click.connect("pressed", lambda *_, name=f"{self.get_name()}:{tag_data[0]}" : self.confirm_pull_model(name))
event_controller_key = Gtk.EventControllerKey.new()
event_controller_key.connect("key-pressed", lambda controller, key, *_, name=f"{self.get_name()}:{tag_data[0]}" : self.confirm_pull_model(name) if key in (Gdk.KEY_space, Gdk.KEY_Return) else None)
tag_row.add_controller(gesture_click)
tag_row.add_controller(event_controller_key)
window.model_tag_list_box.append(tag_row)
class available_model_list(Gtk.ListBox):
__gtype_name__ = 'AlpacaAvailableModelList'
def __init__(self):
super().__init__(
selection_mode=0,
css_classes=["boxed-list"],
visible=False
)
def add_model(self, model_name:str, model_author:str, model_description:str, image_recognition:bool):
model = available_model(model_name, model_author, model_description, image_recognition)
self.append(model)
if not self.get_visible():
self.set_visible(True)
class model_manager_container(Gtk.Box):
__gtype_name__ = 'AlpacaModelManagerContainer'
def __init__(self):
super().__init__(
margin_top=12,
margin_bottom=12,
margin_start=12,
margin_end=12,
spacing=12,
orientation=1
)
self.pulling_list = pulling_model_list()
self.append(self.pulling_list)
self.local_list = local_model_list()
self.append(self.local_list)
self.available_list = available_model_list()
self.append(self.available_list)
self.model_selector = model_selector_button()
window.header_bar.set_title_widget(self.model_selector)
def add_local_model(self, model_name:str):
self.local_list.add_model(model_name)
if not self.local_list.get_visible():
self.local_list.set_visible(True)
self.model_selector.add_model(model_name)
def remove_local_model(self, model_name:str):
logger.debug("Deleting model")
response = connection_handler.simple_delete(f"{connection_handler.URL}/api/delete", data={"name": model_name})
if response.status_code == 200:
self.local_list.remove_model(model_name)
if len(list(self.local_list)) == 0:
self.local_list.set_visible(False)
self.model_selector.remove_model(model_name)
window.show_toast(_("Model deleted successfully"), window.manage_models_overlay)
else:
window.manage_models_dialog.close()
window.connection_error()
def get_selected_model(self) -> str:
row = self.model_selector.get_popover().model_list_box.get_selected_row()
if row:
return row.get_name()
def get_model_list(self) -> list:
return [model.get_name() for model in list(self.model_selector.get_popover().model_list_box)]
#Should only be called when the app starts
def update_local_list(self):
response = connection_handler.simple_get(f"{connection_handler.URL}/api/tags")
if response.status_code == 200:
self.local_list.remove_all()
data = json.loads(response.text)
if len(data['models']) == 0:
self.local_list.set_visible(False)
else:
self.local_list.set_visible(True)
for model in data['models']:
self.add_local_model(model['name'])
else:
window.connection_error()
#Should only be called when the app starts
def update_available_list(self):
with open(os.path.join(source_dir, 'available_models.json'), 'r', encoding="utf-8") as f:
for name, model_info in json.load(f).items():
self.available_list.add_model(name, model_info['author'], available_models_descriptions.descriptions[name], model_info['image'])
def change_model(self, model_name:str):
self.model_selector.change_model(model_name)
#threading.Thread(target=self.pulling_list.pull_model, args=(url, model_name, modelfile)).start()
#Important: Call this using a thread, if not the app crashes
def pull_model(self, url:str, model_name:str, modelfile:str=None): ##TODO, once you make an instance manager remove the url from this
if ':' in model_name and model_name not in [model.get_name() for model in list(self.pulling_list)]:
logger.info("Pulling model: {}".format(model_name))
model = pulling_model(model_name)
self.pulling_list.append(model)
if not self.pulling_list.get_visible():
GLib.idle_add(self.pulling_list.set_visible, True)
if modelfile:
response = connection_handler.stream_post("{}/api/create".format(url), data=json.dumps({"name": model_name, "modelfile": modelfile}), callback=lambda data: model.update(data))
else:
response = connection_handler.stream_post("{}/api/pull".format(url), data=json.dumps({"name": model_name}), callback=lambda data: model.update(data))
if response.status_code == 200 and not model.error:
GLib.idle_add(window.show_notification, _("Task Complete"), _("Model '{}' pulled successfully.").format(model_name), Gio.ThemedIcon.new("emblem-ok-symbolic"))
GLib.idle_add(window.show_toast, _("Model '{}' pulled successfully.").format(model), window.manage_models_overlay)
self.add_local_model(model_name)
elif response.status_code == 200:
GLib.idle_add(window.show_notification, _("Pull Model Error"), _("Failed to pull model '{}': {}").format(model_name, model.error), Gio.ThemedIcon.new("dialog-error-symbolic"))
GLib.idle_add(window.show_toast, _("Error pulling '{}': {}").format(model, model.error), window.manage_models_overlay)
else:
GLib.idle_add(window.show_notification, _("Pull Model Error"), _("Failed to pull model '{}' due to network error.").format(model_name), Gio.ThemedIcon.new("dialog-error-symbolic"))
GLib.idle_add(window.show_toast, _("Error pulling '{}'").format(model), window.manage_models_overlay)
GLib.idle_add(window.manage_models_dialog.close)
GLib.idle_add(window.connection_error)
self.pulling_list.remove(model)
if len(list(self.pulling_list)) == 0:
GLib.idle_add(self.pulling_list.set_visible, False)

View File

@ -116,15 +116,16 @@ def new_chat(self):
# STOP PULL MODEL | WORKS # STOP PULL MODEL | WORKS
def stop_pull_model_response(self, dialog, task, model_name): def stop_pull_model_response(self, dialog, task, pulling_model):
if dialog.choose_finish(task) == "stop": if dialog.choose_finish(task) == "stop":
self.stop_pull_model(model_name) if len(list(pulling_model.get_parent())) == 1:
pulling_model.get_parent().set_visible(False)
pulling_model.get_parent().remove(pulling_model)
def stop_pull_model(self, model_name): def stop_pull_model(self, pulling_model):
#self.pulling_model_list_box.unselect_all()
dialog = Adw.AlertDialog( dialog = Adw.AlertDialog(
heading=_("Stop Download?"), heading=_("Stop Download?"),
body=_("Are you sure you want to stop pulling '{} ({})'?").format(model_name.split(":")[0].capitalize(), model_name.split(":")[1]), body=_("Are you sure you want to stop pulling '{}'?").format(self.convert_model_name(pulling_model.get_name(), 0)),
close_response="cancel" close_response="cancel"
) )
dialog.add_response("cancel", _("Cancel")) dialog.add_response("cancel", _("Cancel"))
@ -134,19 +135,19 @@ def stop_pull_model(self, model_name):
dialog.choose( dialog.choose(
parent = self.manage_models_dialog, parent = self.manage_models_dialog,
cancellable = None, cancellable = None,
callback = lambda dialog, task, model_name = model_name: stop_pull_model_response(self, dialog, task, model_name) callback = lambda dialog, task, model=pulling_model: stop_pull_model_response(self, dialog, task, model)
) )
# DELETE MODEL | WORKS # DELETE MODEL | WORKS
def delete_model_response(self, dialog, task, model_name): def delete_model_response(self, dialog, task, model_name):
if dialog.choose_finish(task) == "delete": if dialog.choose_finish(task) == "delete":
self.delete_model(model_name) self.model_manager.remove_local_model(model_name)
def delete_model(self, model_name): def delete_model(self, model_name):
dialog = Adw.AlertDialog( dialog = Adw.AlertDialog(
heading=_("Delete Model?"), heading=_("Delete Model?"),
body=_("Are you sure you want to delete '{}'?").format(model_name), body=_("Are you sure you want to delete '{}'?").format(self.convert_model_name(model_name, 0)),
close_response="cancel" close_response="cancel"
) )
dialog.add_response("cancel", _("Cancel")) dialog.add_response("cancel", _("Cancel"))

View File

@ -31,3 +31,6 @@
stacksidebar { stacksidebar {
border: none; border: none;
} }
.image_recognition_indicator {
padding: 0px 10px;
}

View File

@ -116,9 +116,7 @@ class AlpacaWindow(Adw.ApplicationWindow):
model_link_button = Gtk.Template.Child() model_link_button = Gtk.Template.Child()
manage_models_dialog = Gtk.Template.Child() manage_models_dialog = Gtk.Template.Child()
pulling_model_list_box = Gtk.Template.Child() model_scroller = Gtk.Template.Child()
local_model_list_box = Gtk.Template.Child()
available_model_list_box = Gtk.Template.Child()
chat_list_container = Gtk.Template.Child() chat_list_container = Gtk.Template.Child()
chat_list_box = None chat_list_box = None
@ -284,7 +282,7 @@ class AlpacaWindow(Adw.ApplicationWindow):
for line in modelfile_raw.split('\n'): for line in modelfile_raw.split('\n'):
if not line.startswith('SYSTEM') and not line.startswith('FROM'): if not line.startswith('SYSTEM') and not line.startswith('FROM'):
modelfile.append(line) modelfile.append(line)
self.pulling_model_list_box.set_visible(True) #self.pulling_model_list_box.set_visible(True)
model_row = Adw.ActionRow( model_row = Adw.ActionRow(
title = name title = name
) )
@ -309,7 +307,7 @@ class AlpacaWindow(Adw.ApplicationWindow):
self.pulling_models[name] = {"row": model_row, "progress_bar": progress_bar, "overlay": overlay} self.pulling_models[name] = {"row": model_row, "progress_bar": progress_bar, "overlay": overlay}
overlay.set_child(model_row) overlay.set_child(model_row)
overlay.add_overlay(progress_bar) overlay.add_overlay(progress_bar)
self.pulling_model_list_box.append(overlay) #self.pulling_model_list_box.append(overlay)
self.navigation_view_manage_models.pop() self.navigation_view_manage_models.pop()
thread.start() thread.start()
@ -334,22 +332,21 @@ class AlpacaWindow(Adw.ApplicationWindow):
@Gtk.Template.Callback() @Gtk.Template.Callback()
def model_search_toggle(self, button): def model_search_toggle(self, button):
self.model_searchbar.set_search_mode(button.get_active()) self.model_searchbar.set_search_mode(button.get_active())
self.pulling_model_list_box.set_visible(not button.get_active() and len(self.pulling_models) > 0) self.model_manager.pulling_list.set_visible(not button.get_active() and len(list(self.model_manager.pulling_list)) > 0)
self.local_model_list_box.set_visible(not button.get_active()) self.model_manager.local_list.set_visible(not button.get_active() and len(list(self.model_manager.local_list)) > 0)
@Gtk.Template.Callback() @Gtk.Template.Callback()
def model_search_changed(self, entry): def model_search_changed(self, entry):
results = 0 results = 0
for i, key in enumerate(self.available_models.keys()): for model in list(self.model_manager.available_list):
row = self.available_model_list_box.get_row_at_index(i) model.set_visible(re.search(entry.get_text(), '{} {} {} {} {}'.format(model.get_name(), model.model_title, model.model_author, model.model_description, (_('image') if model.image_recognition else '')), 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 model.get_visible():
if row.get_visible():
results += 1 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.no_results_page.set_visible(True) self.no_results_page.set_visible(True)
self.model_scroller.set_visible(False)
else: else:
self.available_model_list_box.set_visible(True) self.model_scroller.set_visible(True)
self.no_results_page.set_visible(False) self.no_results_page.set_visible(False)
def verify_if_image_can_be_used(self): def verify_if_image_can_be_used(self):
@ -514,38 +511,6 @@ Generate a title following these rules:
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.chat_list_box.rename_chat(old_chat_name, new_chat_name) self.chat_list_box.rename_chat(old_chat_name, new_chat_name)
def update_list_local_models(self):
logger.debug("Updating list of local models")
response = connection_handler.simple_get(f"{connection_handler.URL}/api/tags")
self.model_selector.clear_list()
if response.status_code == 200:
self.local_model_list_box.remove_all()
if len(json.loads(response.text)['models']) == 0:
self.local_model_list_box.set_visible(False)
else:
self.local_model_list_box.set_visible(True)
for model in json.loads(response.text)['models']:
model_name = self.convert_model_name(model["name"], 0)
model_row = Adw.ActionRow(
title = "<b>{}</b>".format(model_name.split(" (")[0]),
subtitle = model_name.split(" (")[1][:-1]
)
button = Gtk.Button(
icon_name = "user-trash-symbolic",
vexpand = False,
valign = 3,
css_classes = ["error", "circular"],
tooltip_text = _("Remove '{}'").format(model_name)
)
button.connect("clicked", lambda button=button, model_name=model["name"]: dialogs.delete_model(self, model_name))
model_row.add_suffix(button)
self.local_model_list_box.append(model_row)
self.model_selector.add_model(model["name"])
else:
self.connection_error()
def save_server_config(self): def save_server_config(self):
with open(os.path.join(self.config_dir, "server.json"), "w+", encoding="utf-8") as f: with open(os.path.join(self.config_dir, "server.json"), "w+", encoding="utf-8") as f:
json.dump({'remote_url': self.remote_url, 'remote_bearer_token': self.remote_bearer_token, 'run_remote': self.run_remote, 'local_port': local_instance.port, 'run_on_background': self.run_on_background, 'model_tweaks': self.model_tweaks, 'ollama_overrides': local_instance.overrides}, f, indent=6) json.dump({'remote_url': self.remote_url, 'remote_bearer_token': self.remote_bearer_token, 'run_remote': self.run_remote, 'local_port': local_instance.port, 'run_on_background': self.run_on_background, 'model_tweaks': self.model_tweaks, 'ollama_overrides': local_instance.overrides}, f, indent=6)
@ -555,7 +520,7 @@ Generate a title following these rules:
response = connection_handler.simple_get(f"{connection_handler.URL}/api/tags") response = connection_handler.simple_get(f"{connection_handler.URL}/api/tags")
if response.status_code == 200: if response.status_code == 200:
self.save_server_config() self.save_server_config()
self.update_list_local_models() #self.update_list_local_models()
return response.status_code == 200 return response.status_code == 200
except Exception as e: except Exception as e:
logger.error(e) logger.error(e)
@ -613,144 +578,6 @@ Generate a title following these rules:
GLib.idle_add(chat.show_regenerate_button, message_element) GLib.idle_add(chat.show_regenerate_button, message_element)
GLib.idle_add(self.connection_error) GLib.idle_add(self.connection_error)
def pull_model_update(self, data, model_name):
if 'error' in data:
self.pulling_models[model_name]['error'] = data['error']
return
if model_name in self.pulling_models.keys():
if 'completed' in data and 'total' in data:
GLib.idle_add(self.pulling_models[model_name]['row'].set_subtitle, '<tt>{}%</tt>'.format(round(data['completed'] / data['total'] * 100, 2)))
GLib.idle_add(self.pulling_models[model_name]['progress_bar'].set_fraction, (data['completed'] / data['total']))
else:
GLib.idle_add(self.pulling_models[model_name]['row'].set_subtitle, '{}'.format(data['status'].capitalize()))
GLib.idle_add(self.pulling_models[model_name]['progress_bar'].pulse)
else:
if len(list(self.pulling_models.keys())) == 0:
GLib.idle_add(self.pulling_model_list_box.set_visible, False)
def pull_model_process(self, model, modelfile):
if modelfile:
data = {"name": model, "modelfile": modelfile}
response = connection_handler.stream_post(f"{connection_handler.URL}/api/create", data=json.dumps(data), callback=lambda data, model_name=model: self.pull_model_update(data, model_name))
else:
data = {"name": model}
response = connection_handler.stream_post(f"{connection_handler.URL}/api/pull", data=json.dumps(data), callback=lambda data, model_name=model: self.pull_model_update(data, model_name))
GLib.idle_add(self.update_list_local_models)
if response.status_code == 200 and 'error' not in self.pulling_models[model]:
GLib.idle_add(self.show_notification, _("Task Complete"), _("Model '{}' pulled successfully.").format(model), Gio.ThemedIcon.new("emblem-ok-symbolic"))
GLib.idle_add(self.show_toast, _("Model '{}' pulled successfully.").format(model), self.manage_models_overlay)
elif response.status_code == 200 and self.pulling_models[model]['error']:
GLib.idle_add(self.show_notification, _("Pull Model Error"), _("Failed to pull model '{}': {}").format(model, self.pulling_models[model]['error']), Gio.ThemedIcon.new("dialog-error-symbolic"))
GLib.idle_add(self.show_toast, _("Error pulling '{}': {}").format(model, self.pulling_models[model]['error']), self.manage_models_overlay)
else:
GLib.idle_add(self.show_notification, _("Pull Model Error"), _("Failed to pull model '{}' due to network error.").format(model), Gio.ThemedIcon.new("dialog-error-symbolic"))
GLib.idle_add(self.show_toast, _("Error pulling '{}'").format(model), self.manage_models_overlay)
GLib.idle_add(self.manage_models_dialog.close)
GLib.idle_add(self.connection_error)
GLib.idle_add(self.pulling_models[model]['overlay'].get_parent().get_parent().remove, self.pulling_models[model]['overlay'].get_parent())
del self.pulling_models[model]
if len(list(self.pulling_models.keys())) == 0:
GLib.idle_add(self.pulling_model_list_box.set_visible, False)
def pull_model(self, model):
if model in self.pulling_models.keys() or model in self.model_selector.get_model_list() or ":" not in model:
return
logger.info("Pulling model")
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
model_name = self.convert_model_name(model, 0)
model_row = Adw.ActionRow(
title = "<b>{}</b> <small>{}</small>".format(model_name.split(" (")[0], model_name.split(" (")[1][:-1]),
name = model
)
thread = threading.Thread(target=self.pull_model_process, kwargs={"model": model, "modelfile": None})
overlay = Gtk.Overlay()
progress_bar = Gtk.ProgressBar(
valign = 2,
show_text = False,
margin_start = 10,
margin_end = 10,
css_classes = ["osd", "horizontal", "bottom"]
)
button = Gtk.Button(
icon_name = "media-playback-stop-symbolic",
vexpand = False,
valign = 3,
css_classes = ["error", "circular"],
tooltip_text = _("Stop Pulling '{}'").format(model_name)
)
button.connect("clicked", lambda button, model_name=model : dialogs.stop_pull_model(self, model_name))
model_row.add_suffix(button)
self.pulling_models[model] = {"row": model_row, "progress_bar": progress_bar, "overlay": overlay}
overlay.set_child(model_row)
overlay.add_overlay(progress_bar)
self.pulling_model_list_box.append(overlay)
thread.start()
def confirm_pull_model(self, model_name):
logger.debug("Confirming pull model")
self.navigation_view_manage_models.pop()
self.model_tag_list_box.unselect_all()
self.pull_model(model_name)
def list_available_model_tags(self, model_name):
logger.debug("Listing available model tags")
self.navigation_view_manage_models.push_by_tag('model_tags_page')
self.navigation_view_manage_models.find_page('model_tags_page').set_title(model_name.replace("-", " ").title())
self.model_link_button.set_name(self.available_models[model_name]['url'])
self.model_link_button.set_tooltip_text(self.available_models[model_name]['url'])
self.available_model_list_box.unselect_all()
self.model_tag_list_box.remove_all()
tags = self.available_models[model_name]['tags']
for tag_data in tags:
if f"{model_name}:{tag_data[0]}" not in self.model_selector.get_model_list():
tag_row = Adw.ActionRow(
title = tag_data[0],
subtitle = tag_data[1],
name = f"{model_name}:{tag_data[0]}"
)
download_icon = Gtk.Image.new_from_icon_name("folder-download-symbolic")
tag_row.add_suffix(download_icon)
download_icon.update_property([4], [_("Download {}:{}").format(model_name, tag_data[0])])
gesture_click = Gtk.GestureClick.new()
gesture_click.connect("pressed", lambda *_, name=f"{model_name}:{tag_data[0]}" : self.confirm_pull_model(name))
event_controller_key = Gtk.EventControllerKey.new()
event_controller_key.connect("key-pressed", lambda controller, key, *_, name=f"{model_name}:{tag_data[0]}" : self.confirm_pull_model(name) if key in (Gdk.KEY_space, Gdk.KEY_Return) else None)
tag_row.add_controller(gesture_click)
tag_row.add_controller(event_controller_key)
self.model_tag_list_box.append(tag_row)
return True
def update_list_available_models(self):
logger.debug("Updating list of available models")
self.available_model_list_box.remove_all()
for name, model_info in self.available_models.items():
model = Adw.ActionRow(
title = "<b>{}</b> <small>by {}</small>".format(name.replace("-", " ").title(), model_info['author']),
subtitle = available_models_descriptions.descriptions[name] + ("\n\n<b>{}</b>".format(_("Image Recognition")) if model_info['image'] else ""),
name = name
)
next_icon = Gtk.Image.new_from_icon_name("go-next")
next_icon.set_margin_start(5)
next_icon.update_property([4], [_("Enter download menu for {}").format(name.replace("-", ""))])
model.add_suffix(next_icon)
gesture_click = Gtk.GestureClick.new()
gesture_click.connect("pressed", lambda *_, name=name : self.list_available_model_tags(name))
event_controller_key = Gtk.EventControllerKey.new()
event_controller_key.connect("key-pressed", lambda controller, key, *_, name=name : self.list_available_model_tags(name) if key in (Gdk.KEY_space, Gdk.KEY_Return) else None)
model.add_controller(gesture_click)
model.add_controller(event_controller_key)
self.available_model_list_box.append(model)
def save_history(self, chat:chat_widget.chat=None): def save_history(self, chat:chat_widget.chat=None):
logger.debug("Saving history") logger.debug("Saving history")
@ -793,8 +620,7 @@ Generate a title following these rules:
selected_chat = list(data['chats'])[0] selected_chat = list(data['chats'])[0]
if len(data['chats'][selected_chat]['messages'].keys()) > 0: if len(data['chats'][selected_chat]['messages'].keys()) > 0:
last_model_used = data['chats'][selected_chat]['messages'][list(data["chats"][selected_chat]["messages"])[-1]]["model"] last_model_used = data['chats'][selected_chat]['messages'][list(data["chats"][selected_chat]["messages"])[-1]]["model"]
self.model_selector.change_model(last_model_used) self.model_manager.change_model(last_model_used)
for chat_name in data['chats']: for chat_name in data['chats']:
self.chat_list_box.append_chat(chat_name) self.chat_list_box.append_chat(chat_name)
chat_container = self.chat_list_box.get_chat_by_name(chat_name) chat_container = self.chat_list_box.get_chat_by_name(chat_name)
@ -830,16 +656,6 @@ Generate a title following these rules:
self.pulling_models[model_name]['overlay'].get_parent().get_parent().remove(self.pulling_models[model_name]['overlay'].get_parent()) self.pulling_models[model_name]['overlay'].get_parent().get_parent().remove(self.pulling_models[model_name]['overlay'].get_parent())
del self.pulling_models[model_name] del self.pulling_models[model_name]
def delete_model(self, model_name):
logger.debug("Deleting model")
response = connection_handler.simple_delete(f"{connection_handler.URL}/api/delete", data={"name": model_name})
self.update_list_local_models()
if response.status_code == 200:
self.show_toast(_("Model deleted successfully"), self.manage_models_overlay)
else:
self.manage_models_dialog.close()
self.connection_error()
def chat_click_handler(self, gesture, n_press, x, y): def chat_click_handler(self, gesture, n_press, x, y):
chat_row = gesture.get_widget() chat_row = gesture.get_widget()
popover = Gtk.PopoverMenu( popover = Gtk.PopoverMenu(
@ -1056,8 +872,6 @@ Generate a title following these rules:
message_widget.window = self message_widget.window = self
chat_widget.window = self chat_widget.window = self
model_widget.window = self model_widget.window = self
self.model_selector = model_widget.model_selector_button()
self.header_bar.set_title_widget(self.model_selector)
self.chat_list_box = chat_widget.chat_list() self.chat_list_box = chat_widget.chat_list()
self.chat_list_container.set_child(self.chat_list_box) self.chat_list_container.set_child(self.chat_list_box)
@ -1145,5 +959,16 @@ Generate a title following these rules:
self.welcome_dialog.present(self) self.welcome_dialog.present(self)
if self.verify_connection() is False: if self.verify_connection() is False:
self.connection_error() self.connection_error()
self.update_list_available_models() self.model_manager = model_widget.model_manager_container()
self.model_scroller.set_child(self.model_manager)
self.model_manager.update_local_list()
self.model_manager.update_available_list()
"""
response = connection_handler.simple_get(f"{connection_handler.URL}/api/tags")
self.model_selector.clear_list()
to update local models
"""
self.load_history() self.load_history()

View File

@ -440,46 +440,20 @@
</object> </object>
</child> </child>
<property name="content"> <property name="content">
<object class="GtkScrolledWindow"> <object class="GtkBox">
<property name="hexpand">true</property> <property name="hexpand">true</property>
<property name="vexpand">true</property> <property name="vexpand">true</property>
<child> <child>
<object class="GtkBox"> <object class="GtkScrolledWindow" id="model_scroller">
<property name="margin-start">12</property> <property name="hexpand">true</property>
<property name="margin-end">12</property> <property name="vexpand">true</property>
<property name="margin-top">12</property>
<property name="margin-bottom">12</property>
<property name="orientation">1</property>
<property name="spacing">12</property>
<child>
<object class="GtkListBox" id="pulling_model_list_box">
<property name="visible">false</property>
<property name="selection-mode">none</property>
<style>
<class name="boxed-list"/>
</style>
</object>
</child>
<child>
<object class="GtkListBox" id="local_model_list_box">
<property name="selection-mode">none</property>
<style>
<class name="boxed-list"/>
</style>
</object>
</child>
<child>
<object class="GtkListBox" id="available_model_list_box">
<property name="selection-mode">none</property>
<style>
<class name="boxed-list"/>
</style>
</object> </object>
</child> </child>
<child> <child>
<object class="AdwStatusPage" id="no_results_page"> <object class="AdwStatusPage" id="no_results_page">
<property name="visible">false</property> <property name="visible">false</property>
<property name="vexpand">true</property> <property name="vexpand">true</property>
<property name="hexpand">true</property>
<property name="icon-name">edit-find-symbolic</property> <property name="icon-name">edit-find-symbolic</property>
<property name="title" translatable="yes">No Models Found</property> <property name="title" translatable="yes">No Models Found</property>
<property name="description" translatable="yes">Try a different search or pull an unlisted model from it's name</property> <property name="description" translatable="yes">Try a different search or pull an unlisted model from it's name</property>
@ -487,6 +461,7 @@
<object class="GtkButton"> <object class="GtkButton">
<property name="tooltip-text">Pull Model From Name</property> <property name="tooltip-text">Pull Model From Name</property>
<property name="action-name">app.create_model_from_name</property> <property name="action-name">app.create_model_from_name</property>
<property name="halign">center</property>
<property name="child"> <property name="child">
<object class="GtkLabel"> <object class="GtkLabel">
<property name="label" translatable="yes">Pull Model From Name</property> <property name="label" translatable="yes">Pull Model From Name</property>
@ -500,8 +475,6 @@
</object> </object>
</child> </child>
</object> </object>
</child>
</object>
</property> </property>
</object> </object>
</property> </property>
@ -542,6 +515,9 @@
<property name="margin-start">12</property> <property name="margin-start">12</property>
<property name="margin-end">12</property> <property name="margin-end">12</property>
<property name="label" translatable="yes">By downloading this model you accept the license agreement available on the model's website.</property> <property name="label" translatable="yes">By downloading this model you accept the license agreement available on the model's website.</property>
<style>
<class name="dim-label"/>
</style>
</object> </object>
</child> </child>
<child> <child>