I can write good code sometimes
This commit is contained in:
parent
780de2b753
commit
1cf2f04b06
@ -1,6 +1,8 @@
|
|||||||
# connectionhandler.py
|
# connectionhandler.py
|
||||||
import json, requests
|
import json, requests
|
||||||
|
|
||||||
|
url = None
|
||||||
|
|
||||||
def simple_get(connection_url:str) -> dict:
|
def simple_get(connection_url:str) -> dict:
|
||||||
try:
|
try:
|
||||||
response = requests.get(connection_url)
|
response = requests.get(connection_url)
|
||||||
@ -37,25 +39,3 @@ def stream_post(connection_url:str, data, callback:callable) -> dict:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
return {"status": "error", "status_code": 0}
|
return {"status": "error", "status_code": 0}
|
||||||
|
|
||||||
|
|
||||||
from time import sleep
|
|
||||||
def stream_post_fake(connection_url:str, data, callback:callable) -> dict:
|
|
||||||
data = {
|
|
||||||
"status": "pulling manifest"
|
|
||||||
}
|
|
||||||
callback(data)
|
|
||||||
for i in range(2):
|
|
||||||
for a in range(11):
|
|
||||||
sleep(.1)
|
|
||||||
data = {
|
|
||||||
"status": f"downloading digestname {i}",
|
|
||||||
"digest": f"digestname {i}",
|
|
||||||
"total": 500,
|
|
||||||
"completed": a * 50
|
|
||||||
}
|
|
||||||
callback(data)
|
|
||||||
for msg in ["verifying sha256 digest", "writting manifest", "removing any unused layers", "success"]:
|
|
||||||
sleep(.1)
|
|
||||||
data = {"status": msg}
|
|
||||||
callback(data)
|
|
||||||
return {"status": "ok", "status_code": 200}
|
|
||||||
|
228
src/dialogs.py
Normal file
228
src/dialogs.py
Normal file
@ -0,0 +1,228 @@
|
|||||||
|
# dialogs.py
|
||||||
|
|
||||||
|
from gi.repository import Adw, Gtk, Gdk, GLib, GtkSource, Gio, GdkPixbuf
|
||||||
|
from .available_models import available_models
|
||||||
|
|
||||||
|
# CLEAR CHAT | WORKS
|
||||||
|
|
||||||
|
def clear_chat_response(self, dialog, task):
|
||||||
|
if dialog.choose_finish(task) == "clear":
|
||||||
|
self.clear_chat()
|
||||||
|
|
||||||
|
def clear_chat(self):
|
||||||
|
if self.bot_message is not None:
|
||||||
|
self.show_toast("info", 1, self.main_overlay)
|
||||||
|
return
|
||||||
|
dialog = Adw.AlertDialog(
|
||||||
|
heading=_("Clear Chat"),
|
||||||
|
body=_("Are you sure you want to clear the chat?"),
|
||||||
|
close_response="cancel"
|
||||||
|
)
|
||||||
|
dialog.add_response("cancel", _("Cancel"))
|
||||||
|
dialog.add_response("clear", _("Clear"))
|
||||||
|
dialog.set_response_appearance("clear", Adw.ResponseAppearance.DESTRUCTIVE)
|
||||||
|
dialog.choose(
|
||||||
|
parent = self,
|
||||||
|
cancellable = None,
|
||||||
|
callback = lambda dialog, task: clear_chat_response(self, dialog, task)
|
||||||
|
)
|
||||||
|
|
||||||
|
# DELETE CHAT | WORKS
|
||||||
|
|
||||||
|
def delete_chat_response(self, dialog, task, chat_name):
|
||||||
|
if dialog.choose_finish(task) == "delete":
|
||||||
|
self.delete_chat(chat_name)
|
||||||
|
|
||||||
|
def delete_chat(self, chat_name):
|
||||||
|
dialog = Adw.AlertDialog(
|
||||||
|
heading=_("Delete Chat"),
|
||||||
|
body=_("Are you sure you want to delete '{}'?").format(chat_name),
|
||||||
|
close_response="cancel"
|
||||||
|
)
|
||||||
|
dialog.add_response("cancel", _("Cancel"))
|
||||||
|
dialog.add_response("delete", _("Delete"))
|
||||||
|
dialog.set_response_appearance("delete", Adw.ResponseAppearance.DESTRUCTIVE)
|
||||||
|
dialog.choose(
|
||||||
|
parent = self,
|
||||||
|
cancellable = None,
|
||||||
|
callback = lambda dialog, task, chat_name=chat_name: delete_chat_response(self, dialog, task, chat_name)
|
||||||
|
)
|
||||||
|
|
||||||
|
# RENAME CHAT | WORKS
|
||||||
|
|
||||||
|
def rename_chat_response(self, dialog, task, old_chat_name, entry, label_element):
|
||||||
|
if not entry: return
|
||||||
|
new_chat_name = entry.get_text()
|
||||||
|
if old_chat_name == new_chat_name: return
|
||||||
|
if new_chat_name and (task is None or dialog.choose_finish(task) == "rename"):
|
||||||
|
self.rename_chat(old_chat_name, new_chat_name, label_element)
|
||||||
|
|
||||||
|
def rename_chat(self, chat_name:str, label_element):
|
||||||
|
entry = Gtk.Entry()
|
||||||
|
dialog = Adw.AlertDialog(
|
||||||
|
heading=_("Rename Chat"),
|
||||||
|
body=_("Renaming '{}'").format(chat_name),
|
||||||
|
extra_child=entry,
|
||||||
|
close_response="cancel"
|
||||||
|
)
|
||||||
|
entry.connect("activate", lambda dialog, old_chat_name=chat_name, entry=entry, label_element=label_element: rename_chat_response(self, dialog, None, old_chat_name, entry, label_element))
|
||||||
|
dialog.add_response("cancel", _("Cancel"))
|
||||||
|
dialog.add_response("rename", _("Rename"))
|
||||||
|
dialog.set_response_appearance("rename", Adw.ResponseAppearance.SUGGESTED)
|
||||||
|
dialog.choose(
|
||||||
|
parent = self,
|
||||||
|
cancellable = None,
|
||||||
|
callback = lambda dialog, task, old_chat_name=chat_name, entry=entry, label_element=label_element: rename_chat_response(self, dialog, task, old_chat_name, entry, label_element)
|
||||||
|
)
|
||||||
|
|
||||||
|
# NEW CHAT | WORKS
|
||||||
|
|
||||||
|
def new_chat_response(self, dialog, task, entry):
|
||||||
|
chat_name = _("New Chat")
|
||||||
|
if entry is not None and entry.get_text() != "": chat_name = entry.get_text()
|
||||||
|
if chat_name and (task is None or dialog.choose_finish(task) == "create"):
|
||||||
|
self.new_chat(chat_name)
|
||||||
|
|
||||||
|
|
||||||
|
def new_chat(self):
|
||||||
|
entry = Gtk.Entry()
|
||||||
|
dialog = Adw.AlertDialog(
|
||||||
|
heading=_("Create Chat"),
|
||||||
|
body=_("Enter name for new chat"),
|
||||||
|
extra_child=entry,
|
||||||
|
close_response="cancel"
|
||||||
|
)
|
||||||
|
entry.connect("activate", lambda dialog, entry: new_chat_response(self, dialog, None, entry))
|
||||||
|
dialog.add_response("cancel", _("Cancel"))
|
||||||
|
dialog.add_response("create", _("Create"))
|
||||||
|
dialog.set_response_appearance("create", Adw.ResponseAppearance.SUGGESTED)
|
||||||
|
dialog.choose(
|
||||||
|
parent = self,
|
||||||
|
cancellable = None,
|
||||||
|
callback = lambda dialog, task, entry=entry: new_chat_response(self, dialog, task, entry)
|
||||||
|
)
|
||||||
|
|
||||||
|
# STOP PULL MODEL | WORKS
|
||||||
|
|
||||||
|
def stop_pull_model_response(self, dialog, task, model_name):
|
||||||
|
if dialog.choose_finish(task) == "stop":
|
||||||
|
self.stop_pull_model(model_name)
|
||||||
|
|
||||||
|
def stop_pull_model(self, model_name):
|
||||||
|
dialog = Adw.AlertDialog(
|
||||||
|
heading=_("Stop Model"),
|
||||||
|
body=_("Are you sure you want to stop pulling '{}'?").format(model_name),
|
||||||
|
close_response="cancel"
|
||||||
|
)
|
||||||
|
dialog.add_response("cancel", _("Cancel"))
|
||||||
|
dialog.add_response("stop", _("Stop"))
|
||||||
|
dialog.set_response_appearance("stop", Adw.ResponseAppearance.DESTRUCTIVE)
|
||||||
|
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)
|
||||||
|
)
|
||||||
|
|
||||||
|
# DELETE MODEL | WORKS
|
||||||
|
|
||||||
|
def delete_model_response(self, dialog, task, model_name):
|
||||||
|
if dialog.choose_finish(task) == "delete":
|
||||||
|
self.delete_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),
|
||||||
|
close_response="cancel"
|
||||||
|
)
|
||||||
|
dialog.add_response("cancel", _("Cancel"))
|
||||||
|
dialog.add_response("delete", _("Delete"))
|
||||||
|
dialog.set_response_appearance("delete", Adw.ResponseAppearance.DESTRUCTIVE)
|
||||||
|
dialog.choose(
|
||||||
|
parent = self.manage_models_dialog,
|
||||||
|
cancellable = None,
|
||||||
|
callback = lambda dialog, task, model_name = model_name: delete_model_response(self, dialog, task, model_name)
|
||||||
|
)
|
||||||
|
|
||||||
|
# PULL MODEL | WORKS
|
||||||
|
|
||||||
|
def pull_model_response(self, dialog, task, model_name, tag_drop_down):
|
||||||
|
if dialog.choose_finish(task) == "pull":
|
||||||
|
model = f"{model_name}:{tag_drop_down.get_selected_item().get_string()}"
|
||||||
|
self.pull_model(model)
|
||||||
|
|
||||||
|
def pull_model(self, model_name):
|
||||||
|
tag_list = Gtk.StringList()
|
||||||
|
for tag in available_models[model_name]['tags']:
|
||||||
|
tag_list.append(tag)
|
||||||
|
tag_drop_down = Gtk.DropDown(
|
||||||
|
enable_search=True,
|
||||||
|
model=tag_list
|
||||||
|
)
|
||||||
|
dialog = Adw.AlertDialog(
|
||||||
|
heading=_("Pull Model"),
|
||||||
|
body=_("Please select a tag to pull '{}'").format(model_name),
|
||||||
|
extra_child=tag_drop_down,
|
||||||
|
close_response="cancel"
|
||||||
|
)
|
||||||
|
dialog.add_response("cancel", _("Cancel"))
|
||||||
|
dialog.add_response("pull", _("Pull"))
|
||||||
|
dialog.set_response_appearance("pull", Adw.ResponseAppearance.SUGGESTED)
|
||||||
|
dialog.choose(
|
||||||
|
parent = self.manage_models_dialog,
|
||||||
|
cancellable = None,
|
||||||
|
callback = lambda dialog, task, model_name = model_name, tag_drop_down = tag_drop_down: pull_model_response(self, dialog, task, model_name, tag_drop_down)
|
||||||
|
)
|
||||||
|
|
||||||
|
# REMOVE IMAGE | WORKS
|
||||||
|
|
||||||
|
def remove_image_response(self, dialog, task):
|
||||||
|
if dialog.choose_finish(task) == 'remove':
|
||||||
|
self.remove_image()
|
||||||
|
|
||||||
|
def remove_image(self):
|
||||||
|
dialog = Adw.AlertDialog(
|
||||||
|
heading=_("Remove Image"),
|
||||||
|
body=_("Are you sure you want to remove image?"),
|
||||||
|
close_response="cancel"
|
||||||
|
)
|
||||||
|
dialog.add_response("cancel", _("Cancel"))
|
||||||
|
dialog.add_response("remove", _("Remove"))
|
||||||
|
dialog.set_response_appearance("remove", Adw.ResponseAppearance.DESTRUCTIVE)
|
||||||
|
dialog.choose(
|
||||||
|
parent = self,
|
||||||
|
cancellable = None,
|
||||||
|
callback = lambda dialog, task: remove_image_response(self, dialog, task)
|
||||||
|
)
|
||||||
|
|
||||||
|
# RECONNECT REMOTE |
|
||||||
|
|
||||||
|
def reconnect_remote_response(self, dialog, task, entry):
|
||||||
|
response = dialog.choose_finish(task)
|
||||||
|
if not task or response == "remote":
|
||||||
|
self.connect_remote(entry.get_text())
|
||||||
|
elif response == "local":
|
||||||
|
self.connect_local()
|
||||||
|
elif response == "close":
|
||||||
|
self.destroy()
|
||||||
|
|
||||||
|
def reconnect_remote(self):
|
||||||
|
entry = Gtk.Entry(
|
||||||
|
css_classes = ["error"],
|
||||||
|
text = self.ollama_url
|
||||||
|
)
|
||||||
|
dialog = Adw.AlertDialog(
|
||||||
|
heading=_("Connection Error"),
|
||||||
|
body=_("The remote instance has disconnected"),
|
||||||
|
extra_child=entry
|
||||||
|
)
|
||||||
|
entry.connect("activate", lambda entry, dialog: reconnect_remote_response(self, dialog, None, entry))
|
||||||
|
dialog.add_response("close", _("Close Alpaca"))
|
||||||
|
dialog.add_response("local", _("Use local instance"))
|
||||||
|
dialog.add_response("remote", _("Connect"))
|
||||||
|
dialog.set_response_appearance("remote", Adw.ResponseAppearance.SUGGESTED)
|
||||||
|
dialog.choose(
|
||||||
|
parent = self,
|
||||||
|
cancellable = None,
|
||||||
|
callback = lambda dialog, task, entry=entry: reconnect_remote_response(self, dialog, task, entry)
|
||||||
|
)
|
23
src/local_instance.py
Normal file
23
src/local_instance.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# local_instance.py
|
||||||
|
import subprocess, os
|
||||||
|
from time import sleep
|
||||||
|
|
||||||
|
instance = None
|
||||||
|
port = 11435
|
||||||
|
|
||||||
|
def start(data_dir):
|
||||||
|
instance = subprocess.Popen(["/app/bin/ollama", "serve"], env={**os.environ, 'OLLAMA_HOST': f"127.0.0.1:{port}", "HOME": data_dir}, stderr=subprocess.PIPE, text=True)
|
||||||
|
print("Starting Alpaca's Ollama instance...")
|
||||||
|
sleep(1)
|
||||||
|
while True:
|
||||||
|
err = instance.stderr.readline()
|
||||||
|
if err == '' and instance.poll() is not None:
|
||||||
|
break
|
||||||
|
if 'msg="inference compute"' in err: #Ollama outputs a line with this when it finishes loading, yeah
|
||||||
|
break
|
||||||
|
print("Started Alpaca's Ollama instance")
|
||||||
|
|
||||||
|
def stop():
|
||||||
|
if instance: instance.kill()
|
||||||
|
print("Stopped Alpaca's Ollama instance")
|
||||||
|
|
@ -33,7 +33,6 @@ class AlpacaApplication(Adw.Application):
|
|||||||
super().__init__(application_id='com.jeffser.Alpaca',
|
super().__init__(application_id='com.jeffser.Alpaca',
|
||||||
flags=Gio.ApplicationFlags.DEFAULT_FLAGS)
|
flags=Gio.ApplicationFlags.DEFAULT_FLAGS)
|
||||||
self.create_action('quit', lambda *_: self.quit(), ['<primary>q'])
|
self.create_action('quit', lambda *_: self.quit(), ['<primary>q'])
|
||||||
self.create_action('clear', lambda *_: AlpacaWindow.clear_chat_dialog(self.props.active_window), ['<primary>e'])
|
|
||||||
self.create_action('preferences', lambda *_: AlpacaWindow.show_preferences_dialog(self.props.active_window), ['<primary>p'])
|
self.create_action('preferences', lambda *_: AlpacaWindow.show_preferences_dialog(self.props.active_window), ['<primary>p'])
|
||||||
self.create_action('about', self.on_about_action)
|
self.create_action('about', self.on_about_action)
|
||||||
|
|
||||||
|
@ -31,7 +31,9 @@ alpaca_sources = [
|
|||||||
'main.py',
|
'main.py',
|
||||||
'window.py',
|
'window.py',
|
||||||
'connection_handler.py',
|
'connection_handler.py',
|
||||||
'available_models.py'
|
'available_models.py',
|
||||||
|
'dialogs.py',
|
||||||
|
'local_instance.py'
|
||||||
]
|
]
|
||||||
|
|
||||||
install_data(alpaca_sources, install_dir: moduledir)
|
install_data(alpaca_sources, install_dir: moduledir)
|
||||||
|
518
src/window.py
518
src/window.py
@ -26,8 +26,8 @@ from time import sleep
|
|||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from .connection_handler import simple_get, simple_delete, stream_post, stream_post_fake
|
|
||||||
from .available_models import available_models
|
from .available_models import available_models
|
||||||
|
from . import dialogs, local_instance, connection_handler
|
||||||
|
|
||||||
@Gtk.Template(resource_path='/com/jeffser/Alpaca/window.ui')
|
@Gtk.Template(resource_path='/com/jeffser/Alpaca/window.ui')
|
||||||
class AlpacaWindow(Adw.ApplicationWindow):
|
class AlpacaWindow(Adw.ApplicationWindow):
|
||||||
@ -46,17 +46,13 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
|
|
||||||
#Variables
|
#Variables
|
||||||
run_on_background = False
|
run_on_background = False
|
||||||
ollama_url = ""
|
|
||||||
remote_url = ""
|
remote_url = ""
|
||||||
run_remote = False
|
run_remote = False
|
||||||
local_ollama_port = 11435
|
|
||||||
local_ollama_instance = None
|
|
||||||
local_models = []
|
local_models = []
|
||||||
pulling_models = {}
|
pulling_models = {}
|
||||||
current_chat_elements = [] #Used for deleting
|
current_chat_elements = [] #Used for deleting
|
||||||
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
|
|
||||||
|
|
||||||
#Elements
|
#Elements
|
||||||
preferences_dialog = Gtk.Template.Child()
|
preferences_dialog = Gtk.Template.Child()
|
||||||
@ -80,7 +76,6 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
model_drop_down = Gtk.Template.Child()
|
model_drop_down = Gtk.Template.Child()
|
||||||
model_string_list = Gtk.Template.Child()
|
model_string_list = Gtk.Template.Child()
|
||||||
|
|
||||||
manage_models_button = Gtk.Template.Child()
|
|
||||||
manage_models_dialog = Gtk.Template.Child()
|
manage_models_dialog = Gtk.Template.Child()
|
||||||
pulling_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()
|
||||||
@ -213,6 +208,7 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
self.bot_message_view = message_text
|
self.bot_message_view = message_text
|
||||||
self.bot_message_box = message_box
|
self.bot_message_box = message_box
|
||||||
|
|
||||||
|
@Gtk.Template.Callback()
|
||||||
def verify_if_image_can_be_used(self, pspec=None, user_data=None):
|
def verify_if_image_can_be_used(self, pspec=None, user_data=None):
|
||||||
if self.model_drop_down.get_selected_item() == None: return True
|
if self.model_drop_down.get_selected_item() == None: return True
|
||||||
selected = self.model_drop_down.get_selected_item().get_string().split(":")[0]
|
selected = self.model_drop_down.get_selected_item().get_string().split(":")[0]
|
||||||
@ -228,7 +224,7 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
|
|
||||||
def update_list_local_models(self):
|
def update_list_local_models(self):
|
||||||
self.local_models = []
|
self.local_models = []
|
||||||
response = simple_get(self.ollama_url + "/api/tags")
|
response = connection_handler.simple_get(connection_handler.url + "/api/tags")
|
||||||
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':
|
||||||
@ -248,7 +244,7 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
valign = 3,
|
valign = 3,
|
||||||
css_classes = ["error"]
|
css_classes = ["error"]
|
||||||
)
|
)
|
||||||
button.connect("clicked", lambda button=button, model_name=model["name"]: self.model_delete_button_activate(model_name))
|
button.connect("clicked", lambda button=button, model_name=model["name"]: dialogs.delete_model(self, model_name))
|
||||||
model_row.add_suffix(button)
|
model_row.add_suffix(button)
|
||||||
self.local_model_list_box.append(model_row)
|
self.local_model_list_box.append(model_row)
|
||||||
|
|
||||||
@ -258,15 +254,15 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
self.verify_if_image_can_be_used()
|
self.verify_if_image_can_be_used()
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
|
print("huh 2")
|
||||||
self.connection_error()
|
self.connection_error()
|
||||||
|
|
||||||
def verify_connection(self):
|
def verify_connection(self):
|
||||||
response = simple_get(self.ollama_url)
|
response = connection_handler.simple_get(connection_handler.url)
|
||||||
if response['status'] == 'ok':
|
if response['status'] == 'ok':
|
||||||
if "Ollama is running" in response['text']:
|
if "Ollama is running" in response['text']:
|
||||||
with open(os.path.join(self.config_dir, "server.json"), "w+") as f:
|
with open(os.path.join(self.config_dir, "server.json"), "w+") as f:
|
||||||
json.dump({'remote_url': self.remote_url, 'run_remote': self.run_remote, 'local_port': self.local_ollama_port, 'run_on_background': self.run_on_background}, f)
|
json.dump({'remote_url': self.remote_url, 'run_remote': self.run_remote, 'local_port': local_instance.port, 'run_on_background': self.run_on_background}, f)
|
||||||
#self.message_text_view.grab_focus_without_selecting()
|
|
||||||
self.update_list_local_models()
|
self.update_list_local_models()
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
@ -408,7 +404,7 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
self.chats["chats"][self.chats["selected_chat"]]["messages"][-1]['content'] += data['message']['content']
|
self.chats["chats"][self.chats["selected_chat"]]["messages"][-1]['content'] += data['message']['content']
|
||||||
|
|
||||||
def run_message(self, messages, model):
|
def run_message(self, messages, model):
|
||||||
response = stream_post(f"{self.ollama_url}/api/chat", data=json.dumps({"model": model, "messages": messages}), callback=self.update_bot_message)
|
response = connection_handler.stream_post(f"{connection_handler.url}/api/chat", data=json.dumps({"model": model, "messages": messages}), callback=self.update_bot_message)
|
||||||
GLib.idle_add(self.add_code_blocks)
|
GLib.idle_add(self.add_code_blocks)
|
||||||
GLib.idle_add(self.send_button.set_css_classes, ["suggested-action"])
|
GLib.idle_add(self.send_button.set_css_classes, ["suggested-action"])
|
||||||
GLib.idle_add(self.send_button.get_child().set_label, "Send")
|
GLib.idle_add(self.send_button.get_child().set_label, "Send")
|
||||||
@ -425,6 +421,7 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
if response['status'] == 'error':
|
if response['status'] == 'error':
|
||||||
GLib.idle_add(self.connection_error)
|
GLib.idle_add(self.connection_error)
|
||||||
|
|
||||||
|
@Gtk.Template.Callback()
|
||||||
def send_message(self, button=None):
|
def send_message(self, button=None):
|
||||||
if button and self.bot_message: #STOP BUTTON
|
if button and self.bot_message: #STOP BUTTON
|
||||||
if self.loading_spinner: self.chat_container.remove(self.loading_spinner)
|
if self.loading_spinner: self.chat_container.remove(self.loading_spinner)
|
||||||
@ -483,16 +480,6 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
thread = threading.Thread(target=self.run_message, args=(data['messages'], data['model']))
|
thread = threading.Thread(target=self.run_message, args=(data['messages'], data['model']))
|
||||||
thread.start()
|
thread.start()
|
||||||
|
|
||||||
def delete_model(self, dialog, task, model_name):
|
|
||||||
if dialog.choose_finish(task) == "delete":
|
|
||||||
response = simple_delete(self.ollama_url + "/api/delete", data={"name": model_name})
|
|
||||||
self.update_list_local_models()
|
|
||||||
if response['status'] == 'ok':
|
|
||||||
self.show_toast("good", 0, self.manage_models_overlay)
|
|
||||||
else:
|
|
||||||
self.manage_models_dialog.close()
|
|
||||||
self.connection_error()
|
|
||||||
|
|
||||||
def pull_model_update(self, data, model_name):
|
def pull_model_update(self, data, model_name):
|
||||||
if model_name in list(self.pulling_models.keys()):
|
if model_name in list(self.pulling_models.keys()):
|
||||||
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 ""))
|
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 ""))
|
||||||
@ -501,110 +488,48 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
GLib.idle_add(self.pulling_model_list_box.set_visible, False)
|
GLib.idle_add(self.pulling_model_list_box.set_visible, False)
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
def pull_model(self, model_name, tag):
|
def pull_model_process(self, model):
|
||||||
data = {"name":f"{model_name}:{tag}"}
|
data = {"name":model}
|
||||||
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))
|
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)
|
GLib.idle_add(self.update_list_local_models)
|
||||||
|
|
||||||
if response['status'] == 'ok':
|
if response['status'] == 'ok':
|
||||||
GLib.idle_add(self.show_notification, _("Task Complete"), _("Model '{}' pulled successfully.").format(f"{model_name}:{tag}"), True, Gio.ThemedIcon.new("emblem-ok-symbolic"))
|
GLib.idle_add(self.show_notification, _("Task Complete"), _("Model '{}' pulled successfully.").format(model), True, Gio.ThemedIcon.new("emblem-ok-symbolic"))
|
||||||
GLib.idle_add(self.show_toast, "good", 1, self.manage_models_overlay)
|
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}"])
|
GLib.idle_add(self.pulling_models[model].get_parent().remove, self.pulling_models[model])
|
||||||
del self.pulling_models[f"{model_name}:{tag}"]
|
del self.pulling_models[model]
|
||||||
else:
|
else:
|
||||||
GLib.idle_add(self.show_notification, _("Pull Model Error"), _("Failed to pull model '{}' due to network error.").format(f"{model_name}:{tag}"), True, Gio.ThemedIcon.new("dialog-error-symbolic"))
|
GLib.idle_add(self.show_notification, _("Pull Model Error"), _("Failed to pull model '{}' due to network error.").format(model), True, Gio.ThemedIcon.new("dialog-error-symbolic"))
|
||||||
GLib.idle_add(self.pulling_models[f"{model_name}:{tag}"].get_parent().remove, self.pulling_models[f"{model_name}:{tag}"])
|
GLib.idle_add(self.pulling_models[model].get_parent().remove, self.pulling_models[model])
|
||||||
del self.pulling_models[f"{model_name}:{tag}"]
|
del self.pulling_models[model]
|
||||||
GLib.idle_add(self.manage_models_dialog.close)
|
GLib.idle_add(self.manage_models_dialog.close)
|
||||||
GLib.idle_add(self.connection_error)
|
GLib.idle_add(self.connection_error)
|
||||||
if len(list(self.pulling_models.keys())) == 0:
|
if len(list(self.pulling_models.keys())) == 0:
|
||||||
GLib.idle_add(self.pulling_model_list_box.set_visible, False)
|
GLib.idle_add(self.pulling_model_list_box.set_visible, False)
|
||||||
|
|
||||||
def stop_pull_model(self, dialog, task, model_name):
|
def pull_model(self, model):
|
||||||
if dialog.choose_finish(task) == "stop":
|
if model in list(self.pulling_models.keys()):
|
||||||
GLib.idle_add(self.pulling_models[model_name].get_parent().remove, self.pulling_models[model_name])
|
self.show_toast("info", 3, self.manage_models_overlay)
|
||||||
del self.pulling_models[model_name]
|
return
|
||||||
|
if model in self.local_models:
|
||||||
def stop_pull_model_dialog(self, model_name):
|
self.show_toast("info", 4, self.manage_models_overlay)
|
||||||
dialog = Adw.AlertDialog(
|
return
|
||||||
heading=_("Stop Model"),
|
self.pulling_model_list_box.set_visible(True)
|
||||||
body=_("Are you sure you want to stop pulling '{}'?").format(model_name),
|
model_row = Adw.ActionRow(
|
||||||
close_response="cancel"
|
title = model
|
||||||
)
|
)
|
||||||
dialog.add_response("cancel", _("Cancel"))
|
thread = threading.Thread(target=self.pull_model_process, kwargs={"model": model})
|
||||||
dialog.add_response("stop", _("Stop"))
|
self.pulling_models[model] = model_row
|
||||||
dialog.set_response_appearance("stop", Adw.ResponseAppearance.DESTRUCTIVE)
|
button = Gtk.Button(
|
||||||
dialog.choose(
|
icon_name = "media-playback-stop-symbolic",
|
||||||
parent = self.manage_models_dialog,
|
vexpand = False,
|
||||||
cancellable = None,
|
valign = 3,
|
||||||
callback = lambda dialog, task, model_name = model_name: self.stop_pull_model(dialog, task, model_name)
|
css_classes = ["error"]
|
||||||
)
|
|
||||||
|
|
||||||
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()):
|
|
||||||
self.show_toast("info", 3, self.manage_models_overlay)
|
|
||||||
return
|
|
||||||
if f"{model_name}:{tag}" in self.local_models:
|
|
||||||
self.show_toast("info", 4, self.manage_models_overlay)
|
|
||||||
return
|
|
||||||
#self.pull_model_status_page.set_description(f"{model_name}:{tag}")
|
|
||||||
self.pulling_model_list_box.set_visible(True)
|
|
||||||
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):
|
|
||||||
dialog = Adw.AlertDialog(
|
|
||||||
heading=_("Delete Model"),
|
|
||||||
body=_("Are you sure you want to delete '{}'?").format(model_name),
|
|
||||||
close_response="cancel"
|
|
||||||
)
|
|
||||||
dialog.add_response("cancel", _("Cancel"))
|
|
||||||
dialog.add_response("delete", _("Delete"))
|
|
||||||
dialog.set_response_appearance("delete", Adw.ResponseAppearance.DESTRUCTIVE)
|
|
||||||
dialog.choose(
|
|
||||||
parent = self.manage_models_dialog,
|
|
||||||
cancellable = None,
|
|
||||||
callback = lambda dialog, task, model_name = model_name: self.delete_model(dialog, task, model_name)
|
|
||||||
)
|
|
||||||
|
|
||||||
def model_pull_button_activate(self, model_name):
|
|
||||||
tag_list = Gtk.StringList()
|
|
||||||
for tag in available_models[model_name]['tags']:
|
|
||||||
tag_list.append(tag)
|
|
||||||
tag_drop_down = Gtk.DropDown(
|
|
||||||
enable_search=True,
|
|
||||||
model=tag_list
|
|
||||||
)
|
|
||||||
dialog = Adw.AlertDialog(
|
|
||||||
heading=_("Pull Model"),
|
|
||||||
body=_("Please select a tag to pull '{}'").format(model_name),
|
|
||||||
extra_child=tag_drop_down,
|
|
||||||
close_response="cancel"
|
|
||||||
)
|
|
||||||
dialog.add_response("cancel", _("Cancel"))
|
|
||||||
dialog.add_response("pull", _("Pull"))
|
|
||||||
dialog.set_response_appearance("pull", Adw.ResponseAppearance.SUGGESTED)
|
|
||||||
dialog.choose(
|
|
||||||
parent = self.manage_models_dialog,
|
|
||||||
cancellable = None,
|
|
||||||
callback = lambda dialog, task, model_name = model_name, tag_drop_down = tag_drop_down: self.pull_model_start(dialog, task, model_name, tag_drop_down)
|
|
||||||
)
|
)
|
||||||
|
button.connect("clicked", lambda button, model_name=model : dialogs.stop_pull_model(self, model_name))
|
||||||
|
model_row.add_suffix(button)
|
||||||
|
self.pulling_model_list_box.append(model_row)
|
||||||
|
thread.start()
|
||||||
|
|
||||||
def update_list_available_models(self):
|
def update_list_available_models(self):
|
||||||
self.available_model_list_box.remove_all()
|
self.available_model_list_box.remove_all()
|
||||||
@ -626,24 +551,28 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
css_classes = ["accent"]
|
css_classes = ["accent"]
|
||||||
)
|
)
|
||||||
link_button.connect("clicked", lambda button=link_button, link=model_info["url"]: webbrowser.open(link))
|
link_button.connect("clicked", lambda button=link_button, link=model_info["url"]: webbrowser.open(link))
|
||||||
pull_button.connect("clicked", lambda button=pull_button, model_name=name: self.model_pull_button_activate(model_name))
|
pull_button.connect("clicked", lambda button=pull_button, model_name=name: dialogs.pull_model(self, model_name))
|
||||||
model.add_suffix(link_button)
|
model.add_suffix(link_button)
|
||||||
model.add_suffix(pull_button)
|
model.add_suffix(pull_button)
|
||||||
self.available_model_list_box.append(model)
|
self.available_model_list_box.append(model)
|
||||||
|
|
||||||
|
@Gtk.Template.Callback()
|
||||||
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.manage_models_dialog.present(self)
|
self.manage_models_dialog.present(self)
|
||||||
|
|
||||||
|
@Gtk.Template.Callback()
|
||||||
def welcome_carousel_page_changed(self, carousel, index):
|
def welcome_carousel_page_changed(self, carousel, index):
|
||||||
if index == 0: self.welcome_previous_button.set_sensitive(False)
|
if index == 0: self.welcome_previous_button.set_sensitive(False)
|
||||||
else: self.welcome_previous_button.set_sensitive(True)
|
else: self.welcome_previous_button.set_sensitive(True)
|
||||||
if index == carousel.get_n_pages()-1: self.welcome_next_button.set_label("Connect")
|
if index == carousel.get_n_pages()-1: self.welcome_next_button.set_label("Connect")
|
||||||
else: self.welcome_next_button.set_label("Next")
|
else: self.welcome_next_button.set_label("Next")
|
||||||
|
|
||||||
|
@Gtk.Template.Callback()
|
||||||
def welcome_previous_button_activate(self, button):
|
def welcome_previous_button_activate(self, button):
|
||||||
self.welcome_carousel.scroll_to(self.welcome_carousel.get_nth_page(self.welcome_carousel.get_position()-1), True)
|
self.welcome_carousel.scroll_to(self.welcome_carousel.get_nth_page(self.welcome_carousel.get_position()-1), True)
|
||||||
|
|
||||||
|
@Gtk.Template.Callback()
|
||||||
def welcome_next_button_activate(self, button):
|
def welcome_next_button_activate(self, button):
|
||||||
if button.get_label() == "Next": self.welcome_carousel.scroll_to(self.welcome_carousel.get_nth_page(self.welcome_carousel.get_position()+1), True)
|
if button.get_label() == "Next": self.welcome_carousel.scroll_to(self.welcome_carousel.get_nth_page(self.welcome_carousel.get_position()+1), True)
|
||||||
else:
|
else:
|
||||||
@ -651,33 +580,6 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
if not self.verify_connection():
|
if not self.verify_connection():
|
||||||
self.connection_error()
|
self.connection_error()
|
||||||
|
|
||||||
def clear_chat(self):
|
|
||||||
for widget in list(self.chat_container): self.chat_container.remove(widget)
|
|
||||||
self.chats["chats"][self.chats["selected_chat"]]["messages"] = []
|
|
||||||
|
|
||||||
def clear_chat_dialog_response(self, dialog, task):
|
|
||||||
if dialog.choose_finish(task) == "clear":
|
|
||||||
self.clear_chat()
|
|
||||||
self.save_history()
|
|
||||||
|
|
||||||
def clear_chat_dialog(self):
|
|
||||||
if self.bot_message is not None:
|
|
||||||
self.show_toast("info", 1, self.main_overlay)
|
|
||||||
return
|
|
||||||
dialog = Adw.AlertDialog(
|
|
||||||
heading=_("Clear Chat"),
|
|
||||||
body=_("Are you sure you want to clear the chat?"),
|
|
||||||
close_response="cancel"
|
|
||||||
)
|
|
||||||
dialog.add_response("cancel", _("Cancel"))
|
|
||||||
dialog.add_response("clear", _("Clear"))
|
|
||||||
dialog.set_response_appearance("clear", Adw.ResponseAppearance.DESTRUCTIVE)
|
|
||||||
dialog.choose(
|
|
||||||
parent = self,
|
|
||||||
cancellable = None,
|
|
||||||
callback = self.clear_chat_dialog_response
|
|
||||||
)
|
|
||||||
|
|
||||||
def save_history(self):
|
def save_history(self):
|
||||||
with open(os.path.join(self.config_dir, "chats.json"), "w+") as f:
|
with open(os.path.join(self.config_dir, "chats.json"), "w+") as f:
|
||||||
json.dump(self.chats, f, indent=4)
|
json.dump(self.chats, f, indent=4)
|
||||||
@ -729,165 +631,107 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.show_toast("error", 5, self.main_overlay)
|
self.show_toast("error", 5, self.main_overlay)
|
||||||
|
|
||||||
def remove_image(self, dialog, task):
|
def remove_image(self):
|
||||||
if dialog.choose_finish(task) == 'remove':
|
self.image_button.set_css_classes([])
|
||||||
self.image_button.set_css_classes([])
|
self.image_button.get_child().set_icon_name("image-x-generic-symbolic")
|
||||||
self.image_button.get_child().set_icon_name("image-x-generic-symbolic")
|
self.attached_image = {"path": None, "base64": None}
|
||||||
self.attached_image = {"path": None, "base64": None}
|
|
||||||
|
|
||||||
|
@Gtk.Template.Callback()
|
||||||
def open_image(self, button):
|
def open_image(self, button):
|
||||||
if "destructive-action" in button.get_css_classes():
|
if "destructive-action" in button.get_css_classes():
|
||||||
dialog = Adw.AlertDialog(
|
dialogs.remove_image(self)
|
||||||
heading=_("Remove Image"),
|
|
||||||
body=_("Are you sure you want to remove image?"),
|
|
||||||
close_response="cancel"
|
|
||||||
)
|
|
||||||
dialog.add_response("cancel", _("Cancel"))
|
|
||||||
dialog.add_response("remove", _("Remove"))
|
|
||||||
dialog.set_response_appearance("remove", Adw.ResponseAppearance.DESTRUCTIVE)
|
|
||||||
dialog.choose(
|
|
||||||
parent = self,
|
|
||||||
cancellable = None,
|
|
||||||
callback = self.remove_image
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
file_dialog = Gtk.FileDialog(default_filter=self.file_filter_image)
|
file_dialog = Gtk.FileDialog(default_filter=self.file_filter_image)
|
||||||
file_dialog.open(self, None, self.load_image)
|
file_dialog.open(self, None, self.load_image)
|
||||||
|
|
||||||
def chat_delete(self, dialog, task, chat_name):
|
def generate_numbered_chat_name(self, chat_name) -> str:
|
||||||
if dialog.choose_finish(task) == "delete":
|
if chat_name in self.chats["chats"]:
|
||||||
del self.chats['chats'][chat_name]
|
for i in range(len(list(self.chats["chats"].keys()))):
|
||||||
self.save_history()
|
if chat_name + f" {i+1}" not in self.chats["chats"]:
|
||||||
self.update_chat_list()
|
chat_name += f" {i+1}"
|
||||||
if len(self.chats['chats'])==0:
|
break
|
||||||
self.chat_new()
|
return chat_name
|
||||||
|
|
||||||
def chat_delete_dialog(self, chat_name):
|
def clear_chat(self):
|
||||||
dialog = Adw.AlertDialog(
|
for widget in list(self.chat_container): self.chat_container.remove(widget)
|
||||||
heading=_("Delete Chat"),
|
self.chats["chats"][self.chats["selected_chat"]]["messages"] = []
|
||||||
body=_("Are you sure you want to delete '{}'?").format(chat_name),
|
self.save_history()
|
||||||
close_response="cancel"
|
|
||||||
)
|
|
||||||
dialog.add_response("cancel", _("Cancel"))
|
|
||||||
dialog.add_response("delete", _("Delete"))
|
|
||||||
dialog.set_response_appearance("delete", Adw.ResponseAppearance.DESTRUCTIVE)
|
|
||||||
dialog.choose(
|
|
||||||
parent = self,
|
|
||||||
cancellable = None,
|
|
||||||
callback = lambda dialog, task, chat_name=chat_name: self.chat_delete(dialog, task, chat_name)
|
|
||||||
)
|
|
||||||
def chat_rename(self, dialog=None, task=None, old_chat_name:str="", entry=None):
|
|
||||||
if not entry: return
|
|
||||||
new_chat_name = entry.get_text()
|
|
||||||
if old_chat_name == new_chat_name: return
|
|
||||||
if new_chat_name and (not task or dialog.choose_finish(task) == "rename"):
|
|
||||||
dialog.force_close()
|
|
||||||
if new_chat_name in self.chats["chats"]: self.chat_rename_dialog(old_chat_name, f"The name '{new_chat_name}' is already in use", True)
|
|
||||||
else:
|
|
||||||
self.chats["chats"][new_chat_name] = self.chats["chats"][old_chat_name]
|
|
||||||
del self.chats["chats"][old_chat_name]
|
|
||||||
self.save_history()
|
|
||||||
self.update_chat_list()
|
|
||||||
|
|
||||||
def chat_rename_dialog(self, chat_name:str, body:str, error:bool=False):
|
def delete_chat(self, chat_name):
|
||||||
entry = Gtk.Entry(
|
del self.chats['chats'][chat_name]
|
||||||
css_classes = ["error"] if error else None
|
self.save_history()
|
||||||
)
|
self.update_chat_list()
|
||||||
dialog = Adw.AlertDialog(
|
if len(self.chats['chats'])==0:
|
||||||
heading=_("Rename Chat"),
|
self.chat_new()
|
||||||
body=body,
|
|
||||||
extra_child=entry,
|
|
||||||
close_response="cancel"
|
|
||||||
)
|
|
||||||
entry.connect("activate", lambda entry, dialog=dialog, old_chat_name=chat_name: self.chat_rename(dialog=dialog, old_chat_name=old_chat_name, entry=entry))
|
|
||||||
dialog.add_response("cancel", _("Cancel"))
|
|
||||||
dialog.add_response("rename", _("Rename"))
|
|
||||||
dialog.set_response_appearance("rename", Adw.ResponseAppearance.SUGGESTED)
|
|
||||||
dialog.choose(
|
|
||||||
parent = self,
|
|
||||||
cancellable = None,
|
|
||||||
callback = lambda dialog, task, old_chat_name=chat_name, entry=entry: self.chat_rename(dialog=dialog, task=task, old_chat_name=old_chat_name, entry=entry)
|
|
||||||
)
|
|
||||||
|
|
||||||
def chat_new(self, dialog=None, task=None, entry=None):
|
def rename_chat(self, old_chat_name, new_chat_name, label_element):
|
||||||
#if not entry: return
|
new_chat_name = self.generate_numbered_chat_name(new_chat_name)
|
||||||
chat_name = None
|
self.chats["chats"][new_chat_name] = self.chats["chats"][old_chat_name]
|
||||||
if entry is not None: chat_name = entry.get_text()
|
del self.chats["chats"][old_chat_name]
|
||||||
if not chat_name:
|
label_element.set_label(new_chat_name)
|
||||||
chat_name=_("New Chat")
|
self.save_history()
|
||||||
if chat_name in self.chats["chats"]:
|
|
||||||
for i in range(len(list(self.chats["chats"].keys()))):
|
|
||||||
if chat_name + f" {i+1}" not in self.chats["chats"]:
|
|
||||||
chat_name += f" {i+1}"
|
|
||||||
break
|
|
||||||
if not task or dialog.choose_finish(task) == "create":
|
|
||||||
if dialog is not None: dialog.force_close()
|
|
||||||
if chat_name in self.chats["chats"]: self.chat_new_dialog(_("The name '{}' is already in use").format(chat_name), True)
|
|
||||||
else:
|
|
||||||
self.chats["chats"][chat_name] = {"messages": []}
|
|
||||||
self.chats["selected_chat"] = chat_name
|
|
||||||
self.save_history()
|
|
||||||
self.update_chat_list()
|
|
||||||
self.load_history_into_chat()
|
|
||||||
|
|
||||||
def chat_new_dialog(self, body:str, error:bool=False):
|
def new_chat(self, chat_name):
|
||||||
entry = Gtk.Entry(
|
chat_name = self.generate_numbered_chat_name(chat_name)
|
||||||
css_classes = ["error"] if error else None
|
self.chats["chats"][chat_name] = {"messages": []}
|
||||||
|
self.chats["selected_chat"] = chat_name
|
||||||
|
self.save_history()
|
||||||
|
self.new_chat_element(chat_name)
|
||||||
|
|
||||||
|
def stop_pull_model(self, model_name):
|
||||||
|
self.pulling_models[model_name].get_parent().remove(self.pulling_models[model_name])
|
||||||
|
del self.pulling_models[model_name]
|
||||||
|
|
||||||
|
def delete_model(self, model_name):
|
||||||
|
response = connection_handler.simple_delete(connection_handler.url + "/api/delete", data={"name": model_name})
|
||||||
|
self.update_list_local_models()
|
||||||
|
if response['status'] == 'ok':
|
||||||
|
self.show_toast("good", 0, self.manage_models_overlay)
|
||||||
|
else:
|
||||||
|
self.manage_models_dialog.close()
|
||||||
|
self.connection_error()
|
||||||
|
|
||||||
|
def new_chat_element(self, chat_name):
|
||||||
|
chat_content = Gtk.Box(
|
||||||
|
spacing=6
|
||||||
)
|
)
|
||||||
dialog = Adw.AlertDialog(
|
chat_row = Gtk.ListBoxRow(
|
||||||
heading=_("Create Chat"),
|
css_classes = ["chat_row"],
|
||||||
body=body,
|
height_request = 45,
|
||||||
extra_child=entry,
|
child = chat_content,
|
||||||
close_response="cancel"
|
name = chat_name
|
||||||
)
|
)
|
||||||
entry.connect("activate", lambda entry, dialog=dialog: self.chat_new(dialog=dialog, entry=entry))
|
chat_label = Gtk.Label(
|
||||||
dialog.add_response("cancel", _("Cancel"))
|
label=chat_name,
|
||||||
dialog.add_response("create", _("Create"))
|
hexpand=True,
|
||||||
dialog.set_response_appearance("rename", Adw.ResponseAppearance.SUGGESTED)
|
halign=1
|
||||||
dialog.choose(
|
|
||||||
parent = self,
|
|
||||||
cancellable = None,
|
|
||||||
callback = lambda dialog, task, entry=entry: self.chat_new(dialog=dialog, task=task, entry=entry)
|
|
||||||
)
|
)
|
||||||
|
button_delete = Gtk.Button(
|
||||||
|
icon_name = "user-trash-symbolic",
|
||||||
|
vexpand = False,
|
||||||
|
valign = 3,
|
||||||
|
css_classes = ["error", "flat"]
|
||||||
|
)
|
||||||
|
button_delete.connect("clicked", lambda button, chat_name=chat_name: dialogs.delete_chat(self, chat_name))
|
||||||
|
button_rename = Gtk.Button(
|
||||||
|
icon_name = "document-edit-symbolic",
|
||||||
|
vexpand = False,
|
||||||
|
valign = 3,
|
||||||
|
css_classes = ["accent", "flat"]
|
||||||
|
)
|
||||||
|
button_rename.connect("clicked", lambda button, chat_name=chat_name, label_element=chat_label: dialogs.rename_chat(self, chat_name, label_element))
|
||||||
|
chat_content.append(chat_label)
|
||||||
|
chat_content.append(button_delete)
|
||||||
|
chat_content.append(button_rename)
|
||||||
|
self.chat_list_box.append(chat_row)
|
||||||
|
if chat_name==self.chats["selected_chat"]: self.chat_list_box.select_row(chat_row)
|
||||||
|
|
||||||
def update_chat_list(self):
|
def update_chat_list(self):
|
||||||
self.chat_list_box.remove_all()
|
self.chat_list_box.remove_all()
|
||||||
for name, content in self.chats['chats'].items():
|
for name, content in self.chats['chats'].items():
|
||||||
chat_content = Gtk.Box(
|
self.new_chat_element(name)
|
||||||
spacing = 6,
|
|
||||||
)
|
|
||||||
chat_row = Gtk.ListBoxRow(
|
|
||||||
css_classes = ["chat_row"],
|
|
||||||
height_request = 45,
|
|
||||||
child=chat_content,
|
|
||||||
name = name
|
|
||||||
)
|
|
||||||
chat_label = Gtk.Label(
|
|
||||||
label=name,
|
|
||||||
hexpand=True,
|
|
||||||
halign=1
|
|
||||||
)
|
|
||||||
button_delete = Gtk.Button(
|
|
||||||
icon_name = "user-trash-symbolic",
|
|
||||||
vexpand = False,
|
|
||||||
valign = 3,
|
|
||||||
css_classes = ["error", "flat"]
|
|
||||||
)
|
|
||||||
button_delete.connect("clicked", lambda button, chat_name=name: self.chat_delete_dialog(chat_name=chat_name))
|
|
||||||
button_rename = Gtk.Button(
|
|
||||||
icon_name = "document-edit-symbolic",
|
|
||||||
vexpand = False,
|
|
||||||
valign = 3,
|
|
||||||
css_classes = ["accent", "flat"]
|
|
||||||
)
|
|
||||||
button_rename.connect("clicked", lambda button, chat_name=name: self.chat_rename_dialog(chat_name=chat_name, body=f"Renaming '{chat_name}'", error=False))
|
|
||||||
|
|
||||||
chat_content.append(chat_label)
|
|
||||||
chat_content.append(button_delete)
|
|
||||||
chat_content.append(button_rename)
|
|
||||||
self.chat_list_box.append(chat_row)
|
|
||||||
if name==self.chats["selected_chat"]: self.chat_list_box.select_row(chat_row)
|
|
||||||
|
|
||||||
|
@Gtk.Template.Callback()
|
||||||
def chat_changed(self, listbox, row):
|
def chat_changed(self, listbox, row):
|
||||||
if row and row.get_name() != self.chats["selected_chat"]:
|
if row and row.get_name() != self.chats["selected_chat"]:
|
||||||
self.chats["selected_chat"] = row.get_name()
|
self.chats["selected_chat"] = row.get_name()
|
||||||
@ -901,66 +745,24 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
def show_preferences_dialog(self):
|
def show_preferences_dialog(self):
|
||||||
self.preferences_dialog.present(self)
|
self.preferences_dialog.present(self)
|
||||||
|
|
||||||
def start_instance(self):
|
def connect_remote(self, url):
|
||||||
self.ollama_instance = subprocess.Popen(["/app/bin/ollama", "serve"], env={**os.environ, 'OLLAMA_HOST': f"127.0.0.1:{self.local_ollama_port}", "HOME": self.data_dir}, stderr=subprocess.PIPE, text=True)
|
connection_handler.url = url
|
||||||
print("Starting Alpaca's Ollama instance...")
|
self.remote_url = connection_handler.url
|
||||||
sleep(1)
|
self.remote_connection_entry.set_text(self.remote_url)
|
||||||
while True:
|
if self.verify_connection() == False: self.connection_error()
|
||||||
err = self.ollama_instance.stderr.readline()
|
|
||||||
if err == '' and self.ollama_instance.poll() is not None:
|
|
||||||
break
|
|
||||||
if 'msg="inference compute"' in err: #Ollama outputs a line with this when it finishes loading, yeah
|
|
||||||
break
|
|
||||||
print("Started Alpaca's Ollama instance")
|
|
||||||
|
|
||||||
def stop_instance(self):
|
def connect_local(self):
|
||||||
self.ollama_instance.kill()
|
self.run_remote = False
|
||||||
print("Stopped Alpaca's Ollama instance")
|
connection_handler.url = f"http://127.0.0.1:{local_instance.port}"
|
||||||
|
local_instance.start(self.data_dir)
|
||||||
def restart_instance(self):
|
if self.verify_connection() == False: self.connection_error()
|
||||||
if self.ollama_instance is not None: self.stop_instance()
|
else: self.remote_connection_switch.set_active(False)
|
||||||
start_instance(self)
|
|
||||||
|
|
||||||
def reconnect_remote(self, dialog, task=None, entry=None):
|
|
||||||
response = dialog.choose_finish(task)
|
|
||||||
dialog.force_close()
|
|
||||||
if not task or response == "remote":
|
|
||||||
self.ollama_url = entry.get_text()
|
|
||||||
self.remote_url = self.ollama_url
|
|
||||||
self.remote_connection_entry.set_text(self.remote_url)
|
|
||||||
if self.verify_connection() == False: self.connection_error()
|
|
||||||
elif response == "local":
|
|
||||||
self.run_remote = False
|
|
||||||
self.ollama_url = f"http://127.0.0.1:{self.local_ollama_port}"
|
|
||||||
self.start_instance()
|
|
||||||
if self.verify_connection() == False: self.connection_error()
|
|
||||||
else: self.remote_connection_switch.set_active(False)
|
|
||||||
elif response == "close":
|
|
||||||
self.destroy()
|
|
||||||
|
|
||||||
def connection_error(self):
|
def connection_error(self):
|
||||||
if self.run_remote:
|
if self.run_remote:
|
||||||
entry = Gtk.Entry(
|
dialogs.reconnect_remote(self)
|
||||||
css_classes = ["error"],
|
|
||||||
text = self.ollama_url
|
|
||||||
)
|
|
||||||
dialog = Adw.AlertDialog(
|
|
||||||
heading=_("Connection Error"),
|
|
||||||
body=_("The remote instance has disconnected"),
|
|
||||||
extra_child=entry
|
|
||||||
)
|
|
||||||
entry.connect("activate", lambda entry, dialog=dialog: self.reconnect_remote(dialog=dialog, entry=entry))
|
|
||||||
dialog.add_response("close", _("Close Alpaca"))
|
|
||||||
dialog.add_response("local", _("Use local instance"))
|
|
||||||
dialog.add_response("remote", _("Connect"))
|
|
||||||
dialog.set_response_appearance("remote", Adw.ResponseAppearance.SUGGESTED)
|
|
||||||
dialog.choose(
|
|
||||||
parent = self,
|
|
||||||
cancellable = None,
|
|
||||||
callback = lambda dialog, task, entry=entry: self.reconnect_remote(dialog=dialog, task=task, entry=entry)
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
self.restart_instance()
|
local_instance.restart()
|
||||||
self.show_toast("error", 7, self.main_overlay)
|
self.show_toast("error", 7, self.main_overlay)
|
||||||
|
|
||||||
def connection_switched(self):
|
def connection_switched(self):
|
||||||
@ -968,20 +770,20 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
if new_value != self.run_remote:
|
if new_value != self.run_remote:
|
||||||
self.run_remote = new_value
|
self.run_remote = new_value
|
||||||
if self.run_remote:
|
if self.run_remote:
|
||||||
self.ollama_url = self.remote_url
|
connection_handler.url = self.remote_url
|
||||||
if self.verify_connection() == False: self.connection_error()
|
if self.verify_connection() == False: self.connection_error()
|
||||||
else: self.stop_instance()
|
else: local_instance.stop(self)
|
||||||
else:
|
else:
|
||||||
self.ollama_url = f"http://127.0.0.1:{self.local_ollama_port}"
|
connection_handler.url = f"http://127.0.0.1:{local_instance.port}"
|
||||||
self.start_instance()
|
local_instance.start(self.data_dir)
|
||||||
if self.verify_connection() == False: self.connection_error()
|
if self.verify_connection() == False: self.connection_error()
|
||||||
self.update_list_available_models()
|
self.update_list_available_models()
|
||||||
self.update_list_local_models()
|
|
||||||
|
|
||||||
|
@Gtk.Template.Callback()
|
||||||
def change_remote_url(self, entry):
|
def change_remote_url(self, entry):
|
||||||
self.remote_url = entry.get_text()
|
self.remote_url = entry.get_text()
|
||||||
if self.run_remote:
|
if self.run_remote:
|
||||||
self.ollama_url = self.remote_url
|
connection_handler.url = self.remote_url
|
||||||
if self.verify_connection() == False:
|
if self.verify_connection() == False:
|
||||||
entry.set_css_classes(["error"])
|
entry.set_css_classes(["error"])
|
||||||
self.show_toast("error", 1, self.preferences_dialog)
|
self.show_toast("error", 1, self.preferences_dialog)
|
||||||
@ -1033,46 +835,36 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
GtkSource.init()
|
GtkSource.init()
|
||||||
self.set_help_overlay(self.shortcut_window)
|
self.set_help_overlay(self.shortcut_window)
|
||||||
self.get_application().set_accels_for_action("win.show-help-overlay", ['<primary>slash'])
|
self.get_application().set_accels_for_action("win.show-help-overlay", ['<primary>slash'])
|
||||||
|
self.get_application().create_action('clear', lambda *_: dialogs.clear_chat(self), ['<primary>e'])
|
||||||
self.get_application().create_action('send', lambda *_: self.send_message(self), ['Return'])
|
self.get_application().create_action('send', lambda *_: self.send_message(self), ['Return'])
|
||||||
self.manage_models_button.connect("clicked", self.manage_models_button_activate)
|
self.add_chat_button.connect("clicked", lambda button : dialogs.new_chat(self))
|
||||||
self.send_button.connect("clicked", self.send_message)
|
|
||||||
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.set_default_widget(self.send_button)
|
|
||||||
self.model_drop_down.connect("notify", self.verify_if_image_can_be_used)
|
|
||||||
self.chat_list_box.connect("row-selected", self.chat_changed)
|
|
||||||
self.welcome_carousel.connect("page-changed", self.welcome_carousel_page_changed)
|
|
||||||
self.welcome_previous_button.connect("clicked", self.welcome_previous_button_activate)
|
|
||||||
self.welcome_next_button.connect("clicked", self.welcome_next_button_activate)
|
|
||||||
|
|
||||||
self.export_chat_button.connect("clicked", lambda button : self.export_current_chat())
|
self.export_chat_button.connect("clicked", lambda button : self.export_current_chat())
|
||||||
self.import_chat_button.connect("clicked", lambda button : self.import_chat())
|
self.import_chat_button.connect("clicked", lambda button : self.import_chat())
|
||||||
#Preferences
|
|
||||||
self.remote_connection_entry.connect("entry-activated", lambda entry : entry.set_css_classes([]))
|
self.remote_connection_entry.connect("entry-activated", lambda entry : entry.set_css_classes([]))
|
||||||
self.remote_connection_entry.connect("apply", self.change_remote_url)
|
|
||||||
self.remote_connection_switch.connect("notify", lambda pspec, user_data : self.connection_switched())
|
self.remote_connection_switch.connect("notify", lambda pspec, user_data : self.connection_switched())
|
||||||
self.background_switch.connect("notify", lambda pspec, user_data : self.switch_run_on_background())
|
self.background_switch.connect("notify", lambda pspec, user_data : self.switch_run_on_background())
|
||||||
if os.path.exists(os.path.join(self.config_dir, "server.json")):
|
if os.path.exists(os.path.join(self.config_dir, "server.json")):
|
||||||
with open(os.path.join(self.config_dir, "server.json"), "r") as f:
|
with open(os.path.join(self.config_dir, "server.json"), "r") as f:
|
||||||
data = json.load(f)
|
data = json.load(f)
|
||||||
self.run_remote = data['run_remote']
|
self.run_remote = data['run_remote']
|
||||||
self.local_ollama_port = data['local_port']
|
local_instance.port = data['local_port']
|
||||||
self.remote_url = data['remote_url']
|
self.remote_url = data['remote_url']
|
||||||
self.run_on_background = data['run_on_background']
|
self.run_on_background = data['run_on_background']
|
||||||
self.background_switch.set_active(self.run_on_background)
|
self.background_switch.set_active(self.run_on_background)
|
||||||
self.set_hide_on_close(self.run_on_background)
|
self.set_hide_on_close(self.run_on_background)
|
||||||
self.remote_connection_entry.set_text(self.remote_url)
|
self.remote_connection_entry.set_text(self.remote_url)
|
||||||
if self.run_remote:
|
if self.run_remote:
|
||||||
self.ollama_url = data['remote_url']
|
connection_handler.url = data['remote_url']
|
||||||
self.remote_connection_switch.set_active(True)
|
self.remote_connection_switch.set_active(True)
|
||||||
else:
|
else:
|
||||||
self.remote_connection_switch.set_active(False)
|
self.remote_connection_switch.set_active(False)
|
||||||
self.ollama_url = f"http://127.0.0.1:{self.local_ollama_port}"
|
connection_handler.url = f"http://127.0.0.1:{local_instance.port}"
|
||||||
self.start_instance()
|
local_instance.start(self.data_dir)
|
||||||
else:
|
else:
|
||||||
self.start_instance()
|
local_instance.start(self.data_dir)
|
||||||
self.ollama_url = f"http://127.0.0.1:{self.local_ollama_port}"
|
connection_handler.url = f"http://127.0.0.1:{local_instance.port}"
|
||||||
self.first_time_setup = True
|
|
||||||
self.welcome_dialog.present(self)
|
self.welcome_dialog.present(self)
|
||||||
if self.verify_connection() is False and self.run_remote == False: self.connection_error()
|
if self.verify_connection() is False and self.run_remote == False: self.connection_error()
|
||||||
self.update_list_available_models()
|
self.update_list_available_models()
|
||||||
|
@ -36,7 +36,7 @@
|
|||||||
<child type="start">
|
<child type="start">
|
||||||
<object class="GtkButton" id="add_chat_button">
|
<object class="GtkButton" id="add_chat_button">
|
||||||
<property name="tooltip-text" translatable="yes">New chat</property>
|
<property name="tooltip-text" translatable="yes">New chat</property>
|
||||||
<property name="icon-name">tab-new-symbolic</property>
|
<property name="icon-name">chat-message-new-symbolic</property>
|
||||||
<style>
|
<style>
|
||||||
<class name="flat"/>
|
<class name="flat"/>
|
||||||
</style>
|
</style>
|
||||||
@ -68,6 +68,7 @@
|
|||||||
<property name="hexpand">true</property>
|
<property name="hexpand">true</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkListBox" id="chat_list_box">
|
<object class="GtkListBox" id="chat_list_box">
|
||||||
|
<signal name="row-selected" handler="chat_changed"/>
|
||||||
<property name="selection-mode">single</property>
|
<property name="selection-mode">single</property>
|
||||||
<style>
|
<style>
|
||||||
<class name="navigation-sidebar"></class>
|
<class name="navigation-sidebar"></class>
|
||||||
@ -95,6 +96,7 @@
|
|||||||
<property name="spacing">12</property>
|
<property name="spacing">12</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkDropDown" id="model_drop_down">
|
<object class="GtkDropDown" id="model_drop_down">
|
||||||
|
<signal name="notify" handler="verify_if_image_can_be_used"/>
|
||||||
<property name="enable-search">true</property>
|
<property name="enable-search">true</property>
|
||||||
<property name="model">
|
<property name="model">
|
||||||
<object class="GtkStringList" id="model_string_list">
|
<object class="GtkStringList" id="model_string_list">
|
||||||
@ -106,6 +108,7 @@
|
|||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkButton" id="manage_models_button">
|
<object class="GtkButton" id="manage_models_button">
|
||||||
|
<signal name="clicked" handler="manage_models_button_activate"/>
|
||||||
<property name="tooltip-text" translatable="yes">Manage models</property>
|
<property name="tooltip-text" translatable="yes">Manage models</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="AdwButtonContent">
|
<object class="AdwButtonContent">
|
||||||
@ -209,6 +212,7 @@
|
|||||||
<property name="spacing">12</property>
|
<property name="spacing">12</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkButton" id="send_button">
|
<object class="GtkButton" id="send_button">
|
||||||
|
<signal name="clicked" handler="send_message"/>
|
||||||
<style>
|
<style>
|
||||||
<class name="suggested-action"/>
|
<class name="suggested-action"/>
|
||||||
</style>
|
</style>
|
||||||
@ -222,6 +226,7 @@
|
|||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkButton" id="image_button">
|
<object class="GtkButton" id="image_button">
|
||||||
|
<signal name="clicked" handler="open_image"/>
|
||||||
<property name="sensitive">false</property>
|
<property name="sensitive">false</property>
|
||||||
<property name="tooltip-text" translatable="yes">Only available on selected models</property>
|
<property name="tooltip-text" translatable="yes">Only available on selected models</property>
|
||||||
<child>
|
<child>
|
||||||
@ -266,6 +271,7 @@
|
|||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="AdwEntryRow" id="remote_connection_entry">
|
<object class="AdwEntryRow" id="remote_connection_entry">
|
||||||
|
<signal name="apply" handler="change_remote_url"/>
|
||||||
<property name="title" translatable="yes">URL of remote instance</property>
|
<property name="title" translatable="yes">URL of remote instance</property>
|
||||||
<property name="show-apply-button">true</property>
|
<property name="show-apply-button">true</property>
|
||||||
</object>
|
</object>
|
||||||
@ -367,6 +373,7 @@
|
|||||||
<property name="margin-bottom">5</property>
|
<property name="margin-bottom">5</property>
|
||||||
<child type="start">
|
<child type="start">
|
||||||
<object class="GtkButton" id="welcome_previous_button">
|
<object class="GtkButton" id="welcome_previous_button">
|
||||||
|
<signal name="clicked" handler="welcome_previous_button_activate"/>
|
||||||
<property name="tooltip-text" translatable="yes">Previous</property>
|
<property name="tooltip-text" translatable="yes">Previous</property>
|
||||||
<property name="label">Previous</property>
|
<property name="label">Previous</property>
|
||||||
<property name="sensitive">false</property>
|
<property name="sensitive">false</property>
|
||||||
@ -382,6 +389,7 @@
|
|||||||
</child>
|
</child>
|
||||||
<child type="end">
|
<child type="end">
|
||||||
<object class="GtkButton" id="welcome_next_button">
|
<object class="GtkButton" id="welcome_next_button">
|
||||||
|
<signal name="clicked" handler="welcome_next_button_activate"/>
|
||||||
<property name="tooltip-text" translatable="yes">Next</property>
|
<property name="tooltip-text" translatable="yes">Next</property>
|
||||||
<property name="label">Next</property>
|
<property name="label">Next</property>
|
||||||
<style>
|
<style>
|
||||||
@ -394,6 +402,7 @@
|
|||||||
|
|
||||||
<child>
|
<child>
|
||||||
<object class="AdwCarousel" id="welcome_carousel">
|
<object class="AdwCarousel" id="welcome_carousel">
|
||||||
|
<signal name="page-changed" handler="welcome_carousel_page_changed"/>
|
||||||
<property name="hexpand">true</property>
|
<property name="hexpand">true</property>
|
||||||
<property name="vexpand">true</property>
|
<property name="vexpand">true</property>
|
||||||
<property name="allow-long-swipes">true</property>
|
<property name="allow-long-swipes">true</property>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user