Rewrote a whole new dialog system cause I was bored
This commit is contained in:
parent
7f042a906d
commit
3156c70260
@ -86,6 +86,8 @@ class chat(Gtk.ScrolledWindow):
|
||||
self.stop_message()
|
||||
for widget in list(self.container):
|
||||
self.container.remove(widget)
|
||||
self.show_welcome_screen(len(window.model_manager.get_model_list()) > 0)
|
||||
print('clear chat for some reason')
|
||||
|
||||
def add_message(self, message_id:str, model:str=None):
|
||||
msg = message(message_id, model)
|
||||
@ -102,7 +104,9 @@ class chat(Gtk.ScrolledWindow):
|
||||
if self.welcome_screen:
|
||||
self.container.remove(self.welcome_screen)
|
||||
self.welcome_screen = None
|
||||
self.clear_chat()
|
||||
if len(list(self.container)) > 0:
|
||||
self.clear_chat()
|
||||
return
|
||||
button_container = Gtk.Box(
|
||||
orientation=1,
|
||||
spacing=10,
|
||||
@ -333,6 +337,8 @@ class chat_list(Gtk.ListBox):
|
||||
window.save_history()
|
||||
|
||||
def rename_chat(self, old_chat_name:str, new_chat_name:str):
|
||||
if new_chat_name == old_chat_name:
|
||||
return
|
||||
tab = self.get_tab_by_name(old_chat_name)
|
||||
if tab:
|
||||
new_chat_name = window.generate_numbered_name(new_chat_name, [tab.chat_window.get_name() for tab in self.tab_list])
|
||||
|
173
src/custom_widgets/dialog_widget.py
Normal file
173
src/custom_widgets/dialog_widget.py
Normal file
@ -0,0 +1,173 @@
|
||||
#dialog_widget.py
|
||||
"""
|
||||
Handles all dialogs
|
||||
"""
|
||||
|
||||
import gi
|
||||
gi.require_version('Gtk', '4.0')
|
||||
gi.require_version('GtkSource', '5')
|
||||
from gi.repository import Gtk, Gio, Adw, Gdk, GLib
|
||||
|
||||
window=None
|
||||
|
||||
button_appearance={
|
||||
'suggested': Adw.ResponseAppearance.SUGGESTED,
|
||||
'destructive': Adw.ResponseAppearance.DESTRUCTIVE
|
||||
}
|
||||
|
||||
# Don't call this directly outside this script
|
||||
class baseDialog(Adw.AlertDialog):
|
||||
__gtype_name__ = 'AlpacaDialogBase'
|
||||
|
||||
def __init__(self, heading:str, body:str, close_response:str, options:dict):
|
||||
self.options = options
|
||||
super().__init__(
|
||||
heading=heading,
|
||||
body=body,
|
||||
close_response=close_response
|
||||
)
|
||||
for option, data in self.options.items():
|
||||
self.add_response(option, option)
|
||||
if 'appearance' in data:
|
||||
self.set_response_appearance(option, button_appearance[data['appearance']])
|
||||
if 'default' in data and data['default']:
|
||||
self.set_default_response(option)
|
||||
|
||||
|
||||
class Options(baseDialog):
|
||||
__gtype_name__ = 'AlpacaDialogOptions'
|
||||
|
||||
def __init__(self, heading:str, body:str, close_response:str, options:dict):
|
||||
super().__init__(
|
||||
heading,
|
||||
body,
|
||||
close_response,
|
||||
options
|
||||
)
|
||||
self.choose(
|
||||
parent = window,
|
||||
cancellable = None,
|
||||
callback = self.response
|
||||
)
|
||||
|
||||
def response(self, dialog, task):
|
||||
result = dialog.choose_finish(task)
|
||||
if result in self.options and 'callback' in self.options[result]:
|
||||
self.options[result]['callback']()
|
||||
|
||||
class Entry(baseDialog):
|
||||
__gtype_name__ = 'AlpacaDialogEntry'
|
||||
|
||||
def __init__(self, heading:str, body:str, close_response:str, options:dict, entries:list or dict):
|
||||
super().__init__(
|
||||
heading,
|
||||
body,
|
||||
close_response,
|
||||
options
|
||||
)
|
||||
|
||||
self.container = Gtk.Box(
|
||||
orientation=1,
|
||||
spacing=10
|
||||
)
|
||||
|
||||
if isinstance(entries, dict):
|
||||
entries = [entries]
|
||||
|
||||
for data in entries:
|
||||
entry = Gtk.Entry()
|
||||
if 'placeholder' in data and data['placeholder']:
|
||||
entry.set_placeholder_text(data['placeholder'])
|
||||
if 'css' in data and data['css']:
|
||||
entry.set_css_classes(data['css'])
|
||||
if 'text' in data and data['text']:
|
||||
entry.set_text(data['text'])
|
||||
self.container.append(entry)
|
||||
|
||||
self.set_extra_child(self.container)
|
||||
|
||||
self.connect('realize', lambda *_: list(self.container)[0].grab_focus())
|
||||
self.choose(
|
||||
parent = window,
|
||||
cancellable = None,
|
||||
callback = self.response
|
||||
)
|
||||
|
||||
def response(self, dialog, task):
|
||||
result = dialog.choose_finish(task)
|
||||
if result in self.options and 'callback' in self.options[result]:
|
||||
entry_results = []
|
||||
for entry in list(self.container):
|
||||
entry_results.append(entry.get_text())
|
||||
self.options[result]['callback'](*entry_results)
|
||||
|
||||
class DropDown(baseDialog):
|
||||
__gtype_name__ = 'AlpacaDialogDropDown'
|
||||
|
||||
def __init__(self, heading:str, body:str, close_response:str, options:dict, items:list):
|
||||
super().__init__(
|
||||
heading,
|
||||
body,
|
||||
close_response,
|
||||
options
|
||||
)
|
||||
string_list = Gtk.StringList()
|
||||
for item in items:
|
||||
string_list.append(item)
|
||||
self.set_extra_child(Gtk.DropDown(
|
||||
enable_search=len(items) > 10,
|
||||
model=string_list
|
||||
))
|
||||
|
||||
self.connect('realize', lambda *_: self.get_extra_child().grab_focus())
|
||||
self.choose(
|
||||
parent = window,
|
||||
cancellable = None,
|
||||
callback = lambda dialog, task, dropdown=self.get_extra_child(): self.response(dialog, task, dropdown.get_selected_item().get_string())
|
||||
)
|
||||
|
||||
def response(self, dialog, task, item:str):
|
||||
result = dialog.choose_finish(task)
|
||||
if result in self.options and 'callback' in self.options[result]:
|
||||
self.options[result]['callback'](item)
|
||||
|
||||
def simple(heading:str, body:str, callback:callable, button_name:str=_('Accept'), button_appearance:str='suggested'):
|
||||
options = {
|
||||
_('Cancel'): {},
|
||||
button_name: {
|
||||
'appearance': button_appearance,
|
||||
'callback': callback,
|
||||
'default': True
|
||||
}
|
||||
}
|
||||
|
||||
return Options(heading, body, 'cancel', options)
|
||||
|
||||
def simple_entry(heading:str, body:str, callback:callable, entries:list or dict, button_name:str=_('Accept'), button_appearance:str='suggested'):
|
||||
options = {
|
||||
_('Cancel'): {},
|
||||
button_name: {
|
||||
'appearance': button_appearance,
|
||||
'callback': callback,
|
||||
'default': True
|
||||
}
|
||||
}
|
||||
|
||||
return Entry(heading, body, 'cancel', options, entries)
|
||||
|
||||
def simple_dropdown(heading:str, body:str, callback:callable, items:list, button_name:str=_('Accept'), button_appearance:str='suggested'):
|
||||
options = {
|
||||
_('Cancel'): {},
|
||||
button_name: {
|
||||
'appearance': button_appearance,
|
||||
'callback': callback,
|
||||
'default': True
|
||||
}
|
||||
}
|
||||
|
||||
return DropDown(heading, body, 'cancel', options, items)
|
||||
|
||||
def simple_file(file_filter:Gtk.FileFilter, callback:callable):
|
||||
file_dialog = Gtk.FileDialog(default_filter=file_filter)
|
||||
file_dialog.open(window, None, lambda file_dialog, result: callback(file_dialog.open_finish(result)) if result else None)
|
||||
|
@ -10,7 +10,7 @@ from gi.repository import Gtk, GObject, Gio, Adw, GtkSource, GLib, Gdk
|
||||
import logging, os, datetime, re, shutil, threading, sys
|
||||
from ..internal import config_dir, data_dir, cache_dir, source_dir
|
||||
from .table_widget import TableWidget
|
||||
from .. import dialogs
|
||||
from . import dialog_widget, terminal_widget
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -180,7 +180,13 @@ class code_block(Gtk.Box):
|
||||
logger.debug("Running script")
|
||||
start = self.buffer.get_start_iter()
|
||||
end = self.buffer.get_end_iter()
|
||||
dialogs.run_script(window, self.buffer.get_text(start, end, False), language_name)
|
||||
dialog_widget.simple(
|
||||
_('Run Script'),
|
||||
_('Make sure you understand what this script does before running it, Alpaca is not responsible for any damages to your device or data'),
|
||||
lambda script=self.buffer.get_text(start, end, False), language_name=language_name: terminal_widget.run_terminal(script, language_name),
|
||||
_('Execute'),
|
||||
'destructive'
|
||||
)
|
||||
|
||||
class attachment(Gtk.Button):
|
||||
__gtype_name__ = 'AlpacaAttachment'
|
||||
|
@ -10,6 +10,7 @@ from gi.repository import Gtk, GObject, Gio, Adw, GtkSource, GLib, Gdk
|
||||
import logging, os, datetime, re, shutil, threading, json, sys, glob
|
||||
from ..internal import config_dir, data_dir, cache_dir, source_dir
|
||||
from .. import available_models_descriptions, dialogs
|
||||
from . import dialog_widget
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -178,7 +179,7 @@ class pulling_model(Gtk.ListBoxRow):
|
||||
css_classes = ["error", "circular"],
|
||||
tooltip_text = _("Stop Pulling '{}'").format(window.convert_model_name(model_name, 0))
|
||||
)
|
||||
stop_button.connect('clicked', lambda *_: dialogs.stop_pull_model(window, self))
|
||||
stop_button.connect('clicked', lambda *i: dialog_widget.simple(_('Stop Download?'), _("Are you sure you want to stop pulling '{}'?").format(window.convert_model_name(self.get_name(), 0)), self.stop, _('Stop'), 'destructive'))
|
||||
|
||||
container_box = Gtk.Box(
|
||||
hexpand=True,
|
||||
@ -201,6 +202,11 @@ class pulling_model(Gtk.ListBoxRow):
|
||||
self.error = None
|
||||
self.digests = []
|
||||
|
||||
def stop(self):
|
||||
if len(list(self.get_parent())) == 1:
|
||||
self.get_parent().set_visible(False)
|
||||
self.get_parent().remove(self)
|
||||
|
||||
def update(self, data):
|
||||
if 'digest' in data and data['digest'] not in self.digests:
|
||||
self.digests.append(data['digest'].replace(':', '-'))
|
||||
@ -270,7 +276,8 @@ class local_model(Gtk.ListBoxRow):
|
||||
css_classes = ["error", "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))
|
||||
|
||||
delete_button.connect('clicked', lambda *i: dialog_widget.simple(_('Delete Model?'), _("Are you sure you want to delete '{}'?").format(model_title), lambda model_name=model_name: window.model_manager.remove_local_model(model_name), _('Delete'), 'destructive'))
|
||||
|
||||
container_box = Gtk.Box(
|
||||
hexpand=True,
|
||||
|
@ -7,6 +7,12 @@ import gi
|
||||
gi.require_version('Gtk', '4.0')
|
||||
gi.require_version('Vte', '3.91')
|
||||
from gi.repository import Gtk, Vte, GLib, Pango, GLib, Gdk
|
||||
import logging, os, shutil, subprocess
|
||||
from ..internal import data_dir
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
window = None
|
||||
|
||||
class terminal(Vte.Terminal):
|
||||
__gtype_name__ = 'AlpacaTerminal'
|
||||
@ -42,3 +48,44 @@ class terminal(Vte.Terminal):
|
||||
self.copy_clipboard()
|
||||
return True
|
||||
return False
|
||||
|
||||
def show_terminal(script):
|
||||
window.terminal_scroller.set_child(terminal(script))
|
||||
window.terminal_dialog.present(window)
|
||||
|
||||
def run_terminal(script:str, language_name:str):
|
||||
logger.info('Running: \n{}'.format(language_name))
|
||||
if language_name == 'python3':
|
||||
if not os.path.isdir(os.path.join(data_dir, 'pyenv')):
|
||||
os.mkdir(os.path.join(data_dir, 'pyenv'))
|
||||
with open(os.path.join(data_dir, 'pyenv', 'main.py'), 'w') as f:
|
||||
f.write(script)
|
||||
script = [
|
||||
'echo "🐍 {}\n"'.format(_('Setting up Python environment...')),
|
||||
'python3 -m venv "{}"'.format(os.path.join(data_dir, 'pyenv')),
|
||||
'{} {}'.format(os.path.join(data_dir, 'pyenv', 'bin', 'python3').replace(' ', '\\ '), os.path.join(data_dir, 'pyenv', 'main.py').replace(' ', '\\ '))
|
||||
]
|
||||
if os.path.isfile(os.path.join(data_dir, 'pyenv', 'requirements.txt')):
|
||||
script.insert(1, '{} install -r {} | grep -v "already satisfied"; clear'.format(os.path.join(data_dir, 'pyenv', 'bin', 'pip3'), os.path.join(data_dir, 'pyenv', 'requirements.txt')))
|
||||
else:
|
||||
with open(os.path.join(data_dir, 'pyenv', 'requirements.txt'), 'w') as f:
|
||||
f.write('')
|
||||
script = ';\n'.join(script)
|
||||
|
||||
script += '; echo "\n🦙 {}"'.format(_('Script exited'))
|
||||
if language_name == 'bash':
|
||||
script = re.sub(r'(?m)^\s*sudo', 'pkexec', script)
|
||||
if shutil.which('flatpak-spawn') and language_name == 'bash':
|
||||
sandbox = True
|
||||
try:
|
||||
process = subprocess.run(['flatpak-spawn', '--host', 'bash', '-c', 'echo "test"'], check=True)
|
||||
sandbox = False
|
||||
except Exception as e:
|
||||
pass
|
||||
if sandbox:
|
||||
script = 'echo "🦙 {}\n";'.format(_('The script is contained inside Flatpak')) + script
|
||||
show_terminal(['bash', '-c', script])
|
||||
else:
|
||||
show_terminal(['flatpak-spawn', '--host', 'bash', '-c', script])
|
||||
else:
|
||||
show_terminal(['bash', '-c', script])
|
||||
|
474
src/dialogs.py
474
src/dialogs.py
@ -1,474 +0,0 @@
|
||||
# dialogs.py
|
||||
"""
|
||||
Handles UI dialogs
|
||||
"""
|
||||
import os
|
||||
import logging, requests, threading, shutil, subprocess, re
|
||||
from pytube import YouTube
|
||||
from html2text import html2text
|
||||
from gi.repository import Adw, Gtk
|
||||
from .internal import cache_dir, data_dir
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
# CLEAR CHAT | WORKS
|
||||
|
||||
def clear_chat_response(self, dialog, task):
|
||||
if dialog.choose_finish(task) == "clear":
|
||||
self.chat_list_box.get_current_chat().show_welcome_screen(len(self.model_manager.get_model_list()) > 0)
|
||||
self.save_history(self.chat_list_box.get_current_chat())
|
||||
|
||||
def clear_chat(self):
|
||||
if self.chat_list_box.get_current_chat().busy:
|
||||
self.show_toast(_("Chat cannot be cleared while receiving a message"), 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.set_default_response("clear")
|
||||
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.chat_list_box.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.set_default_response("delete")
|
||||
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):
|
||||
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.chat_list_box.rename_chat(old_chat_name, new_chat_name)
|
||||
|
||||
def rename_chat(self, chat_name):
|
||||
entry = Gtk.Entry()
|
||||
dialog = Adw.AlertDialog(
|
||||
heading=_("Rename Chat?"),
|
||||
body=_("Renaming '{}'").format(chat_name),
|
||||
extra_child=entry,
|
||||
close_response="cancel"
|
||||
)
|
||||
dialog.add_response("cancel", _("Cancel"))
|
||||
dialog.add_response("rename", _("Rename"))
|
||||
dialog.set_response_appearance("rename", Adw.ResponseAppearance.SUGGESTED)
|
||||
dialog.set_default_response("rename")
|
||||
dialog.choose(
|
||||
parent = self,
|
||||
cancellable = None,
|
||||
callback = lambda dialog, task, old_chat_name=chat_name, entry=entry: rename_chat_response(self, dialog, task, old_chat_name, entry)
|
||||
)
|
||||
|
||||
# NEW CHAT | WORKS | UNUSED REASON: The 'Add Chat' button now creates a chat without a name AKA "New Chat"
|
||||
|
||||
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"
|
||||
)
|
||||
dialog.add_response("cancel", _("Cancel"))
|
||||
dialog.add_response("create", _("Create"))
|
||||
dialog.set_response_appearance("create", Adw.ResponseAppearance.SUGGESTED)
|
||||
dialog.set_default_response("create")
|
||||
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, pulling_model):
|
||||
if dialog.choose_finish(task) == "stop":
|
||||
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, pulling_model):
|
||||
dialog = Adw.AlertDialog(
|
||||
heading=_("Stop Download?"),
|
||||
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"))
|
||||
dialog.add_response("stop", _("Stop"))
|
||||
dialog.set_response_appearance("stop", Adw.ResponseAppearance.DESTRUCTIVE)
|
||||
dialog.set_default_response("stop")
|
||||
dialog.choose(
|
||||
parent = self.manage_models_dialog,
|
||||
cancellable = None,
|
||||
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.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(self.convert_model_name(model_name, 0)),
|
||||
close_response="cancel"
|
||||
)
|
||||
dialog.add_response("cancel", _("Cancel"))
|
||||
dialog.add_response("delete", _("Delete"))
|
||||
dialog.set_response_appearance("delete", Adw.ResponseAppearance.DESTRUCTIVE)
|
||||
dialog.set_default_response("delete")
|
||||
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)
|
||||
)
|
||||
|
||||
# REMOVE IMAGE | WORKS
|
||||
|
||||
def remove_attached_file_response(self, dialog, task, name):
|
||||
if dialog.choose_finish(task) == 'remove':
|
||||
self.file_preview_dialog.close()
|
||||
self.remove_attached_file(name)
|
||||
|
||||
def remove_attached_file(self, name):
|
||||
dialog = Adw.AlertDialog(
|
||||
heading=_("Remove Attachment?"),
|
||||
body=_("Are you sure you want to remove attachment?"),
|
||||
close_response="cancel"
|
||||
)
|
||||
dialog.add_response("cancel", _("Cancel"))
|
||||
dialog.add_response("remove", _("Remove"))
|
||||
dialog.set_response_appearance("remove", Adw.ResponseAppearance.DESTRUCTIVE)
|
||||
dialog.set_default_response("remove")
|
||||
dialog.choose(
|
||||
parent = self,
|
||||
cancellable = None,
|
||||
callback = lambda dialog, task, name=name: remove_attached_file_response(self, dialog, task, name)
|
||||
)
|
||||
|
||||
# RECONNECT REMOTE | WORKS
|
||||
|
||||
def reconnect_remote_response(self, dialog, task, url_entry, bearer_entry):
|
||||
response = dialog.choose_finish(task)
|
||||
if not task or response == "remote":
|
||||
self.remote_connection_entry.set_text(url_entry.get_text())
|
||||
self.remote_connection_switch.set_sensitive(url_entry.get_text())
|
||||
self.remote_bearer_token_entry.set_text(bearer_entry.get_text())
|
||||
self.remote_connection_switch.set_active(True)
|
||||
self.model_manager.update_local_list()
|
||||
elif response == "local":
|
||||
self.ollama_instance.remote = False
|
||||
self.ollama_instance.start()
|
||||
self.model_manager.update_local_list()
|
||||
elif response == "close":
|
||||
self.destroy()
|
||||
|
||||
def reconnect_remote(self):
|
||||
entry_url = Gtk.Entry(
|
||||
css_classes = ["error"],
|
||||
text = self.ollama_instance.remote_url,
|
||||
placeholder_text = "URL"
|
||||
)
|
||||
entry_bearer_token = Gtk.Entry(
|
||||
css_classes = ["error"] if self.ollama_instance.bearer_token else None,
|
||||
text = self.ollama_instance.bearer_token,
|
||||
placeholder_text = "Bearer Token (Optional)"
|
||||
)
|
||||
container = Gtk.Box(
|
||||
orientation = 1,
|
||||
spacing = 10
|
||||
)
|
||||
container.append(entry_url)
|
||||
container.append(entry_bearer_token)
|
||||
dialog = Adw.AlertDialog(
|
||||
heading=_("Connection Error"),
|
||||
body=_("The remote instance has disconnected"),
|
||||
extra_child=container
|
||||
)
|
||||
dialog.add_response("close", _("Close Alpaca"))
|
||||
if shutil.which('ollama'):
|
||||
dialog.add_response("local", _("Use local instance"))
|
||||
dialog.add_response("remote", _("Connect"))
|
||||
dialog.set_response_appearance("remote", Adw.ResponseAppearance.SUGGESTED)
|
||||
dialog.set_default_response("remote")
|
||||
dialog.choose(
|
||||
parent = self,
|
||||
cancellable = None,
|
||||
callback = lambda dialog, task, url_entry=entry_url, bearer_entry=entry_bearer_token: reconnect_remote_response(self, dialog, task, url_entry, bearer_entry)
|
||||
)
|
||||
|
||||
# CREATE MODEL | WORKS
|
||||
|
||||
def create_model_from_existing_response(self, dialog, task, dropdown):
|
||||
model = dropdown.get_selected_item().get_string()
|
||||
if dialog.choose_finish(task) == 'accept' and model:
|
||||
self.create_model(model, False)
|
||||
|
||||
def create_model_from_existing(self):
|
||||
string_list = Gtk.StringList()
|
||||
for model in self.model_manager.get_model_list():
|
||||
string_list.append(self.convert_model_name(model, 0))
|
||||
|
||||
dropdown = Gtk.DropDown()
|
||||
dropdown.set_model(string_list)
|
||||
dialog = Adw.AlertDialog(
|
||||
heading=_("Select Model"),
|
||||
body=_("This model will be used as the base for the new model"),
|
||||
extra_child=dropdown
|
||||
)
|
||||
dialog.add_response("cancel", _("Cancel"))
|
||||
dialog.add_response("accept", _("Accept"))
|
||||
dialog.set_response_appearance("accept", Adw.ResponseAppearance.SUGGESTED)
|
||||
dialog.set_default_response("accept")
|
||||
dialog.choose(
|
||||
parent = self,
|
||||
cancellable = None,
|
||||
callback = lambda dialog, task, dropdown=dropdown: create_model_from_existing_response(self, dialog, task, dropdown)
|
||||
)
|
||||
|
||||
def create_model_from_file_response(self, file_dialog, result):
|
||||
try:
|
||||
file = file_dialog.open_finish(result)
|
||||
try:
|
||||
self.create_model(file.get_path(), True)
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
self.show_toast(_("An error occurred while creating the model"), self.main_overlay)
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
|
||||
def create_model_from_file(self):
|
||||
file_dialog = Gtk.FileDialog(default_filter=self.file_filter_gguf)
|
||||
file_dialog.open(self, None, lambda file_dialog, result: create_model_from_file_response(self, file_dialog, result))
|
||||
|
||||
def create_model_from_name_response(self, dialog, task, entry):
|
||||
model = entry.get_text().lower().strip()
|
||||
if dialog.choose_finish(task) == 'accept' and model:
|
||||
threading.Thread(target=self.model_manager.pull_model, kwargs={"model_name": model}).start()
|
||||
|
||||
def create_model_from_name(self):
|
||||
entry = Gtk.Entry()
|
||||
entry.get_delegate().connect("insert-text", lambda *_ : self.check_alphanumeric(*_, ['-', '.', ':', '_', '/']))
|
||||
dialog = Adw.AlertDialog(
|
||||
heading=_("Pull Model"),
|
||||
body=_("Input the name of the model in this format\nname:tag"),
|
||||
extra_child=entry
|
||||
)
|
||||
dialog.add_response("cancel", _("Cancel"))
|
||||
dialog.add_response("accept", _("Accept"))
|
||||
dialog.set_response_appearance("accept", Adw.ResponseAppearance.SUGGESTED)
|
||||
dialog.set_default_response("accept")
|
||||
dialog.choose(
|
||||
parent = self,
|
||||
cancellable = None,
|
||||
callback = lambda dialog, task, entry=entry: create_model_from_name_response(self, dialog, task, entry)
|
||||
)
|
||||
# FILE CHOOSER | WORKS
|
||||
|
||||
def attach_file_response(self, file_dialog, result):
|
||||
file_types = {
|
||||
"plain_text": ["txt", "md", "html", "css", "js", "py", "java", "json", "xml"],
|
||||
"image": ["png", "jpeg", "jpg", "webp", "gif"],
|
||||
"pdf": ["pdf"]
|
||||
}
|
||||
try:
|
||||
file = file_dialog.open_finish(result)
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
return
|
||||
extension = file.get_path().split(".")[-1]
|
||||
file_type = next(key for key, value in file_types.items() if extension in value)
|
||||
if not file_type:
|
||||
return
|
||||
if file_type == 'image' and not self.model_manager.verify_if_image_can_be_used():
|
||||
self.show_toast(_("Image recognition is only available on specific models"), self.main_overlay)
|
||||
return
|
||||
self.attach_file(file.get_path(), file_type)
|
||||
|
||||
def attach_file(self, file_filter):
|
||||
file_dialog = Gtk.FileDialog(default_filter=file_filter)
|
||||
file_dialog.open(self, None, lambda file_dialog, result: attach_file_response(self, file_dialog, result))
|
||||
|
||||
# YouTube caption | WORKS
|
||||
|
||||
def youtube_caption_response(self, dialog, task, video_url, caption_drop_down):
|
||||
if dialog.choose_finish(task) == "accept":
|
||||
buffer = self.message_text_view.get_buffer()
|
||||
text = buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter(), False).replace(video_url, "")
|
||||
buffer.delete(buffer.get_start_iter(), buffer.get_end_iter())
|
||||
buffer.insert(buffer.get_start_iter(), text, len(text))
|
||||
|
||||
yt = YouTube(video_url)
|
||||
text = "{}\n{}\n{}\n\n".format(yt.title, yt.author, yt.watch_url)
|
||||
selected_caption = caption_drop_down.get_selected_item().get_string()
|
||||
for event in yt.captions[selected_caption.split('(')[-1][:-1]].json_captions['events']:
|
||||
text += "{}\n".format(event['segs'][0]['utf8'].replace('\n', '\\n'))
|
||||
if not os.path.exists(os.path.join(cache_dir, 'tmp/youtube')):
|
||||
os.makedirs(os.path.join(cache_dir, 'tmp/youtube'))
|
||||
file_path = os.path.join(os.path.join(cache_dir, 'tmp/youtube'), f'{yt.title} ({selected_caption.split(" (")[0]})')
|
||||
with open(file_path, 'w+', encoding="utf-8") as f:
|
||||
f.write(text)
|
||||
self.attach_file(file_path, 'youtube')
|
||||
|
||||
def youtube_caption(self, video_url):
|
||||
yt = YouTube(video_url)
|
||||
video_title = yt.title
|
||||
captions = yt.captions
|
||||
if len(captions) == 0:
|
||||
self.show_toast(_("This video does not have any transcriptions"), self.main_overlay)
|
||||
return
|
||||
caption_list = Gtk.StringList()
|
||||
for caption in captions:
|
||||
caption_list.append("{} ({})".format(caption.name.title(), caption.code))
|
||||
caption_drop_down = Gtk.DropDown(
|
||||
enable_search=len(captions) > 10,
|
||||
model=caption_list
|
||||
)
|
||||
dialog = Adw.AlertDialog(
|
||||
heading=_("Attach YouTube Video?"),
|
||||
body=_("{}\n\nPlease select a transcript to include").format(video_title),
|
||||
extra_child=caption_drop_down,
|
||||
close_response="cancel"
|
||||
)
|
||||
dialog.add_response("cancel", _("Cancel"))
|
||||
dialog.add_response("accept", _("Accept"))
|
||||
dialog.set_response_appearance("accept", Adw.ResponseAppearance.SUGGESTED)
|
||||
dialog.set_default_response("accept")
|
||||
dialog.choose(
|
||||
parent = self,
|
||||
cancellable = None,
|
||||
callback = lambda dialog, task, video_url = video_url, caption_drop_down = caption_drop_down: youtube_caption_response(self, dialog, task, video_url, caption_drop_down)
|
||||
)
|
||||
|
||||
# Website extraction |
|
||||
|
||||
def attach_website_response(self, dialog, task, url):
|
||||
if dialog.choose_finish(task) == "accept":
|
||||
response = requests.get(url)
|
||||
if response.status_code == 200:
|
||||
html = response.text
|
||||
md = html2text(html)
|
||||
buffer = self.message_text_view.get_buffer()
|
||||
textview_text = buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter(), False).replace(url, "")
|
||||
buffer.delete(buffer.get_start_iter(), buffer.get_end_iter())
|
||||
buffer.insert(buffer.get_start_iter(), textview_text, len(textview_text))
|
||||
if not os.path.exists('/tmp/alpaca/websites/'):
|
||||
os.makedirs('/tmp/alpaca/websites/')
|
||||
md_name = self.generate_numbered_name('website.md', os.listdir('/tmp/alpaca/websites'))
|
||||
file_path = os.path.join('/tmp/alpaca/websites/', md_name)
|
||||
with open(file_path, 'w+', encoding="utf-8") as f:
|
||||
f.write('{}\n\n{}'.format(url, md))
|
||||
self.attach_file(file_path, 'website')
|
||||
else:
|
||||
self.show_toast(_("An error occurred while extracting text from the website"), self.main_overlay)
|
||||
|
||||
|
||||
def attach_website(self, url):
|
||||
dialog = Adw.AlertDialog(
|
||||
heading=_("Attach Website? (Experimental)"),
|
||||
body=_("Are you sure you want to attach\n'{}'?").format(url),
|
||||
close_response="cancel"
|
||||
)
|
||||
dialog.add_response("cancel", _("Cancel"))
|
||||
dialog.add_response("accept", _("Accept"))
|
||||
dialog.set_response_appearance("accept", Adw.ResponseAppearance.SUGGESTED)
|
||||
dialog.set_default_response("accept")
|
||||
dialog.choose(
|
||||
parent = self,
|
||||
cancellable = None,
|
||||
callback = lambda dialog, task, url=url: attach_website_response(self, dialog, task, url)
|
||||
)
|
||||
|
||||
# Run Script
|
||||
|
||||
def run_script_response(self, dialog, task, script, language_name):
|
||||
if dialog.choose_finish(task) == "accept":
|
||||
logger.info('Running: \n{}'.format(script))
|
||||
if language_name == 'python3':
|
||||
if not os.path.isdir(os.path.join(data_dir, 'pyenv')):
|
||||
os.mkdir(os.path.join(data_dir, 'pyenv'))
|
||||
with open(os.path.join(data_dir, 'pyenv', 'main.py'), 'w') as f:
|
||||
f.write(script)
|
||||
script = [
|
||||
'echo "🐍 {}\n"'.format(_('Setting up Python environment...')),
|
||||
'python3 -m venv "{}"'.format(os.path.join(data_dir, 'pyenv')),
|
||||
'{} {}'.format(os.path.join(data_dir, 'pyenv', 'bin', 'python3').replace(' ', '\\ '), os.path.join(data_dir, 'pyenv', 'main.py').replace(' ', '\\ '))
|
||||
]
|
||||
if os.path.isfile(os.path.join(data_dir, 'pyenv', 'requirements.txt')):
|
||||
script.insert(1, '{} install -r {} | grep -v "already satisfied"; clear'.format(os.path.join(data_dir, 'pyenv', 'bin', 'pip3'), os.path.join(data_dir, 'pyenv', 'requirements.txt')))
|
||||
else:
|
||||
with open(os.path.join(data_dir, 'pyenv', 'requirements.txt'), 'w') as f:
|
||||
f.write('')
|
||||
script = ';\n'.join(script)
|
||||
|
||||
script += '; echo "\n🦙 {}"'.format(_('Script exited'))
|
||||
if language_name == 'bash':
|
||||
script = re.sub(r'(?m)^\s*sudo', 'pkexec', script)
|
||||
if shutil.which('flatpak-spawn') and language_name == 'bash':
|
||||
sandbox = True
|
||||
try:
|
||||
process = subprocess.run(['flatpak-spawn', '--host', 'bash', '-c', 'echo "test"'], check=True)
|
||||
sandbox = False
|
||||
except Exception as e:
|
||||
pass
|
||||
if sandbox:
|
||||
script = 'echo "🦙 {}\n";'.format(_('The script is contained inside Flatpak')) + script
|
||||
self.run_terminal(['bash', '-c', script])
|
||||
else:
|
||||
self.run_terminal(['flatpak-spawn', '--host', 'bash', '-c', script])
|
||||
else:
|
||||
self.run_terminal(['bash', '-c', script])
|
||||
|
||||
def run_script(self, script:str, language_name:str):
|
||||
dialog = Adw.AlertDialog(
|
||||
heading=_("Run Script"),
|
||||
body=_("Make sure you understand what this script does before running it, Alpaca is not responsible for any damages to your device or data"),
|
||||
close_response="cancel"
|
||||
)
|
||||
dialog.add_response("cancel", _("Cancel"))
|
||||
dialog.add_response("accept", _("Accept"))
|
||||
dialog.set_response_appearance("accept", Adw.ResponseAppearance.SUGGESTED)
|
||||
dialog.set_default_response("accept")
|
||||
dialog.choose(
|
||||
parent = self,
|
||||
cancellable = None,
|
||||
callback = lambda dialog, task, script=script, language_name=language_name: run_script_response(self, dialog, task, script, language_name)
|
||||
)
|
72
src/generic_actions.py
Normal file
72
src/generic_actions.py
Normal file
@ -0,0 +1,72 @@
|
||||
#generic_actions.py
|
||||
"""
|
||||
Working on organizing the code
|
||||
"""
|
||||
|
||||
import os, requests
|
||||
from pytube import YouTube
|
||||
from html2text import html2text
|
||||
from .internal import cache_dir
|
||||
|
||||
window = None
|
||||
|
||||
def connect_local():
|
||||
window.remote_connection_switch.set_active(False)
|
||||
|
||||
def connect_remote(url:str, bearer:str):
|
||||
window.remote_connection_entry.set_text(url)
|
||||
window.remote_bearer_token_entry.set_text(bearer)
|
||||
window.remote_connection_switch.set_active(True)
|
||||
|
||||
def attach_youtube(video_url:str, caption_name:str):
|
||||
buffer = window.message_text_view.get_buffer()
|
||||
text = buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter(), False).replace(video_url, "")
|
||||
buffer.delete(buffer.get_start_iter(), buffer.get_end_iter())
|
||||
buffer.insert(buffer.get_start_iter(), text, len(text))
|
||||
|
||||
yt = YouTube(video_url)
|
||||
text = "{}\n{}\n{}\n\n".format(yt.title, yt.author, yt.watch_url)
|
||||
|
||||
for event in yt.captions[caption_name.split('(')[-1][:-1]].json_captions['events']:
|
||||
text += "{}\n".format(event['segs'][0]['utf8'].replace('\n', '\\n'))
|
||||
if not os.path.exists(os.path.join(cache_dir, 'tmp/youtube')):
|
||||
os.makedirs(os.path.join(cache_dir, 'tmp/youtube'))
|
||||
file_path = os.path.join(os.path.join(cache_dir, 'tmp/youtube'), f'{yt.title} ({caption_name.split(" (")[0]})')
|
||||
with open(file_path, 'w+', encoding="utf-8") as f:
|
||||
f.write(text)
|
||||
|
||||
window.attach_file(file_path, 'youtube')
|
||||
|
||||
def attach_website(url:str):
|
||||
response = requests.get(url)
|
||||
if response.status_code == 200:
|
||||
html = response.text
|
||||
md = html2text(html)
|
||||
buffer = window.message_text_view.get_buffer()
|
||||
textview_text = buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter(), False).replace(url, "")
|
||||
buffer.delete(buffer.get_start_iter(), buffer.get_end_iter())
|
||||
buffer.insert(buffer.get_start_iter(), textview_text, len(textview_text))
|
||||
if not os.path.exists('/tmp/alpaca/websites/'):
|
||||
os.makedirs('/tmp/alpaca/websites/')
|
||||
md_name = window.generate_numbered_name('website.md', os.listdir('/tmp/alpaca/websites'))
|
||||
file_path = os.path.join('/tmp/alpaca/websites/', md_name)
|
||||
with open(file_path, 'w+', encoding="utf-8") as f:
|
||||
f.write('{}\n\n{}'.format(url, md))
|
||||
window.attach_file(file_path, 'website')
|
||||
else:
|
||||
window.show_toast(_("An error occurred while extracting text from the website"), window.main_overlay)
|
||||
|
||||
def attach_file(file):
|
||||
file_types = {
|
||||
"plain_text": ["txt", "md", "html", "css", "js", "py", "java", "json", "xml"],
|
||||
"image": ["png", "jpeg", "jpg", "webp", "gif"],
|
||||
"pdf": ["pdf"]
|
||||
}
|
||||
extension = file.get_path().split(".")[-1]
|
||||
file_type = next(key for key, value in file_types.items() if extension in value)
|
||||
if not file_type:
|
||||
return
|
||||
if file_type == 'image' and not window.model_manager.verify_if_image_can_be_used():
|
||||
window.show_toast(_("Image recognition is only available on specific models"), window.main_overlay)
|
||||
return
|
||||
window.attach_file(file.get_path(), file_type)
|
@ -40,10 +40,10 @@ alpaca_sources = [
|
||||
'main.py',
|
||||
'window.py',
|
||||
'connection_handler.py',
|
||||
'dialogs.py',
|
||||
'available_models.json',
|
||||
'available_models_descriptions.py',
|
||||
'internal.py'
|
||||
'internal.py',
|
||||
'generic_actions.py'
|
||||
]
|
||||
|
||||
custom_widgets = [
|
||||
@ -51,7 +51,8 @@ custom_widgets = [
|
||||
'custom_widgets/message_widget.py',
|
||||
'custom_widgets/chat_widget.py',
|
||||
'custom_widgets/model_widget.py',
|
||||
'custom_widgets/terminal_widget.py'
|
||||
'custom_widgets/terminal_widget.py',
|
||||
'custom_widgets/dialog_widget.py'
|
||||
]
|
||||
|
||||
install_data(alpaca_sources, install_dir: moduledir)
|
||||
|
@ -24,6 +24,7 @@ from io import BytesIO
|
||||
from PIL import Image
|
||||
from pypdf import PdfReader
|
||||
from datetime import datetime
|
||||
from pytube import YouTube
|
||||
|
||||
import gi
|
||||
gi.require_version('GtkSource', '5')
|
||||
@ -31,8 +32,8 @@ gi.require_version('GdkPixbuf', '2.0')
|
||||
|
||||
from gi.repository import Adw, Gtk, Gdk, GLib, GtkSource, Gio, GdkPixbuf
|
||||
|
||||
from . import dialogs, connection_handler
|
||||
from .custom_widgets import message_widget, chat_widget, model_widget, terminal_widget
|
||||
from . import dialogs, connection_handler, generic_actions
|
||||
from .custom_widgets import message_widget, chat_widget, model_widget, terminal_widget, dialog_widget
|
||||
from .internal import config_dir, data_dir, cache_dir, source_dir
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@ -371,10 +372,6 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
||||
clipboard.read_text_async(None, self.cb_text_received)
|
||||
clipboard.read_texture_async(None, self.cb_image_received)
|
||||
|
||||
def run_terminal(self, script:list):
|
||||
self.terminal_scroller.set_child(terminal_widget.terminal(script))
|
||||
self.terminal_dialog.present(self)
|
||||
|
||||
def convert_model_name(self, name:str, mode:int) -> str: # mode=0 name:tag -> Name (tag) | mode=1 Name (tag) -> name:tag
|
||||
try:
|
||||
if mode == 0:
|
||||
@ -669,7 +666,34 @@ Generate a title following these rules:
|
||||
def connection_error(self):
|
||||
logger.error("Connection error")
|
||||
if self.ollama_instance.remote:
|
||||
dialogs.reconnect_remote(self)
|
||||
#dialogs.reconnect_remote(self)
|
||||
options = {
|
||||
_("Close Alpaca"): {
|
||||
"callback": lambda *_: self.get_application().quit(),
|
||||
"appearance": "destructive"
|
||||
},
|
||||
_("Use Local Instance"): {
|
||||
"callback": lambda *_: window.remote_connection_switch.set_active(False)
|
||||
},
|
||||
_("Connect"): {
|
||||
"callback": lambda url, bearer: generic_actions.connect_remote(url,bearer),
|
||||
"appearance": "suggested"
|
||||
}
|
||||
}
|
||||
entries = [
|
||||
{
|
||||
"text": self.ollama_instance.remote_url,
|
||||
"css": ['error'],
|
||||
"placeholder": _('Server URL')
|
||||
},
|
||||
{
|
||||
"text": self.ollama_instance.bearer_token,
|
||||
"css": ['error'] if self.ollama_instance.bearer_token else None,
|
||||
"placeholder": _('Bearer Token (Optional)')
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
else:
|
||||
self.ollama_instance.reset()
|
||||
self.show_toast(_("There was an error with the local Ollama instance, so it has been reset"), self.main_overlay)
|
||||
@ -714,6 +738,8 @@ Generate a title following these rules:
|
||||
del self.attachments[name]
|
||||
if len(self.attachments) == 0:
|
||||
self.attachment_box.set_visible(False)
|
||||
if self.file_preview_dialog.get_visible():
|
||||
self.file_preview_dialog.close()
|
||||
|
||||
def attach_file(self, file_path, file_type):
|
||||
logger.debug(f"Attaching file: {file_path}")
|
||||
@ -739,7 +765,6 @@ Generate a title following these rules:
|
||||
child=button_content
|
||||
)
|
||||
self.attachments[file_name] = {"path": file_path, "type": file_type, "content": content, "button": button}
|
||||
#button.connect("clicked", lambda button: dialogs.remove_attached_file(self, button))
|
||||
button.connect("clicked", lambda button : self.preview_file(file_path, file_type, file_name))
|
||||
self.attachment_container.append(button)
|
||||
self.attachment_box.set_visible(True)
|
||||
@ -749,11 +774,11 @@ Generate a title following these rules:
|
||||
chat_name = chat_row.label.get_label()
|
||||
action_name = action.get_name()
|
||||
if action_name in ('delete_chat', 'delete_current_chat'):
|
||||
dialogs.delete_chat(self, chat_name)
|
||||
dialog_widget.simple(_('Delete Chat?'), _("Are you sure you want to delete '{}'?").format(chat_name), lambda chat_name=chat_name, *_: self.chat_list_box.delete_chat(chat_name), _('Delete'), 'destructive')
|
||||
elif action_name in ('duplicate_chat', 'duplicate_current_chat'):
|
||||
self.chat_list_box.duplicate_chat(chat_name)
|
||||
elif action_name in ('rename_chat', 'rename_current_chat'):
|
||||
dialogs.rename_chat(self, chat_name)
|
||||
dialog_widget.simple_entry(_('Rename Chat?'), _("Renaming '{}'").format(chat_name), lambda new_chat_name, old_chat_name=chat_name, *_: self.chat_list_box.rename_chat(old_chat_name, new_chat_name), {'placeholder': _('Chat name')}, _('Rename'))
|
||||
elif action_name in ('export_chat', 'export_current_chat'):
|
||||
self.chat_list_box.export_chat(chat_name)
|
||||
|
||||
@ -777,12 +802,27 @@ Generate a title following these rules:
|
||||
)
|
||||
if youtube_regex.match(text):
|
||||
try:
|
||||
dialogs.youtube_caption(self, text)
|
||||
yt = YouTube(text)
|
||||
captions = yt.captions
|
||||
if len(captions) == 0:
|
||||
self.show_toast(_("This video does not have any transcriptions"), self.main_overlay)
|
||||
return
|
||||
video_title = yt.title
|
||||
dialog_widget.simple_dropdown(
|
||||
_('Attach YouTube Video?'),
|
||||
_('{}\n\nPlease select a transcript to include').format(video_title),
|
||||
lambda caption_name, video_url=text: generic_actions.attach_youtube(video_url, caption_name),
|
||||
["{} ({})".format(caption.name.title(), caption.code) for caption in captions]
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
self.show_toast(_("This video is not available"), self.main_overlay)
|
||||
elif url_regex.match(text):
|
||||
dialogs.attach_website(self, text)
|
||||
dialog_widget.simple(
|
||||
_('Attach Website? (Experimental)'),
|
||||
_("Are you sure you want to attach\n'{}'?").format(text),
|
||||
lambda url=text: generic_actions.attach_website(url)
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
|
||||
@ -865,6 +905,9 @@ Generate a title following these rules:
|
||||
message_widget.window = self
|
||||
chat_widget.window = self
|
||||
model_widget.window = self
|
||||
dialog_widget.window = self
|
||||
terminal_widget.window = self
|
||||
generic_actions.window = self
|
||||
connection_handler.window = self
|
||||
|
||||
drop_target = Gtk.DropTarget.new(Gdk.FileList, Gdk.DragAction.COPY)
|
||||
@ -884,11 +927,11 @@ Generate a title following these rules:
|
||||
|
||||
universal_actions = {
|
||||
'new_chat': [lambda *_: self.chat_list_box.new_chat(), ['<primary>n']],
|
||||
'clear': [lambda *_: dialogs.clear_chat(self), ['<primary>e']],
|
||||
'clear': [lambda *i: dialog_widget.simple(_('Clear Chat?'), _('Are you sure you want to clear the chat?'), self.chat_list_box.get_current_chat().clear_chat, _('Clear')), ['<primary>e']],
|
||||
'import_chat': [lambda *_: self.chat_list_box.import_chat(), ['<primary>i']],
|
||||
'create_model_from_existing': [lambda *_: dialogs.create_model_from_existing(self)],
|
||||
'create_model_from_file': [lambda *_: dialogs.create_model_from_file(self)],
|
||||
'create_model_from_name': [lambda *_: dialogs.create_model_from_name(self)],
|
||||
'create_model_from_existing': [lambda *i: dialog_widget.simple_dropdown(_('Select Model'), _('This model will be used as the base for the new model'), lambda model: self.create_model(model, False), self.model_manager.get_model_list())],
|
||||
'create_model_from_file': [lambda *i, file_filter=self.file_filter_gguf: dialog_widget.simple_file(file_filter, lambda file: self.create_model(file.get_path(), True))],
|
||||
'create_model_from_name': [lambda *i: dialog_widget.simple_entry(_('Pull Model'), _('Input the name of the model in this format\nname:tag'), lambda model: threading.Thread(target=self.model_manager.pull_model, kwargs={"model_name": model}).start(), {'placeholder': 'llama3.2:latest'})],
|
||||
'duplicate_chat': [self.chat_actions],
|
||||
'duplicate_current_chat': [self.current_chat_actions],
|
||||
'delete_chat': [self.chat_actions],
|
||||
@ -908,8 +951,8 @@ Generate a title following these rules:
|
||||
self.get_application().lookup_action('manage_models').set_enabled(False)
|
||||
self.get_application().lookup_action('preferences').set_enabled(False)
|
||||
|
||||
self.file_preview_remove_button.connect('clicked', lambda button : dialogs.remove_attached_file(self, button.get_name()))
|
||||
self.attachment_button.connect("clicked", lambda button, file_filter=self.file_filter_attachments: dialogs.attach_file(self, file_filter))
|
||||
self.file_preview_remove_button.connect('clicked', lambda button : dialog_widget.simple(_('Remove Attachment?'), _("Are you sure you want to remove attachment?"), lambda button=button: self.remove_attached_file(button.get_name()), _('Remove'), 'destructive'))
|
||||
self.attachment_button.connect("clicked", lambda button, file_filter=self.file_filter_attachments: dialog_widget.simple_file(file_filter, generic_actions.attach_file))
|
||||
self.create_model_name.get_delegate().connect("insert-text", lambda *_: self.check_alphanumeric(*_, ['-', '.', '_']))
|
||||
self.remote_connection_entry.connect("entry-activated", lambda entry : entry.set_css_classes([]))
|
||||
self.set_focus(self.message_text_view)
|
||||
|
Loading…
x
Reference in New Issue
Block a user