Added support for background pulling of models
This commit is contained in:
parent
454aeac5e2
commit
d540114ae5
139
src/window.py
139
src/window.py
@ -21,7 +21,7 @@ import gi
|
|||||||
gi.require_version('GtkSource', '5')
|
gi.require_version('GtkSource', '5')
|
||||||
gi.require_version('GdkPixbuf', '2.0')
|
gi.require_version('GdkPixbuf', '2.0')
|
||||||
from gi.repository import Adw, Gtk, Gdk, GLib, GtkSource, Gio, GdkPixbuf
|
from gi.repository import Adw, Gtk, Gdk, GLib, GtkSource, Gio, GdkPixbuf
|
||||||
import json, requests, threading, os, re, base64
|
import json, requests, threading, os, re, base64, sys
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
@ -35,6 +35,7 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
#Variables
|
#Variables
|
||||||
ollama_url = None
|
ollama_url = None
|
||||||
local_models = []
|
local_models = []
|
||||||
|
pulling_models = {}
|
||||||
chats = {"chats": {"New Chat": {"messages": []}}, "selected_chat": "New Chat"}
|
chats = {"chats": {"New Chat": {"messages": []}}, "selected_chat": "New Chat"}
|
||||||
attached_image = {"path": None, "base64": None}
|
attached_image = {"path": None, "base64": None}
|
||||||
first_time_setup = False
|
first_time_setup = False
|
||||||
@ -50,7 +51,6 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
connection_next_button = Gtk.Template.Child()
|
connection_next_button = Gtk.Template.Child()
|
||||||
connection_url_entry = Gtk.Template.Child()
|
connection_url_entry = Gtk.Template.Child()
|
||||||
main_overlay = Gtk.Template.Child()
|
main_overlay = Gtk.Template.Child()
|
||||||
pull_overlay = Gtk.Template.Child()
|
|
||||||
manage_models_overlay = Gtk.Template.Child()
|
manage_models_overlay = Gtk.Template.Child()
|
||||||
connection_overlay = Gtk.Template.Child()
|
connection_overlay = Gtk.Template.Child()
|
||||||
chat_container = Gtk.Template.Child()
|
chat_container = Gtk.Template.Child()
|
||||||
@ -64,10 +64,10 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
|
|
||||||
manage_models_button = Gtk.Template.Child()
|
manage_models_button = Gtk.Template.Child()
|
||||||
manage_models_dialog = Gtk.Template.Child()
|
manage_models_dialog = Gtk.Template.Child()
|
||||||
available_model_list_box = Gtk.Template.Child()
|
pulling_model_list_box = Gtk.Template.Child()
|
||||||
local_model_list_box = Gtk.Template.Child()
|
local_model_list_box = Gtk.Template.Child()
|
||||||
|
available_model_list_box = Gtk.Template.Child()
|
||||||
|
|
||||||
pull_model_dialog = Gtk.Template.Child()
|
|
||||||
pull_model_status_page = Gtk.Template.Child()
|
pull_model_status_page = Gtk.Template.Child()
|
||||||
pull_model_progress_bar = Gtk.Template.Child()
|
pull_model_progress_bar = Gtk.Template.Child()
|
||||||
|
|
||||||
@ -180,7 +180,22 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
for i in range(self.model_string_list.get_n_items() -1, -1, -1):
|
for i in range(self.model_string_list.get_n_items() -1, -1, -1):
|
||||||
self.model_string_list.remove(i)
|
self.model_string_list.remove(i)
|
||||||
if response['status'] == 'ok':
|
if response['status'] == 'ok':
|
||||||
|
self.local_model_list_box.remove_all()
|
||||||
for model in json.loads(response['text'])['models']:
|
for model in json.loads(response['text'])['models']:
|
||||||
|
model_row = Adw.ActionRow(
|
||||||
|
title = model["name"].split(":")[0],
|
||||||
|
subtitle = model["name"].split(":")[1]
|
||||||
|
)
|
||||||
|
button = Gtk.Button(
|
||||||
|
icon_name = "user-trash-symbolic",
|
||||||
|
vexpand = False,
|
||||||
|
valign = 3,
|
||||||
|
css_classes = ["error"]
|
||||||
|
)
|
||||||
|
button.connect("clicked", lambda button=button, model_name=model["name"]: self.model_delete_button_activate(model_name))
|
||||||
|
model_row.add_suffix(button)
|
||||||
|
self.local_model_list_box.append(model_row)
|
||||||
|
|
||||||
self.model_string_list.append(model["name"])
|
self.model_string_list.append(model["name"])
|
||||||
self.local_models.append(model["name"])
|
self.local_models.append(model["name"])
|
||||||
self.model_drop_down.set_selected(0)
|
self.model_drop_down.set_selected(0)
|
||||||
@ -350,7 +365,6 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
if dialog.choose_finish(task) == "delete":
|
if dialog.choose_finish(task) == "delete":
|
||||||
response = simple_delete(self.ollama_url + "/api/delete", data={"name": model_name})
|
response = simple_delete(self.ollama_url + "/api/delete", data={"name": model_name})
|
||||||
self.update_list_local_models()
|
self.update_list_local_models()
|
||||||
self.update_list_available_models()
|
|
||||||
if response['status'] == 'ok':
|
if response['status'] == 'ok':
|
||||||
self.show_toast("good", 0, self.manage_models_overlay)
|
self.show_toast("good", 0, self.manage_models_overlay)
|
||||||
else:
|
else:
|
||||||
@ -358,59 +372,71 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
self.manage_models_dialog.close()
|
self.manage_models_dialog.close()
|
||||||
self.show_connection_dialog(True)
|
self.show_connection_dialog(True)
|
||||||
|
|
||||||
def pull_model_update(self, data):
|
def pull_model_update(self, data, model_name):
|
||||||
try:
|
if model_name in list(self.pulling_models.keys()):
|
||||||
GLib.idle_add(self.pull_model_progress_bar.set_text, data['status'])
|
GLib.idle_add(self.pulling_models[model_name].set_subtitle, data['status'] + (f" | {round(data['completed'] / data['total'] * 100, 2)}%" if 'completed' in data and 'total' in data else ""))
|
||||||
if 'completed' in data:
|
else:
|
||||||
if 'total' in data: GLib.idle_add(self.pull_model_progress_bar.set_fraction, data['completed'] / data['total'])
|
sys.exit()
|
||||||
else: GLib.idle_add(self.pull_model_progress_bar.set_fraction, 1.0)
|
|
||||||
else:
|
|
||||||
GLib.idle_add(self.pull_model_progress_bar.set_fraction, 0.0)
|
|
||||||
except Exception as e: print(e)
|
|
||||||
|
|
||||||
def pull_model(self, dialog, task, model_name, tag):
|
def pull_model(self, model_name, tag):
|
||||||
if dialog.choose_finish(task) == "pull":
|
data = {"name":f"{model_name}:{tag}"}
|
||||||
data = {"name":f"{model_name}:{tag}"}
|
response = stream_post(f"{self.ollama_url}/api/pull", data=json.dumps(data), callback=lambda data, model_name=f"{model_name}:{tag}": self.pull_model_update(data, model_name))
|
||||||
|
GLib.idle_add(self.update_list_local_models)
|
||||||
|
if response['status'] == 'ok':
|
||||||
|
GLib.idle_add(self.show_notification, "Task Complete", f"Model '{model_name}:{tag}' pulled successfully.", True, Gio.ThemedIcon.new("emblem-ok-symbolic"))
|
||||||
|
GLib.idle_add(self.show_toast, "good", 1, self.manage_models_overlay)
|
||||||
|
GLib.idle_add(self.pulling_models[f"{model_name}:{tag}"].get_parent().remove, self.pulling_models[f"{model_name}:{tag}"])
|
||||||
|
del self.pulling_models[f"{model_name}:{tag}"]
|
||||||
|
|
||||||
GLib.idle_add(self.pull_model_dialog.present, self.manage_models_dialog)
|
else:
|
||||||
response = stream_post(f"{self.ollama_url}/api/pull", data=json.dumps(data), callback=self.pull_model_update)
|
GLib.idle_add(self.show_notification, "Pull Model Error", f"Failed to pull model '{model_name}:{tag}' due to network error.", True, Gio.ThemedIcon.new("dialog-error-symbolic"))
|
||||||
|
GLib.idle_add(self.show_toast, "error", 4, self.connection_overlay)
|
||||||
|
GLib.idle_add(self.manage_models_dialog.close)
|
||||||
|
GLib.idle_add(self.show_connection_dialog, True)
|
||||||
|
|
||||||
GLib.idle_add(self.update_list_local_models)
|
def stop_pull_model(self, dialog, task, model_name):
|
||||||
GLib.idle_add(self.update_list_available_models)
|
if dialog.choose_finish(task) == "stop":
|
||||||
GLib.idle_add(self.pull_model_dialog.force_close)
|
GLib.idle_add(self.pulling_models[model_name].get_parent().remove, self.pulling_models[model_name])
|
||||||
if response['status'] == 'ok':
|
del self.pulling_models[model_name]
|
||||||
GLib.idle_add(self.show_notification, "Task Complete", f"Model '{model_name}:{tag}' pulled successfully.", True, Gio.ThemedIcon.new("emblem-ok-symbolic"))
|
|
||||||
GLib.idle_add(self.show_toast, "good", 1, self.manage_models_overlay)
|
|
||||||
else:
|
|
||||||
GLib.idle_add(self.show_notification, "Pull Model Error", f"Failed to pull model '{model_name}:{tag}' due to network error.", True, Gio.ThemedIcon.new("dialog-error-symbolic"))
|
|
||||||
GLib.idle_add(self.show_toast, "error", 4, self.connection_overlay)
|
|
||||||
GLib.idle_add(self.manage_models_dialog.close)
|
|
||||||
GLib.idle_add(self.show_connection_dialog, True)
|
|
||||||
|
|
||||||
|
def stop_pull_model_dialog(self, model_name):
|
||||||
def pull_model_start(self, dialog, task, model_name, tag_drop_down):
|
|
||||||
tag = tag_drop_down.get_selected_item().get_string()
|
|
||||||
self.pull_model_status_page.set_description(f"{model_name}:{tag}")
|
|
||||||
thread = threading.Thread(target=self.pull_model, args=(dialog, task, model_name, tag))
|
|
||||||
thread.start()
|
|
||||||
|
|
||||||
def model_action_button_activate(self, button, model_name):
|
|
||||||
action = list(set(button.get_css_classes()) & set(["delete", "pull"]))[0]
|
|
||||||
dialog = Adw.AlertDialog(
|
dialog = Adw.AlertDialog(
|
||||||
heading=f"{action.capitalize()} Model",
|
heading="Stop Model",
|
||||||
body=f"Are you sure you want to {action} '{model_name}'?",
|
body=f"Are you sure you want to stop pulling '{model_name}'?",
|
||||||
close_response="cancel"
|
close_response="cancel"
|
||||||
)
|
)
|
||||||
dialog.add_response("cancel", "Cancel")
|
dialog.add_response("cancel", "Cancel")
|
||||||
dialog.add_response(action, action.capitalize())
|
dialog.add_response("stop", "Stop")
|
||||||
dialog.set_response_appearance(action, Adw.ResponseAppearance.DESTRUCTIVE if action == "delete" else Adw.ResponseAppearance.SUGGESTED)
|
dialog.set_response_appearance("stop", Adw.ResponseAppearance.DESTRUCTIVE)
|
||||||
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, button = button:
|
callback = lambda dialog, task, model_name = model_name: self.stop_pull_model(dialog, task, model_name)
|
||||||
self.delete_model(dialog, task, model_name, button) if action == "delete" else self.pull_model_start(dialog, task, model_name,button)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def pull_model_start(self, dialog, task, model_name, tag_drop_down):
|
||||||
|
if dialog.choose_finish(task) == "pull":
|
||||||
|
tag = tag_drop_down.get_selected_item().get_string()
|
||||||
|
if f"{model_name}:{tag}" in list(self.pulling_models.keys()): return ##TODO add message: 'already being pulled'
|
||||||
|
if f"{model_name}:{tag}" in self.local_models: return ##TODO add message 'already pulled'
|
||||||
|
#self.pull_model_status_page.set_description(f"{model_name}:{tag}")
|
||||||
|
model_row = Adw.ActionRow(
|
||||||
|
title = f"{model_name}:{tag}",
|
||||||
|
subtitle = ""
|
||||||
|
)
|
||||||
|
thread = threading.Thread(target=self.pull_model, args=(model_name, tag))
|
||||||
|
self.pulling_models[f"{model_name}:{tag}"] = model_row
|
||||||
|
button = Gtk.Button(
|
||||||
|
icon_name = "media-playback-stop-symbolic",
|
||||||
|
vexpand = False,
|
||||||
|
valign = 3,
|
||||||
|
css_classes = ["error"]
|
||||||
|
)
|
||||||
|
button.connect("clicked", lambda button, model_name=f"{model_name}:{tag}" : self.stop_pull_model_dialog(model_name))
|
||||||
|
model_row.add_suffix(button)
|
||||||
|
self.pulling_model_list_box.append(model_row)
|
||||||
|
thread.start()
|
||||||
|
|
||||||
def model_delete_button_activate(self, model_name):
|
def model_delete_button_activate(self, model_name):
|
||||||
dialog = Adw.AlertDialog(
|
dialog = Adw.AlertDialog(
|
||||||
heading="Delete Model",
|
heading="Delete Model",
|
||||||
@ -450,23 +476,7 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def update_list_available_models(self):
|
def update_list_available_models(self):
|
||||||
self.local_model_list_box.remove_all()
|
|
||||||
self.available_model_list_box.remove_all()
|
self.available_model_list_box.remove_all()
|
||||||
for model_name in self.local_models:
|
|
||||||
model = Adw.ActionRow(
|
|
||||||
title = model_name.split(":")[0],
|
|
||||||
subtitle = model_name.split(":")[1]
|
|
||||||
)
|
|
||||||
button = Gtk.Button(
|
|
||||||
icon_name = "user-trash-symbolic",
|
|
||||||
vexpand = False,
|
|
||||||
valign = 3,
|
|
||||||
css_classes = ["error"]
|
|
||||||
)
|
|
||||||
button.connect("clicked", lambda button=button, model_name=model_name: self.model_delete_button_activate(model_name))
|
|
||||||
model.add_suffix(button)
|
|
||||||
self.local_model_list_box.append(model)
|
|
||||||
|
|
||||||
for name, model_info in available_models.items():
|
for name, model_info in available_models.items():
|
||||||
model = Adw.ActionRow(
|
model = Adw.ActionRow(
|
||||||
title = name,
|
title = name,
|
||||||
@ -484,7 +494,6 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
|
|
||||||
def manage_models_button_activate(self, button=None):
|
def manage_models_button_activate(self, button=None):
|
||||||
self.update_list_local_models()
|
self.update_list_local_models()
|
||||||
self.update_list_available_models()
|
|
||||||
self.manage_models_dialog.present(self)
|
self.manage_models_dialog.present(self)
|
||||||
|
|
||||||
def connection_carousel_page_changed(self, carousel, index):
|
def connection_carousel_page_changed(self, carousel, index):
|
||||||
@ -785,9 +794,6 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
self.model_drop_down.set_selected(i)
|
self.model_drop_down.set_selected(i)
|
||||||
break
|
break
|
||||||
|
|
||||||
def selected_model_changed(self, pspec=None, user_data=None):
|
|
||||||
self.verify_if_image_can_be_used()
|
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
GtkSource.init()
|
GtkSource.init()
|
||||||
@ -801,7 +807,7 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
self.image_button.connect("clicked", self.open_image)
|
self.image_button.connect("clicked", self.open_image)
|
||||||
self.add_chat_button.connect("clicked", lambda button : self.chat_new_dialog("Enter name for new chat", False))
|
self.add_chat_button.connect("clicked", lambda button : self.chat_new_dialog("Enter name for new chat", False))
|
||||||
self.set_default_widget(self.send_button)
|
self.set_default_widget(self.send_button)
|
||||||
self.model_drop_down.connect("notify", self.selected_model_changed)
|
self.model_drop_down.connect("notify", self.verify_if_image_can_be_used)
|
||||||
self.chat_list_box.connect("row-selected", self.chat_changed)
|
self.chat_list_box.connect("row-selected", self.chat_changed)
|
||||||
#self.message_text_view.set_activates_default(self.send_button)
|
#self.message_text_view.set_activates_default(self.send_button)
|
||||||
self.connection_carousel.connect("page-changed", self.connection_carousel_page_changed)
|
self.connection_carousel.connect("page-changed", self.connection_carousel_page_changed)
|
||||||
@ -817,4 +823,5 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
else:
|
else:
|
||||||
self.first_time_setup = True
|
self.first_time_setup = True
|
||||||
self.connection_dialog.present(self)
|
self.connection_dialog.present(self)
|
||||||
|
self.update_list_available_models()
|
||||||
self.update_chat_list()
|
self.update_chat_list()
|
||||||
|
@ -245,35 +245,6 @@
|
|||||||
</object>
|
</object>
|
||||||
</property>
|
</property>
|
||||||
|
|
||||||
<object class="AdwDialog" id="pull_model_dialog">
|
|
||||||
<property name="can-close">false</property>
|
|
||||||
<property name="width-request">400</property>
|
|
||||||
<child>
|
|
||||||
<object class="AdwToastOverlay" id="pull_overlay">
|
|
||||||
<child>
|
|
||||||
<object class="AdwToolbarView">
|
|
||||||
<child>
|
|
||||||
<object class="AdwStatusPage" id="pull_model_status_page">
|
|
||||||
<property name="hexpand">true</property>
|
|
||||||
<property name="vexpand">true</property>
|
|
||||||
<property name="margin-top">24</property>
|
|
||||||
<property name="margin-bottom">24</property>
|
|
||||||
<property name="margin-start">24</property>
|
|
||||||
<property name="margin-end">24</property>
|
|
||||||
<property name="title" translatable="yes">Pulling Model</property>
|
|
||||||
<child>
|
|
||||||
<object class="GtkProgressBar" id="pull_model_progress_bar">
|
|
||||||
<property name="show-text">true</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
|
|
||||||
<object class="AdwDialog" id="manage_models_dialog">
|
<object class="AdwDialog" id="manage_models_dialog">
|
||||||
<property name="can-close">true</property>
|
<property name="can-close">true</property>
|
||||||
<property name="width-request">400</property>
|
<property name="width-request">400</property>
|
||||||
@ -303,6 +274,21 @@
|
|||||||
<object class="GtkBox">
|
<object class="GtkBox">
|
||||||
<property name="orientation">1</property>
|
<property name="orientation">1</property>
|
||||||
<property name="spacing">12</property>
|
<property name="spacing">12</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkListBox" id="pulling_model_list_box">
|
||||||
|
<property name="selection-mode">none</property>
|
||||||
|
<style>
|
||||||
|
<class name="boxed-list"/>
|
||||||
|
</style>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkSeparator">
|
||||||
|
<style>
|
||||||
|
<class name="spacer"/>
|
||||||
|
</style>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkListBox" id="local_model_list_box">
|
<object class="GtkListBox" id="local_model_list_box">
|
||||||
<property name="selection-mode">none</property>
|
<property name="selection-mode">none</property>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user