Rewrite of Chat / Message / Model selector systems
This commit is contained in:
parent
8026550f7a
commit
4545f5a1b2
@ -1,7 +1,7 @@
|
||||
{
|
||||
"id" : "com.jeffser.Alpaca",
|
||||
"runtime" : "org.gnome.Platform",
|
||||
"runtime-version" : "46",
|
||||
"runtime-version" : "master",
|
||||
"sdk" : "org.gnome.Sdk",
|
||||
"command" : "alpaca",
|
||||
"finish-args" : [
|
||||
|
390
src/custom_widgets/chat_widget.py
Normal file
390
src/custom_widgets/chat_widget.py
Normal file
@ -0,0 +1,390 @@
|
||||
#chat_widget.py
|
||||
"""
|
||||
Handles the chat widget (testing)
|
||||
"""
|
||||
|
||||
import gi
|
||||
gi.require_version('Gtk', '4.0')
|
||||
gi.require_version('GtkSource', '5')
|
||||
from gi.repository import Gtk, GObject, Gio, Adw, GtkSource, GLib, Gdk
|
||||
import logging, os, datetime, re, shutil, random, tempfile, tarfile, json
|
||||
from ..internal import config_dir, data_dir, cache_dir, source_dir
|
||||
from .message_widget import message
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
window = None
|
||||
|
||||
possible_prompts = [
|
||||
"What can you do?",
|
||||
"Give me a pancake recipe",
|
||||
"Why is the sky blue?",
|
||||
"Can you tell me a joke?",
|
||||
"Give me a healthy breakfast recipe",
|
||||
"How to make a pizza",
|
||||
"Can you write a poem?",
|
||||
"Can you write a story?",
|
||||
"What is GNU-Linux?",
|
||||
"Which is the best Linux distro?",
|
||||
"Why is Pluto not a planet?",
|
||||
"What is a black-hole?",
|
||||
"Tell me how to stay fit",
|
||||
"Write a conversation between sun and Earth",
|
||||
"Why is the grass green?",
|
||||
"Write an Haïku about AI",
|
||||
"What is the meaning of life?",
|
||||
"Explain quantum physics in simple terms",
|
||||
"Explain the theory of relativity",
|
||||
"Explain how photosynthesis works",
|
||||
"Recommend a film about nature",
|
||||
"What is nostalgia?"
|
||||
]
|
||||
|
||||
class chat(Gtk.ScrolledWindow):
|
||||
__gtype_name__ = 'AlpacaChat'
|
||||
|
||||
def __init__(self, name:str):
|
||||
self.container = Gtk.Box(
|
||||
orientation=1,
|
||||
hexpand=True,
|
||||
vexpand=True,
|
||||
spacing=12,
|
||||
margin_top=12,
|
||||
margin_bottom=12,
|
||||
margin_start=12,
|
||||
margin_end=12
|
||||
)
|
||||
self.clamp = Adw.Clamp(
|
||||
maximum_size=1000,
|
||||
tightening_threshold=800,
|
||||
child=self.container
|
||||
)
|
||||
super().__init__(
|
||||
child=self.clamp,
|
||||
propagate_natural_height=True,
|
||||
kinetic_scrolling=True,
|
||||
vexpand=True,
|
||||
hexpand=True,
|
||||
css_classes=["undershoot-bottom"],
|
||||
name=name
|
||||
)
|
||||
self.messages = {}
|
||||
self.welcome_screen = None
|
||||
self.busy = False
|
||||
|
||||
def stop_message(self):
|
||||
self.busy = False
|
||||
window.switch_send_stop_button(True)
|
||||
|
||||
def clear_chat(self):
|
||||
if self.busy:
|
||||
self.stop_message()
|
||||
self.message = {}
|
||||
self.stop_message()
|
||||
for widget in list(self.container):
|
||||
self.container.remove(widget)
|
||||
|
||||
def add_message(self, message_id:str, model:str=None):
|
||||
msg = message(message_id, model)
|
||||
self.messages[message_id] = msg
|
||||
self.container.append(msg)
|
||||
|
||||
def send_sample_prompt(self, prompt):
|
||||
buffer = window.message_text_view.get_buffer()
|
||||
buffer.delete(buffer.get_start_iter(), buffer.get_end_iter())
|
||||
buffer.insert(buffer.get_start_iter(), prompt, len(prompt.encode('utf-8')))
|
||||
window.send_message()
|
||||
|
||||
def show_welcome_screen(self, show_prompts:bool):
|
||||
if self.welcome_screen:
|
||||
self.container.remove(self.welcome_screen)
|
||||
self.welcome_screen = None
|
||||
self.clear_chat()
|
||||
button_container = Gtk.Box(
|
||||
orientation=1,
|
||||
spacing=10,
|
||||
halign=3
|
||||
)
|
||||
if show_prompts:
|
||||
for prompt in random.sample(possible_prompts, 3):
|
||||
prompt_button = Gtk.Button(
|
||||
label=prompt,
|
||||
tooltip_text=_("Send prompt: '{}'").format(prompt)
|
||||
)
|
||||
prompt_button.connect('clicked', lambda *_, prompt=prompt : self.send_sample_prompt(prompt))
|
||||
button_container.append(prompt_button)
|
||||
else:
|
||||
button = Gtk.Button(
|
||||
label=_("Open Model Manager"),
|
||||
tooltip_text=_("Open Model Manager"),
|
||||
css_classes=["suggested-action", "pill"]
|
||||
)
|
||||
button.connect('clicked', lambda *_ : window.manage_models_dialog.present(window))
|
||||
button_container.append(button)
|
||||
|
||||
self.welcome_screen = Adw.StatusPage(
|
||||
icon_name="com.jeffser.Alpaca",
|
||||
title="Alpaca",
|
||||
description=_("Try one of these prompts") if show_prompts else _("It looks like you don't have any models downloaded yet. Download models to get started!"),
|
||||
child=button_container,
|
||||
vexpand=True
|
||||
)
|
||||
|
||||
self.container.append(self.welcome_screen)
|
||||
|
||||
def load_chat_messages(self, messages:dict):
|
||||
if len(messages.keys()) > 0:
|
||||
if self.welcome_screen:
|
||||
self.container.remove(self.welcome_screen)
|
||||
self.welcome_screen = None
|
||||
for message_id, message_data in messages.items():
|
||||
if message_data['content']:
|
||||
self.add_message(message_id, message_data['model'] if message_data['role'] == 'assistant' else None)
|
||||
message_element = self.messages[message_id]
|
||||
if 'images' in message_data:
|
||||
images=[]
|
||||
for image in message_data['images']:
|
||||
images.append(os.path.join(data_dir, "chats", self.get_name(), message_id, image))
|
||||
message_element.add_images(images)
|
||||
if 'files' in message_data:
|
||||
files={}
|
||||
for file_name, file_type in message_data['files'].items():
|
||||
files[os.path.join(data_dir, "chats", self.get_name(), message_id, file_name)] = file_type
|
||||
message_element.add_attachments(files)
|
||||
message_element.set_text(message_data['content'])
|
||||
message_element.add_footer(datetime.datetime.strptime(message_data['date'] + (":00" if message_data['date'].count(":") == 1 else ""), '%Y/%m/%d %H:%M:%S'))
|
||||
else:
|
||||
self.show_welcome_screen(len(window.model_selector.get_model_list()) > 0)
|
||||
|
||||
def messages_to_dict(self) -> dict:
|
||||
messages_dict = {}
|
||||
for message_id, message_element in self.messages.items():
|
||||
if message_element.text and message_element.dt:
|
||||
messages_dict[message_id] = {
|
||||
'role': 'assistant' if message_element.bot else 'user',
|
||||
'model': message_element.model,
|
||||
'date': message_element.dt.strftime("%Y/%m/%d %H:%M:%S"),
|
||||
'content': message_element.text
|
||||
}
|
||||
|
||||
if message_element.image_c:
|
||||
images = []
|
||||
for file in message_element.image_c.files:
|
||||
images.append(file.image_name)
|
||||
messages_dict[message_id]['images'] = images
|
||||
|
||||
if message_element.attachment_c:
|
||||
files = {}
|
||||
for file in message_element.attachment_c.files:
|
||||
files[file.file_name] = file.file_type
|
||||
messages_dict[message_id]['files'] = files
|
||||
return messages_dict
|
||||
|
||||
|
||||
|
||||
class chat_tab(Gtk.ListBoxRow):
|
||||
__gtype_name__ = 'AlpacaChatTab'
|
||||
|
||||
def __init__(self, chat_window:chat):
|
||||
self.chat_window=chat_window
|
||||
self.label = Gtk.Label(
|
||||
label=self.chat_window.get_name(),
|
||||
tooltip_text=self.chat_window.get_name(),
|
||||
hexpand=True,
|
||||
halign=0,
|
||||
wrap=True,
|
||||
ellipsize=3,
|
||||
wrap_mode=2,
|
||||
xalign=0
|
||||
)
|
||||
super().__init__(
|
||||
css_classes = ["chat_row"],
|
||||
height_request = 45,
|
||||
child = self.label
|
||||
)
|
||||
|
||||
self.gesture = Gtk.GestureClick(button=3)
|
||||
self.gesture.connect("released", window.chat_click_handler)
|
||||
self.add_controller(self.gesture)
|
||||
|
||||
class chat_list(Gtk.ListBox):
|
||||
__gtype_name__ = 'AlpacaChatList'
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
selection_mode=1,
|
||||
css_classes=["navigation-sidebar"]
|
||||
)
|
||||
self.connect("row-selected", lambda listbox, row: self.chat_changed(row))
|
||||
self.tab_list = []
|
||||
|
||||
def get_tab_by_name(self, chat_name:str) -> chat_tab:
|
||||
for tab in self.tab_list:
|
||||
if tab.chat_window.get_name() == chat_name:
|
||||
return tab
|
||||
|
||||
def get_chat_by_name(self, chat_name:str) -> chat:
|
||||
tab = self.get_tab_by_name(chat_name)
|
||||
if tab:
|
||||
return tab.chat_window
|
||||
|
||||
def get_current_chat(self) -> chat:
|
||||
row = self.get_selected_row()
|
||||
if row:
|
||||
return self.get_selected_row().chat_window
|
||||
|
||||
def send_tab_to_top(self, tab:chat_tab):
|
||||
self.unselect_all()
|
||||
self.tab_list.remove(tab)
|
||||
self.tab_list.insert(0, tab)
|
||||
self.remove(tab)
|
||||
self.prepend(tab)
|
||||
self.select_row(tab)
|
||||
|
||||
def append_chat(self, chat_name:str) -> chat:
|
||||
chat_name = window.generate_numbered_name(chat_name, [tab.chat_window.get_name() for tab in self.tab_list])
|
||||
chat_window = chat(chat_name)
|
||||
tab = chat_tab(chat_window)
|
||||
self.append(tab)
|
||||
self.tab_list.append(tab)
|
||||
window.chat_stack.add_child(chat_window)
|
||||
return chat_window
|
||||
|
||||
def prepend_chat(self, chat_name:str) -> chat:
|
||||
chat_name = window.generate_numbered_name(chat_name, [tab.chat_window.get_name() for tab in self.tab_list])
|
||||
chat_window = chat(chat_name)
|
||||
tab = chat_tab(chat_window)
|
||||
self.prepend(tab)
|
||||
self.tab_list.insert(0, tab)
|
||||
chat_window.show_welcome_screen(len(window.model_selector.get_model_list()) > 0)
|
||||
window.chat_stack.add_child(chat_window)
|
||||
window.chat_list_box.select_row(tab)
|
||||
return chat_window
|
||||
|
||||
def new_chat(self):
|
||||
window.save_history(self.prepend_chat(_("New Chat")))
|
||||
|
||||
def delete_chat(self, chat_name:str):
|
||||
chat_tab = None
|
||||
for c in self.tab_list:
|
||||
if c.chat_window.get_name() == chat_name:
|
||||
chat_tab = c
|
||||
if chat_tab:
|
||||
chat_tab.chat_window.stop_message()
|
||||
window.chat_stack.remove(chat_tab.chat_window)
|
||||
self.tab_list.remove(chat_tab)
|
||||
self.remove(chat_tab)
|
||||
if os.path.exists(os.path.join(data_dir, "chats", chat_name)):
|
||||
shutil.rmtree(os.path.join(data_dir, "chats", chat_name))
|
||||
if len(self.tab_list) == 0:
|
||||
self.new_chat()
|
||||
if not self.get_current_chat() or self.get_current_chat() == chat_tab.chat_window:
|
||||
self.select_row(self.get_row_at_index(0))
|
||||
window.save_history()
|
||||
|
||||
def rename_chat(self, old_chat_name:str, new_chat_name:str):
|
||||
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])
|
||||
tab.get_child().set_label(new_chat_name)
|
||||
tab.get_child().set_tooltip_text(new_chat_name)
|
||||
tab.chat_window.set_name(new_chat_name)
|
||||
|
||||
def duplicate_chat(self, chat_name:str):
|
||||
new_chat_name = window.generate_numbered_name(_("Copy of {}").format(chat_name), [tab.chat_window.get_name() for tab in self.tab_list])
|
||||
try:
|
||||
shutil.copytree(os.path.join(data_dir, "chats", chat_name), os.path.join(data_dir, "chats", new_chat_name))
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
self.prepend_chat(new_chat_name)
|
||||
self.get_tab_by_name(new_chat_name).chat_window.load_chat_messages(self.get_tab_by_name(chat_name).chat_window.messages_to_dict())
|
||||
|
||||
def on_replace_contents(self, file, result):
|
||||
file.replace_contents_finish(result)
|
||||
window.show_toast(_("Chat exported successfully"), window.main_overlay)
|
||||
|
||||
def on_export_chat(self, file_dialog, result, chat_name):
|
||||
file = file_dialog.save_finish(result)
|
||||
if not file:
|
||||
return
|
||||
json_data = json.dumps({chat_name: self.get_chat_by_name(chat_name).messages_to_dict()}, indent=4).encode("UTF-8")
|
||||
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
json_path = os.path.join(temp_dir, "data.json")
|
||||
with open(json_path, "wb") as json_file:
|
||||
json_file.write(json_data)
|
||||
|
||||
tar_path = os.path.join(temp_dir, chat_name)
|
||||
with tarfile.open(tar_path, "w") as tar:
|
||||
tar.add(json_path, arcname="data.json")
|
||||
directory = os.path.join(data_dir, "chats", chat_name)
|
||||
if os.path.exists(directory) and os.path.isdir(directory):
|
||||
tar.add(directory, arcname=os.path.basename(directory))
|
||||
|
||||
with open(tar_path, "rb") as tar:
|
||||
tar_content = tar.read()
|
||||
|
||||
file.replace_contents_async(
|
||||
tar_content,
|
||||
etag=None,
|
||||
make_backup=False,
|
||||
flags=Gio.FileCreateFlags.NONE,
|
||||
cancellable=None,
|
||||
callback=self.on_replace_contents
|
||||
)
|
||||
|
||||
def export_chat(self, chat_name:str):
|
||||
logger.info("Exporting chat")
|
||||
file_dialog = Gtk.FileDialog(initial_name=f"{chat_name}.tar")
|
||||
file_dialog.save(parent=window, cancellable=None, callback=lambda file_dialog, result, chat_name=chat_name: self.on_export_chat(file_dialog, result, chat_name))
|
||||
|
||||
def on_chat_imported(self, file_dialog, result):
|
||||
file = file_dialog.open_finish(result)
|
||||
if not file:
|
||||
return
|
||||
stream = file.read(None)
|
||||
data_stream = Gio.DataInputStream.new(stream)
|
||||
tar_content = data_stream.read_bytes(1024 * 1024, None)
|
||||
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
tar_filename = os.path.join(temp_dir, "imported_chat.tar")
|
||||
|
||||
with open(tar_filename, "wb") as tar_file:
|
||||
tar_file.write(tar_content.get_data())
|
||||
|
||||
with tarfile.open(tar_filename, "r") as tar:
|
||||
tar.extractall(path=temp_dir)
|
||||
chat_name = None
|
||||
chat_content = None
|
||||
for member in tar.getmembers():
|
||||
if member.name == "data.json":
|
||||
json_filepath = os.path.join(temp_dir, member.name)
|
||||
with open(json_filepath, "r", encoding="utf-8") as json_file:
|
||||
data = json.load(json_file)
|
||||
for chat_name, chat_content in data.items():
|
||||
new_chat_name = window.generate_numbered_name(chat_name, [tab.chat_window.get_name() for tab in self.tab_list])
|
||||
src_path = os.path.join(temp_dir, chat_name)
|
||||
dest_path = os.path.join(data_dir, "chats", new_chat_name)
|
||||
if os.path.exists(src_path) and os.path.isdir(src_path) and not os.path.exists(dest_path):
|
||||
shutil.copytree(src_path, dest_path)
|
||||
|
||||
self.prepend_chat(new_chat_name)
|
||||
self.get_chat_by_name(new_chat_name).load_chat_messages(chat_content['messages'])
|
||||
window.show_toast(_("Chat imported successfully"), window.main_overlay)
|
||||
|
||||
def import_chat(self):
|
||||
logger.info("Importing chat")
|
||||
file_dialog = Gtk.FileDialog(default_filter=window.file_filter_tar)
|
||||
file_dialog.open(window, None, self.on_chat_imported)
|
||||
|
||||
def chat_changed(self, row):
|
||||
if row:
|
||||
current_tab_i = next((i for i, t in enumerate(self.tab_list) if t.chat_window == window.chat_stack.get_visible_child()), -1)
|
||||
if self.tab_list.index(row) != current_tab_i:
|
||||
window.chat_stack.set_transition_type(4 if self.tab_list.index(row) > current_tab_i else 5)
|
||||
window.chat_stack.set_visible_child(row.chat_window)
|
||||
window.switch_send_stop_button(not row.chat_window.busy)
|
||||
if len(row.chat_window.messages) > 0:
|
||||
last_model_used = row.chat_window.messages[list(row.chat_window.messages)[-1]].model
|
||||
window.model_selector.change_model(last_model_used)
|
||||
|
531
src/custom_widgets/message_widget.py
Normal file
531
src/custom_widgets/message_widget.py
Normal file
@ -0,0 +1,531 @@
|
||||
#message_widget.py
|
||||
"""
|
||||
Handles the message widget (testing)
|
||||
"""
|
||||
|
||||
import gi
|
||||
gi.require_version('Gtk', '4.0')
|
||||
gi.require_version('GtkSource', '5')
|
||||
from gi.repository import Gtk, GObject, Gio, Adw, GtkSource, GLib, Gdk
|
||||
import logging, os, datetime, re, shutil, threading
|
||||
from ..internal import config_dir, data_dir, cache_dir, source_dir
|
||||
from .table_widget import TableWidget
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
window = None
|
||||
|
||||
class edit_text_block(Gtk.TextView):
|
||||
__gtype_name__ = 'AlpacaEditTextBlock'
|
||||
|
||||
def __init__(self, text:str):
|
||||
super().__init__(
|
||||
hexpand=True,
|
||||
halign=0,
|
||||
margin_top=5,
|
||||
margin_bottom=5,
|
||||
margin_start=5,
|
||||
margin_end=5,
|
||||
css_classes=["view", "editing_message_textview"]
|
||||
)
|
||||
self.get_buffer().insert(self.get_buffer().get_start_iter(), text, len(text.encode('utf-8')))
|
||||
enter_key_controller = Gtk.EventControllerKey.new()
|
||||
enter_key_controller.connect("key-pressed", lambda controller, keyval, keycode, state: self.edit_message() if keyval==Gdk.KEY_Return and not (state & Gdk.ModifierType.SHIFT_MASK) else None)
|
||||
self.add_controller(enter_key_controller)
|
||||
|
||||
def edit_message(self):
|
||||
self.get_parent().get_parent().action_buttons.set_visible(True)
|
||||
self.get_parent().get_parent().set_text(self.get_buffer().get_text(self.get_buffer().get_start_iter(), self.get_buffer().get_end_iter(), False))
|
||||
self.get_parent().get_parent().add_footer(self.get_parent().get_parent().dt)
|
||||
window.save_history(self.get_parent().get_parent().get_parent().get_parent().get_parent().get_parent())
|
||||
self.get_parent().remove(self)
|
||||
window.show_toast(_("Message edited successfully"), window.main_overlay)
|
||||
return True
|
||||
|
||||
class text_block(Gtk.Label):
|
||||
__gtype_name__ = 'AlpacaTextBlock'
|
||||
|
||||
def __init__(self, bot:bool):
|
||||
super().__init__(
|
||||
hexpand=True,
|
||||
halign=0,
|
||||
wrap=True,
|
||||
wrap_mode=0,
|
||||
xalign=0,
|
||||
margin_top=5,
|
||||
margin_start=5,
|
||||
margin_end=5,
|
||||
focusable=True,
|
||||
selectable=True
|
||||
)
|
||||
self.update_property([4, 7], [_("Response message") if bot else _("User message"), False])
|
||||
|
||||
def insert_at_end(self, text:str, markdown:bool):
|
||||
if markdown:
|
||||
self.set_markup(self.get_text() + text)
|
||||
else:
|
||||
self.set_text(self.get_text() + text)
|
||||
self.update_property([1], [self.get_text()])
|
||||
|
||||
def clear_text(self):
|
||||
self.buffer.delete(self.textbuffer.get_start_iter(), self.textbuffer.get_end_iter())
|
||||
self.update_property([1], [""])
|
||||
|
||||
class code_block(Gtk.Box):
|
||||
__gtype_name__ = 'AlpacaCodeBlock'
|
||||
|
||||
def __init__(self, text:str, language_name:str=None):
|
||||
super().__init__(
|
||||
css_classes=["card", "code_block"],
|
||||
orientation=1,
|
||||
overflow=1,
|
||||
margin_start=5,
|
||||
margin_end=5
|
||||
)
|
||||
|
||||
self.language = None
|
||||
if language_name:
|
||||
self.language = GtkSource.LanguageManager.get_default().get_language(language_name)
|
||||
if self.language:
|
||||
self.buffer = GtkSource.Buffer.new_with_language(self.language)
|
||||
else:
|
||||
self.buffer = GtkSource.Buffer()
|
||||
self.buffer.set_style_scheme(GtkSource.StyleSchemeManager.get_default().get_scheme('Adwaita-dark'))
|
||||
self.source_view = GtkSource.View(
|
||||
auto_indent=True, indent_width=4, buffer=self.buffer, show_line_numbers=True, editable=None,
|
||||
top_margin=6, bottom_margin=6, left_margin=12, right_margin=12, css_classes=["code_block"]
|
||||
)
|
||||
self.source_view.update_property([4], [_("{}Code Block").format('{} '.format(self.language.get_name()) if self.language else "")])
|
||||
|
||||
title_box = Gtk.Box(margin_start=12, margin_top=3, margin_bottom=3, margin_end=3)
|
||||
title_box.append(Gtk.Label(label=self.language.get_name() if self.language else _("Code Block"), hexpand=True, xalign=0))
|
||||
copy_button = Gtk.Button(icon_name="edit-copy-symbolic", css_classes=["flat", "circular"], tooltip_text=_("Copy Message"))
|
||||
copy_button.connect("clicked", self.on_copy)
|
||||
title_box.append(copy_button)
|
||||
self.append(title_box)
|
||||
self.append(Gtk.Separator())
|
||||
self.append(self.source_view)
|
||||
self.buffer.set_text(text)
|
||||
|
||||
def on_copy(self, *_):
|
||||
logger.debug("Copying code")
|
||||
clipboard = Gdk.Display().get_default().get_clipboard()
|
||||
start = self.buffer.get_start_iter()
|
||||
end = self.buffer.get_end_iter()
|
||||
text = self.buffer.get_text(start, end, False)
|
||||
clipboard.set(text)
|
||||
window.show_toast(_("Code copied to the clipboard"), window.main_overlay)
|
||||
|
||||
class attachment(Gtk.Button):
|
||||
__gtype_name__ = 'AlpacaAttachment'
|
||||
|
||||
def __init__(self, file_name:str, file_path:str, file_type:str):
|
||||
self.file_name = file_name
|
||||
self.file_path = file_path
|
||||
self.file_type = file_type
|
||||
|
||||
directory, file_name = os.path.split(self.file_path)
|
||||
head, last_dir = os.path.split(directory)
|
||||
head, second_last_dir = os.path.split(head)
|
||||
self.file_path = os.path.join(head, '{selected_chat}', last_dir, file_name)
|
||||
|
||||
button_content = Adw.ButtonContent(
|
||||
label=self.file_name,
|
||||
icon_name={
|
||||
"plain_text": "document-text-symbolic",
|
||||
"pdf": "document-text-symbolic",
|
||||
"youtube": "play-symbolic",
|
||||
"website": "globe-symbolic"
|
||||
}[self.file_type]
|
||||
)
|
||||
|
||||
super().__init__(
|
||||
vexpand=False,
|
||||
valign=3,
|
||||
name=self.file_name,
|
||||
css_classes=["flat"],
|
||||
tooltip_text=self.file_name,
|
||||
child=button_content
|
||||
)
|
||||
|
||||
self.connect("clicked", lambda button, file_path=self.file_path, file_type=self.file_type: window.preview_file(file_path, file_type, None))
|
||||
|
||||
class attachment_container(Gtk.ScrolledWindow):
|
||||
__gtype_name__ = 'AlpacaAttachmentContainer'
|
||||
|
||||
def __init__(self):
|
||||
self.files = []
|
||||
|
||||
self.container = Gtk.Box(
|
||||
orientation=0,
|
||||
spacing=12
|
||||
)
|
||||
|
||||
super().__init__(
|
||||
margin_top=10,
|
||||
margin_start=10,
|
||||
margin_end=10,
|
||||
hexpand=True,
|
||||
child=self.container
|
||||
)
|
||||
|
||||
def add_file(self, file:attachment):
|
||||
self.container.append(file)
|
||||
self.files.append(file)
|
||||
|
||||
class image(Gtk.Button):
|
||||
__gtype_name__ = 'AlpacaImage'
|
||||
|
||||
def __init__(self, image_path:str):
|
||||
self.image_path = image_path
|
||||
self.image_name = os.path.basename(self.image_path)
|
||||
|
||||
directory, file_name = os.path.split(self.image_path)
|
||||
head, last_dir = os.path.split(directory)
|
||||
head, second_last_dir = os.path.split(head)
|
||||
|
||||
try:
|
||||
if not os.path.isfile(self.image_path):
|
||||
raise FileNotFoundError("'{}' was not found or is a directory".format(self.image_path))
|
||||
image = Gtk.Image.new_from_file(self.image_path)
|
||||
image.set_size_request(240, 240)
|
||||
super().__init__(
|
||||
child=image,
|
||||
css_classes=["flat", "chat_image_button"],
|
||||
name=self.image_name,
|
||||
tooltip_text=_("Image")
|
||||
)
|
||||
image.update_property([4], [_("Image")])
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
image_texture = Gtk.Image.new_from_icon_name("image-missing-symbolic")
|
||||
image_texture.set_icon_size(2)
|
||||
image_texture.set_vexpand(True)
|
||||
image_texture.set_pixel_size(120)
|
||||
image_label = Gtk.Label(
|
||||
label=_("Missing Image"),
|
||||
)
|
||||
image_box = Gtk.Box(
|
||||
spacing=10,
|
||||
orientation=1,
|
||||
margin_top=10,
|
||||
margin_bottom=10,
|
||||
margin_start=10,
|
||||
margin_end=10
|
||||
)
|
||||
image_box.append(image_texture)
|
||||
image_box.append(image_label)
|
||||
image_box.set_size_request(220, 220)
|
||||
super().__init__(
|
||||
child=image_box,
|
||||
css_classes=["flat", "chat_image_button"],
|
||||
tooltip_text=_("Missing Image")
|
||||
)
|
||||
image_texture.update_property([4], [_("Missing image")])
|
||||
self.connect("clicked", lambda button, file_path=os.path.join(head, '{selected_chat}', last_dir, file_name): window.preview_file(file_path, 'image', None))
|
||||
|
||||
class image_container(Gtk.ScrolledWindow):
|
||||
__gtype_name__ = 'AlpacaImageContainer'
|
||||
|
||||
def __init__(self):
|
||||
self.files = []
|
||||
|
||||
self.container = Gtk.Box(
|
||||
orientation=0,
|
||||
spacing=12
|
||||
)
|
||||
|
||||
super().__init__(
|
||||
margin_top=10,
|
||||
margin_start=10,
|
||||
margin_end=10,
|
||||
hexpand=True,
|
||||
height_request = 240,
|
||||
child=self.container
|
||||
)
|
||||
|
||||
def add_image(self, img:image):
|
||||
self.container.append(img)
|
||||
self.files.append(img)
|
||||
|
||||
class footer(Gtk.Label):
|
||||
__gtype_name__ = 'AlpacaMessageFooter'
|
||||
|
||||
def __init__(self, dt:datetime.datetime, model:str=None):
|
||||
super().__init__(
|
||||
hexpand=False,
|
||||
halign=0,
|
||||
wrap=True,
|
||||
ellipsize=3,
|
||||
wrap_mode=2,
|
||||
xalign=0,
|
||||
margin_bottom=5,
|
||||
margin_start=5,
|
||||
focusable=True
|
||||
)
|
||||
self.set_markup("{}<small>{}</small>".format((window.convert_model_name(model, 0) + "\n") if model else "", GLib.markup_escape_text(self.format_datetime(dt))))
|
||||
|
||||
def format_datetime(self, dt:datetime) -> str:
|
||||
date = GLib.DateTime.new(GLib.DateTime.new_now_local().get_timezone(), dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second)
|
||||
current_date = GLib.DateTime.new_now_local()
|
||||
if date.format("%Y/%m/%d") == current_date.format("%Y/%m/%d"):
|
||||
return date.format("%H:%M %p")
|
||||
if date.format("%Y") == current_date.format("%Y"):
|
||||
return date.format("%b %d, %H:%M %p")
|
||||
return date.format("%b %d %Y, %H:%M %p")
|
||||
|
||||
class action_buttons(Gtk.Box):
|
||||
__gtype_name__ = 'AlpacaActionButtonContainer'
|
||||
|
||||
def __init__(self, bot:bool):
|
||||
super().__init__(
|
||||
orientation=0,
|
||||
spacing=6,
|
||||
margin_end=6,
|
||||
margin_bottom=6,
|
||||
valign="end",
|
||||
halign="end"
|
||||
)
|
||||
|
||||
self.delete_button = Gtk.Button(
|
||||
icon_name = "user-trash-symbolic",
|
||||
css_classes = ["flat", "circular"],
|
||||
tooltip_text = _("Remove Message")
|
||||
)
|
||||
self.delete_button.connect('clicked', lambda *_: self.delete_message())
|
||||
self.append(self.delete_button)
|
||||
|
||||
self.copy_button = Gtk.Button(
|
||||
icon_name = "edit-copy-symbolic",
|
||||
css_classes = ["flat", "circular"],
|
||||
tooltip_text = _("Copy Message")
|
||||
)
|
||||
self.copy_button.connect('clicked', lambda *_: self.copy_message())
|
||||
self.append(self.copy_button)
|
||||
|
||||
self.regenerate_button = Gtk.Button(
|
||||
icon_name = "update-symbolic",
|
||||
css_classes = ["flat", "circular"],
|
||||
tooltip_text = _("Regenerate Message")
|
||||
)
|
||||
self.regenerate_button.connect('clicked', lambda *_: self.regenerate_message())
|
||||
|
||||
self.edit_button = Gtk.Button(
|
||||
icon_name = "edit-symbolic",
|
||||
css_classes = ["flat", "circular"],
|
||||
tooltip_text = _("Edit Message")
|
||||
)
|
||||
self.edit_button.connect('clicked', lambda *_: self.edit_message())
|
||||
|
||||
self.append(self.regenerate_button if bot else self.edit_button)
|
||||
|
||||
def delete_message(self):
|
||||
logger.debug("Deleting message")
|
||||
chat = self.get_parent().get_parent().get_parent().get_parent().get_parent()
|
||||
message_id = self.get_parent().message_id
|
||||
self.get_parent().get_parent().remove(self.get_parent())
|
||||
if os.path.exists(os.path.join(data_dir, "chats", window.chat_list_box.get_current_chat().get_name(), self.get_parent().message_id)):
|
||||
shutil.rmtree(os.path.join(data_dir, "chats", window.chat_list_box.get_current_chat().get_name(), self.get_parent().message_id))
|
||||
del chat.messages[message_id]
|
||||
window.save_history(chat)
|
||||
if len(chat.messages) == 0:
|
||||
chat.show_welcome_screen(len(window.model_selector.get_model_list()) > 0)
|
||||
|
||||
def copy_message(self):
|
||||
logger.debug("Copying message")
|
||||
clipboard = Gdk.Display().get_default().get_clipboard()
|
||||
clipboard.set(self.get_parent().text)
|
||||
window.show_toast(_("Message copied to the clipboard"), window.main_overlay)
|
||||
|
||||
def regenerate_message(self):
|
||||
chat = self.get_parent().get_parent().get_parent().get_parent().get_parent()
|
||||
message_element = self.get_parent()
|
||||
if not chat.busy:
|
||||
message_element.set_text()
|
||||
message_element.container.remove(message_element.footer)
|
||||
message_element.remove_overlay(self)
|
||||
message_element.action_buttons = None
|
||||
history = window.convert_history_to_ollama(chat)[:list(chat.messages).index(message_element.message_id)]
|
||||
data = {
|
||||
"model": window.get_current_model(1),
|
||||
"messages": history,
|
||||
"options": {"temperature": window.model_tweaks["temperature"], "seed": window.model_tweaks["seed"]},
|
||||
"keep_alive": f"{window.model_tweaks['keep_alive']}m"
|
||||
}
|
||||
thread = threading.Thread(target=window.run_message, args=(data, message_element))
|
||||
thread.start()
|
||||
else:
|
||||
window.show_toast(_("Message cannot be regenerated while receiving a response"), window.main_overlay)
|
||||
|
||||
def edit_message(self):
|
||||
logger.debug("Editing message")
|
||||
self.get_parent().action_buttons.set_visible(False)
|
||||
for child in self.get_parent().content_children:
|
||||
self.get_parent().container.remove(child)
|
||||
self.get_parent().content_children = []
|
||||
self.get_parent().container.remove(self.get_parent().footer)
|
||||
self.get_parent().footer = None
|
||||
edit_text_b = edit_text_block(self.get_parent().text)
|
||||
self.get_parent().container.append(edit_text_b)
|
||||
window.set_focus(edit_text_b)
|
||||
|
||||
|
||||
class message(Gtk.Overlay):
|
||||
__gtype_name__ = 'AlpacaMessage'
|
||||
|
||||
def __init__(self, message_id:str, model:str=None):
|
||||
self.message_id = message_id
|
||||
self.bot = model != None
|
||||
self.dt = None
|
||||
self.model = model
|
||||
self.action_buttons = None
|
||||
self.content_children = [] #These are the code blocks, text blocks and tables
|
||||
self.footer = None
|
||||
self.image_c = None
|
||||
self.attachment_c = None
|
||||
self.spinner = None
|
||||
self.text = None
|
||||
|
||||
self.container = Gtk.Box(
|
||||
orientation=1,
|
||||
halign='fill',
|
||||
css_classes=["response_message"] if self.bot else ["card", "user_message"],
|
||||
spacing=12
|
||||
)
|
||||
|
||||
super().__init__(css_classes=["message"], name=message_id)
|
||||
self.set_child(self.container)
|
||||
|
||||
def add_attachments(self, attachments:dict):
|
||||
self.attachment_c = attachment_container()
|
||||
self.container.append(self.attachment_c)
|
||||
for file_path, file_type in attachments.items():
|
||||
file = attachment(os.path.basename(file_path), file_path, file_type)
|
||||
self.attachment_c.add_file(file)
|
||||
|
||||
def add_images(self, images:list):
|
||||
self.image_c = image_container()
|
||||
self.container.append(self.image_c)
|
||||
for image_path in images:
|
||||
image_element = image(image_path)
|
||||
self.image_c.add_image(image_element)
|
||||
|
||||
def add_footer(self, dt:datetime.datetime):
|
||||
self.dt = dt
|
||||
self.footer = footer(self.dt, self.model)
|
||||
self.container.append(self.footer)
|
||||
|
||||
def add_action_buttons(self):
|
||||
if not self.action_buttons:
|
||||
self.action_buttons = action_buttons(self.bot)
|
||||
self.add_overlay(self.action_buttons)
|
||||
|
||||
def update_message(self, data:dict):
|
||||
chat = self.get_parent().get_parent().get_parent().get_parent()
|
||||
if chat.busy:
|
||||
if self.spinner:
|
||||
if not window.chat_list_box.get_sensitive():
|
||||
window.chat_list_box.set_sensitive(True)
|
||||
self.container.remove(self.spinner)
|
||||
self.spinner = None
|
||||
self.content_children[-1].set_visible(True)
|
||||
self.content_children[-1].insert_at_end(data['message']['content'], False)
|
||||
if 'done' in data and data['done']:
|
||||
if chat.welcome_screen:
|
||||
chat.container.remove(chat.welcome_screen)
|
||||
chat.welcome_screen = None
|
||||
chat.stop_message()
|
||||
self.set_text(self.content_children[-1].get_label())
|
||||
self.dt = datetime.datetime.now()
|
||||
self.add_footer(self.dt)
|
||||
window.save_history(chat)
|
||||
|
||||
def set_text(self, text:str=None):
|
||||
self.text = text
|
||||
for child in self.content_children:
|
||||
self.container.remove(child)
|
||||
self.content_children = []
|
||||
if text:
|
||||
self.content_children = []
|
||||
code_block_pattern = re.compile(r'```(\w+)\n(.*?)\n```', re.DOTALL)
|
||||
no_lang_code_block_pattern = re.compile(r'`\n(.*?)\n`', re.DOTALL)
|
||||
table_pattern = re.compile(r'((\r?\n){2}|^)([^\r\n]*\|[^\r\n]*(\r?\n)?)+(?=(\r?\n){2}|$)', re.MULTILINE)
|
||||
bold_pattern = re.compile(r'\*\*(.*?)\*\*') #"**text**"
|
||||
code_pattern = re.compile(r'`([^`\n]*?)`') #"`text`"
|
||||
h1_pattern = re.compile(r'^#\s(.*)$') #"# text"
|
||||
h2_pattern = re.compile(r'^##\s(.*)$') #"## text"
|
||||
markup_pattern = re.compile(r'<(b|u|tt|span.*)>(.*?)<\/(b|u|tt|span)>') #heh butt span, I'm so funny
|
||||
parts = []
|
||||
pos = 0
|
||||
# Code blocks
|
||||
for match in code_block_pattern.finditer(self.text):
|
||||
start, end = match.span()
|
||||
if pos < start:
|
||||
normal_text = self.text[pos:start]
|
||||
parts.append({"type": "normal", "text": normal_text.strip()})
|
||||
language = match.group(1)
|
||||
code_text = match.group(2)
|
||||
parts.append({"type": "code", "text": code_text, "language": 'python3' if language == 'python' else language})
|
||||
pos = end
|
||||
# Code blocks (No language)
|
||||
for match in no_lang_code_block_pattern.finditer(self.text):
|
||||
start, end = match.span()
|
||||
if pos < start:
|
||||
normal_text = self.text[pos:start]
|
||||
parts.append({"type": "normal", "text": normal_text.strip()})
|
||||
code_text = match.group(1)
|
||||
parts.append({"type": "code", "text": code_text, "language": None})
|
||||
pos = end
|
||||
# Tables
|
||||
for match in table_pattern.finditer(self.text):
|
||||
start, end = match.span()
|
||||
if pos < start:
|
||||
normal_text = self.text[pos:start]
|
||||
parts.append({"type": "normal", "text": normal_text.strip()})
|
||||
table_text = match.group(0)
|
||||
parts.append({"type": "table", "text": table_text})
|
||||
pos = end
|
||||
# Text blocks
|
||||
if pos < len(text):
|
||||
normal_text = text[pos:]
|
||||
if normal_text.strip():
|
||||
parts.append({"type": "normal", "text": normal_text.strip()})
|
||||
|
||||
for part in parts:
|
||||
if part['type'] == 'normal':
|
||||
text_b = text_block(self.bot)
|
||||
part['text'] = part['text'].replace("\n* ", "\n• ")
|
||||
part['text'] = code_pattern.sub(r'<tt>\1</tt>', part['text'])
|
||||
part['text'] = bold_pattern.sub(r'<b>\1</b>', part['text'])
|
||||
part['text'] = h1_pattern.sub(r'<span size="x-large">\1</span>', part['text'])
|
||||
part['text'] = h2_pattern.sub(r'<span size="large">\1</span>', part['text'])
|
||||
pos = 0
|
||||
for match in markup_pattern.finditer(part['text']):
|
||||
start, end = match.span()
|
||||
if pos < start:
|
||||
text_b.insert_at_end(part['text'][pos:start], False)
|
||||
text_b.insert_at_end(match.group(0), True)
|
||||
pos = end
|
||||
|
||||
if pos < len(part['text']):
|
||||
text_b.insert_at_end(part['text'][pos:], False)
|
||||
self.content_children.append(text_b)
|
||||
self.container.append(text_b)
|
||||
elif part['type'] == 'code':
|
||||
code_b = code_block(part['text'], part['language'])
|
||||
self.content_children.append(code_b)
|
||||
self.container.append(code_b)
|
||||
elif part['type'] == 'table':
|
||||
table_w = TableWidget(part['text'])
|
||||
self.content_children.append(table_w)
|
||||
self.container.append(table_w)
|
||||
self.add_action_buttons()
|
||||
else:
|
||||
text_b = text_block(self.bot)
|
||||
text_b.set_visible(False)
|
||||
self.content_children.append(text_b)
|
||||
self.spinner = Gtk.Spinner(spinning=True, margin_top=12, margin_bottom=12, hexpand=True)
|
||||
self.container.append(self.spinner)
|
||||
self.container.append(text_b)
|
||||
self.container.queue_draw()
|
||||
|
110
src/custom_widgets/model_widget.py
Normal file
110
src/custom_widgets/model_widget.py
Normal file
@ -0,0 +1,110 @@
|
||||
#model_widget.py
|
||||
"""
|
||||
Handles the model widget (testing)
|
||||
"""
|
||||
|
||||
import gi
|
||||
gi.require_version('Gtk', '4.0')
|
||||
gi.require_version('GtkSource', '5')
|
||||
from gi.repository import Gtk, GObject, Gio, Adw, GtkSource, GLib, Gdk
|
||||
import logging, os, datetime, re, shutil, threading
|
||||
from ..internal import config_dir, data_dir, cache_dir, source_dir
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
window = None
|
||||
|
||||
class model_selector_popup(Gtk.Popover):
|
||||
__gtype_name__ = 'AlpacaModelSelectorPopup'
|
||||
|
||||
def __init__(self):
|
||||
manage_models_button = Gtk.Button(
|
||||
tooltip_text=_('Model Manager'),
|
||||
child=Gtk.Label(label=_('Model Manager')),
|
||||
hexpand=True,
|
||||
css_classes=['manage_models_button', 'flat']
|
||||
)
|
||||
manage_models_button.set_action_name("app.manage_models")
|
||||
manage_models_button.connect("clicked", lambda *_: self.hide())
|
||||
self.model_list_box = Gtk.ListBox(
|
||||
css_classes=['navigation-sidebar', 'model_list_box'],
|
||||
height_request=0
|
||||
)
|
||||
container = Gtk.Box(
|
||||
orientation=1,
|
||||
spacing=5
|
||||
)
|
||||
container.append(self.model_list_box)
|
||||
container.append(Gtk.Separator())
|
||||
container.append(manage_models_button)
|
||||
|
||||
scroller = Gtk.ScrolledWindow(
|
||||
max_content_height=300,
|
||||
propagate_natural_width=True,
|
||||
propagate_natural_height=True,
|
||||
child=container
|
||||
)
|
||||
|
||||
super().__init__(
|
||||
css_classes=['model_popover'],
|
||||
has_arrow=False,
|
||||
child=scroller
|
||||
)
|
||||
|
||||
class model_selector_button(Gtk.MenuButton):
|
||||
__gtype_name__ = 'AlpacaModelSelectorButton'
|
||||
|
||||
def __init__(self):
|
||||
self.popover = model_selector_popup()
|
||||
self.popover.model_list_box.connect('selected-rows-changed', self.model_changed)
|
||||
self.popover.model_list_box.connect('row-activated', lambda *_: self.get_popover().hide())
|
||||
super().__init__(
|
||||
tooltip_text=_('Select a Model'),
|
||||
child=Adw.ButtonContent(
|
||||
label=_('Select a model'),
|
||||
icon_name='down-symbolic'
|
||||
),
|
||||
popover=self.popover
|
||||
)
|
||||
|
||||
def change_model(self, model_name:str):
|
||||
for model_row in list(self.get_popover().model_list_box):
|
||||
if model_name == model_row.get_name():
|
||||
self.get_popover().model_list_box.select_row(model_row)
|
||||
break
|
||||
|
||||
def model_changed(self, listbox:Gtk.ListBox):
|
||||
row = listbox.get_selected_row()
|
||||
if row:
|
||||
model_name = row.get_name()
|
||||
self.get_child().set_label(window.convert_model_name(model_name, 0))
|
||||
self.set_tooltip_text(window.convert_model_name(model_name, 0))
|
||||
elif len(list(listbox)) == 0:
|
||||
self.get_child().set_label(_("Select a model"))
|
||||
self.set_tooltip_text(_("Select a Model"))
|
||||
|
||||
def get_model(self) -> str:
|
||||
row = self.get_popover().model_list_box.get_selected_row()
|
||||
if row:
|
||||
return row.get_name()
|
||||
|
||||
def add_model(self, model_name:str):
|
||||
model_row = Gtk.ListBoxRow(
|
||||
child = Gtk.Label(
|
||||
label=window.convert_model_name(model_name, 0),
|
||||
halign=1,
|
||||
hexpand=True
|
||||
),
|
||||
halign=0,
|
||||
hexpand=True,
|
||||
name=model_name,
|
||||
tooltip_text=window.convert_model_name(model_name, 0)
|
||||
)
|
||||
self.get_popover().model_list_box.append(model_row)
|
||||
self.change_model(model_name)
|
||||
|
||||
def get_model_list(self) -> list:
|
||||
return [model.get_name() for model in list(self.get_popover().model_list_box)]
|
||||
|
||||
def clear_list(self):
|
||||
self.get_popover().model_list_box.remove_all()
|
@ -37,7 +37,8 @@ class TableWidget(Gtk.Frame):
|
||||
|
||||
def __init__(self, markdown):
|
||||
super().__init__()
|
||||
|
||||
self.set_margin_start(5)
|
||||
self.set_margin_end(5)
|
||||
self.table = MarkdownTable()
|
||||
|
||||
self.set_halign(Gtk.Align.START)
|
||||
|
@ -14,7 +14,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
def clear_chat_response(self, dialog, task):
|
||||
if dialog.choose_finish(task) == "clear":
|
||||
self.clear_chat()
|
||||
self.chat_list_box.get_current_chat().clear_chat()
|
||||
|
||||
def clear_chat(self):
|
||||
if self.bot_message is not None:
|
||||
@ -39,7 +39,7 @@ def clear_chat(self):
|
||||
|
||||
def delete_chat_response(self, dialog, task, chat_name):
|
||||
if dialog.choose_finish(task) == "delete":
|
||||
self.delete_chat(chat_name)
|
||||
self.chat_list_box.delete_chat(chat_name)
|
||||
|
||||
def delete_chat(self, chat_name):
|
||||
dialog = Adw.AlertDialog(
|
||||
@ -59,16 +59,16 @@ def delete_chat(self, chat_name):
|
||||
|
||||
# RENAME CHAT | WORKS
|
||||
|
||||
def rename_chat_response(self, dialog, task, old_chat_name, entry, label_element):
|
||||
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.rename_chat(old_chat_name, new_chat_name, label_element)
|
||||
self.chat_list_box.rename_chat(old_chat_name, new_chat_name)
|
||||
|
||||
def rename_chat(self, chat_name, label_element):
|
||||
def rename_chat(self, chat_name):
|
||||
entry = Gtk.Entry()
|
||||
dialog = Adw.AlertDialog(
|
||||
heading=_("Rename Chat?"),
|
||||
@ -83,7 +83,7 @@ def rename_chat(self, chat_name, label_element):
|
||||
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)
|
||||
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"
|
||||
|
@ -48,7 +48,10 @@ alpaca_sources = [
|
||||
]
|
||||
|
||||
custom_widgets = [
|
||||
'custom_widgets/table_widget.py'
|
||||
'custom_widgets/table_widget.py',
|
||||
'custom_widgets/message_widget.py',
|
||||
'custom_widgets/chat_widget.py',
|
||||
'custom_widgets/model_widget.py'
|
||||
]
|
||||
|
||||
install_data(alpaca_sources, install_dir: moduledir)
|
||||
|
@ -18,13 +18,16 @@
|
||||
.model_list_box > * {
|
||||
margin: 0;
|
||||
}
|
||||
.user_message, .response_message {
|
||||
padding: 12px;
|
||||
.user_message > label, .response_message > label {
|
||||
padding: 7px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
.user_message:focus, .response_message:focus, .editing_message_textview:focus, .code_block:focus {
|
||||
.user_message label:focus, .response_message label:focus, .editing_message_textview:focus, .code_block:focus {
|
||||
box-shadow: 0 0 1px 2px mix(@accent_color, @window_bg_color, 0.5);
|
||||
}
|
||||
.model_popover {
|
||||
margin-top: 6px;
|
||||
}
|
||||
stacksidebar {
|
||||
border: none;
|
||||
}
|
||||
|
1029
src/window.py
1029
src/window.py
File diff suppressed because it is too large
Load Diff
132
src/window.ui
132
src/window.ui
@ -26,6 +26,7 @@
|
||||
<object class="AdwHeaderBar">
|
||||
<child type="start">
|
||||
<object class="GtkButton" id="add_chat_button">
|
||||
<property name="action-name">app.new_chat</property>
|
||||
<property name="tooltip-text" translatable="yes">New Chat</property>
|
||||
<property name="icon-name">chat-message-new-symbolic</property>
|
||||
<style>
|
||||
@ -44,18 +45,9 @@
|
||||
</object>
|
||||
</child>
|
||||
<property name="content">
|
||||
<object class="GtkScrolledWindow">
|
||||
<object class="GtkScrolledWindow" id="chat_list_container">
|
||||
<property name="vexpand">true</property>
|
||||
<property name="hexpand">true</property>
|
||||
<child>
|
||||
<object class="GtkListBox" id="chat_list_box">
|
||||
<signal name="row-selected" handler="chat_changed"/>
|
||||
<property name="selection-mode">single</property>
|
||||
<style>
|
||||
<class name="navigation-sidebar"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
@ -71,91 +63,6 @@
|
||||
<property name="active" bind-source="split_view_overlay" bind-property="show-sidebar" bind-flags="sync-create"/>
|
||||
</object>
|
||||
</child>
|
||||
<property name="title-widget">
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">0</property>
|
||||
<property name="spacing">12</property>
|
||||
<child>
|
||||
<object class="GtkMenuButton" id="model_selector_button">
|
||||
<property name="tooltip-text" translatable="yes">Select Model</property>
|
||||
<property name="child">
|
||||
<object class="GtkBox">
|
||||
<property name="spacing">10</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="label" translatable="yes">Select a Model</property>
|
||||
<property name="ellipsize">2</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="icon-name">down-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
<property name="halign">1</property>
|
||||
<style>
|
||||
<class name="raised"/>
|
||||
</style>
|
||||
<property name="popover">
|
||||
<object class="GtkPopover" id="model_popover">
|
||||
<style>
|
||||
<class name="model_popover"/>
|
||||
</style>
|
||||
<property name="has-arrow">false</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">1</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkButton">
|
||||
<property name="child">
|
||||
<object class="GtkLabel">
|
||||
<property name="label" translatable="yes">Manage Models</property>
|
||||
<property name="justify">left</property>
|
||||
<property name="halign">1</property>
|
||||
</object>
|
||||
</property>
|
||||
<property name="hexpand">true</property>
|
||||
<property name="tooltip-text" translatable="yes">Manage Models</property>
|
||||
<property name="action-name">app.manage_models</property>
|
||||
<signal name="clicked" handler="close_model_popup"/>
|
||||
<style>
|
||||
<class name="flat"/>
|
||||
<class name="manage_models_button"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparator"/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="max-content-height">300</property>
|
||||
<property name="propagate-natural-width">true</property>
|
||||
<property name="propagate-natural-height">true</property>
|
||||
<child>
|
||||
<object class="GtkListBox" id="model_list_box">
|
||||
<property name="hexpand">true</property>
|
||||
<style>
|
||||
<class name="navigation-sidebar"/>
|
||||
<class name="model_list_box"/>
|
||||
</style>
|
||||
<signal name="row-selected" handler="change_model"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
<child type="end">
|
||||
<object class="GtkMenuButton" id="secondary_menu_button">
|
||||
<property name="primary">False</property>
|
||||
@ -174,32 +81,10 @@
|
||||
<child>
|
||||
<object class="AdwToastOverlay" id="main_overlay">
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="chat_window">
|
||||
<property name="propagate-natural-height">true</property>
|
||||
<property name="kinetic-scrolling">true</property>
|
||||
<object class="GtkStack" id="chat_stack">
|
||||
<property name="hexpand">true</property>
|
||||
<property name="vexpand">true</property>
|
||||
<style>
|
||||
<class name="undershoot-bottom"/>
|
||||
</style>
|
||||
<child>
|
||||
<object class="AdwClamp">
|
||||
<property name="maximum-size">1000</property>
|
||||
<property name="tightening-threshold">800</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="chat_container">
|
||||
<property name="orientation">1</property>
|
||||
<property name="homogeneous">false</property>
|
||||
<property name="hexpand">true</property>
|
||||
<property name="vexpand">true</property>
|
||||
<property name="spacing">12</property>
|
||||
<property name="margin-top">12</property>
|
||||
<property name="margin-bottom">12</property>
|
||||
<property name="margin-start">12</property>
|
||||
<property name="margin-end">12</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<property name="hhomogeneous">true</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
@ -1139,6 +1024,12 @@
|
||||
<property name="title" translatable="yes">Toggle sidebar</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkShortcutsShortcut">
|
||||
<property name="accelerator">F2</property>
|
||||
<property name="title" translatable="yes">Rename chat</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
@ -1175,3 +1066,4 @@
|
||||
</object>
|
||||
</interface>
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user