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.add_footer(datetime.datetime.strptime(message_data['date'] + (":00" if message_data['date'].count(":") == 1 else ""), '%Y/%m/%d %H:%M:%S'))
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:
messages_dict = {}
@ -269,7 +269,7 @@ class chat_list(Gtk.ListBox):
tab = chat_tab(chat_window)
self.prepend(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_list_box.select_row(tab)
return chat_window
@ -399,5 +399,4 @@ class chat_list(Gtk.ListBox):
window.switch_send_stop_button(not row.chat_window.busy)
if len(row.chat_window.messages) > 0:
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('GtkSource', '5')
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 .. import connection_handler, available_models_descriptions, dialogs
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.set_tooltip_text(window.convert_model_name(model_name, 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"))
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):
model_row = Gtk.ListBoxRow(
child = Gtk.Label(
@ -103,8 +99,409 @@ class model_selector_button(Gtk.MenuButton):
self.get_popover().model_list_box.append(model_row)
self.change_model(model_name)
def get_model_list(self) -> list:
return [model.get_name() for model in list(self.get_popover().model_list_box)]
def remove_model(self, model_name:str):
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):
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
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":
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):
#self.pulling_model_list_box.unselect_all()
def stop_pull_model(self, pulling_model):
dialog = Adw.AlertDialog(
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"
)
dialog.add_response("cancel", _("Cancel"))
@ -134,19 +135,19 @@ def stop_pull_model(self, model_name):
dialog.choose(
parent = self.manage_models_dialog,
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
def delete_model_response(self, dialog, task, model_name):
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):
dialog = Adw.AlertDialog(
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"
)
dialog.add_response("cancel", _("Cancel"))

View File

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

View File

@ -116,9 +116,7 @@ class AlpacaWindow(Adw.ApplicationWindow):
model_link_button = Gtk.Template.Child()
manage_models_dialog = Gtk.Template.Child()
pulling_model_list_box = Gtk.Template.Child()
local_model_list_box = Gtk.Template.Child()
available_model_list_box = Gtk.Template.Child()
model_scroller = Gtk.Template.Child()
chat_list_container = Gtk.Template.Child()
chat_list_box = None
@ -284,7 +282,7 @@ class AlpacaWindow(Adw.ApplicationWindow):
for line in modelfile_raw.split('\n'):
if not line.startswith('SYSTEM') and not line.startswith('FROM'):
modelfile.append(line)
self.pulling_model_list_box.set_visible(True)
#self.pulling_model_list_box.set_visible(True)
model_row = Adw.ActionRow(
title = name
)
@ -309,7 +307,7 @@ class AlpacaWindow(Adw.ApplicationWindow):
self.pulling_models[name] = {"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)
#self.pulling_model_list_box.append(overlay)
self.navigation_view_manage_models.pop()
thread.start()
@ -334,22 +332,21 @@ class AlpacaWindow(Adw.ApplicationWindow):
@Gtk.Template.Callback()
def model_search_toggle(self, button):
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.local_model_list_box.set_visible(not button.get_active())
self.model_manager.pulling_list.set_visible(not button.get_active() and len(list(self.model_manager.pulling_list)) > 0)
self.model_manager.local_list.set_visible(not button.get_active() and len(list(self.model_manager.local_list)) > 0)
@Gtk.Template.Callback()
def model_search_changed(self, entry):
results = 0
for i, key in enumerate(self.available_models.keys()):
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))
if row.get_visible():
for model in list(self.model_manager.available_list):
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))
if model.get_visible():
results += 1
if entry.get_text() and results == 0:
self.available_model_list_box.set_visible(False)
self.no_results_page.set_visible(True)
self.model_scroller.set_visible(False)
else:
self.available_model_list_box.set_visible(True)
self.model_scroller.set_visible(True)
self.no_results_page.set_visible(False)
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 '...')
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):
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)
@ -555,7 +520,7 @@ Generate a title following these rules:
response = connection_handler.simple_get(f"{connection_handler.URL}/api/tags")
if response.status_code == 200:
self.save_server_config()
self.update_list_local_models()
#self.update_list_local_models()
return response.status_code == 200
except Exception as 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(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):
logger.debug("Saving history")
@ -793,8 +620,7 @@ Generate a title following these rules:
selected_chat = list(data['chats'])[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"]
self.model_selector.change_model(last_model_used)
self.model_manager.change_model(last_model_used)
for chat_name in data['chats']:
self.chat_list_box.append_chat(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())
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):
chat_row = gesture.get_widget()
popover = Gtk.PopoverMenu(
@ -1056,8 +872,6 @@ Generate a title following these rules:
message_widget.window = self
chat_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_container.set_child(self.chat_list_box)
@ -1145,5 +959,16 @@ Generate a title following these rules:
self.welcome_dialog.present(self)
if self.verify_connection() is False:
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()

View File

@ -440,65 +440,38 @@
</object>
</child>
<property name="content">
<object class="GtkScrolledWindow">
<object class="GtkBox">
<property name="hexpand">true</property>
<property name="vexpand">true</property>
<child>
<object class="GtkBox">
<property name="margin-start">12</property>
<property name="margin-end">12</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>
</child>
<child>
<object class="AdwStatusPage" id="no_results_page">
<property name="visible">false</property>
<property name="vexpand">true</property>
<property name="icon-name">edit-find-symbolic</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>
<object class="GtkScrolledWindow" id="model_scroller">
<property name="hexpand">true</property>
<property name="vexpand">true</property>
</object>
</child>
<child>
<object class="AdwStatusPage" id="no_results_page">
<property name="visible">false</property>
<property name="vexpand">true</property>
<property name="hexpand">true</property>
<property name="icon-name">edit-find-symbolic</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="child">
<object class="GtkButton">
<property name="tooltip-text">Pull Model From Name</property>
<property name="action-name">app.create_model_from_name</property>
<property name="halign">center</property>
<property name="child">
<object class="GtkButton">
<property name="tooltip-text">Pull Model From Name</property>
<property name="action-name">app.create_model_from_name</property>
<property name="child">
<object class="GtkLabel">
<property name="label" translatable="yes">Pull Model From Name</property>
</object>
</property>
<style>
<class name="suggested-action"/>
</style>
<object class="GtkLabel">
<property name="label" translatable="yes">Pull Model From Name</property>
</object>
</property>
<style>
<class name="suggested-action"/>
</style>
</object>
</child>
</property>
</object>
</child>
</object>
@ -542,6 +515,9 @@
<property name="margin-start">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>
<style>
<class name="dim-label"/>
</style>
</object>
</child>
<child>