24 Commits
1.0.3 ... 1.0.5

Author SHA1 Message Date
jeffser
8c0ec3957f Preparing for 1.0.5 2024-08-02 23:56:17 -06:00
jeffser
72063a15d9 Better check for message finishing 2024-08-02 23:48:03 -06:00
jeffser
0d1b15aafc New feature: Regenerate response 2024-08-02 23:42:35 -06:00
jeffser
ca10369bdc Added message to support dialog 2024-08-02 21:44:19 -06:00
jeffser
42af75d8d2 typo 2024-08-02 20:53:19 -06:00
jeffser
a02871dd28 'S fixed again :3 2024-08-02 20:50:04 -06:00
jeffser
e65a8bc648 Proper GGUF / name Model pulling 2024-08-02 20:47:04 -06:00
jeffser
b373b6a34f Sidebar button 2024-08-02 16:44:24 -06:00
jeffser
6d6a0255e2 Better model name handling internally 2024-08-02 16:00:47 -06:00
jeffser
003d6a3d5f Restore last model used 2024-08-02 15:30:03 -06:00
jeffser
77a2c60fe5 Fixed message entry shadow 2024-08-02 15:19:28 -06:00
jeffser
ac3bd699ee Changed width request for model dropdown 2024-08-02 14:51:08 -06:00
jeffser
596498c81e Fixed 'S problem with title generation 2024-08-02 14:42:11 -06:00
jeffser
c95f764c77 Merge branch 'main' of github.com-jeffser:Jeffser/Alpaca 2024-08-02 14:39:26 -06:00
jeffser
5c5be05843 Reverted back to standard styles 2024-08-02 14:39:19 -06:00
aritra saha
3fb26ec49e updated translation (#180)
* Update bn.po

* Update bn.po

* Update bn.po

* Update bn.po

* Update zh_CN.po
2024-08-02 11:19:23 -06:00
jeffser
3f767d22e9 New relase notes 2024-08-01 14:56:38 -06:00
jeffser
7f3fb0d82d Tweaks to chat title generation 2024-08-01 14:52:37 -06:00
jeffser
d56c132459 Fixed title of model tag selector dialog 2024-08-01 14:48:11 -06:00
jeffser
acdce762c9 Translations update 2024-08-01 14:37:06 -06:00
jeffser
bd557d9652 Preparing for 1.0.4 2024-08-01 14:21:55 -06:00
Jeffry Samuel
3363d13fa0 changed support dialog frequency 2024-08-01 14:03:38 -06:00
Jeffry Samuel
52ba44e260 Update README.md 2024-08-01 14:02:28 -06:00
Nokse22
f06c2dae23 Added tables (#179) 2024-08-01 13:44:56 -06:00
19 changed files with 7177 additions and 3744 deletions

View File

@@ -59,7 +59,7 @@ You can change anything except `$HOME` and `$OLLAMA_HOST`, to do this go to `~/.
- [TylerLaBree](https://github.com/TylerLaBree) for their requests and ideas - [TylerLaBree](https://github.com/TylerLaBree) for their requests and ideas
- [Alexkdeveloper](https://github.com/alexkdeveloper) for their help translating the app to Russian - [Alexkdeveloper](https://github.com/alexkdeveloper) for their help translating the app to Russian
- [Imbev](https://github.com/imbev) for their reports and suggestions - [Imbev](https://github.com/imbev) for their reports and suggestions
- [Nokse](https://github.com/Nokse22) for their contributions to the UI - [Nokse](https://github.com/Nokse22) for their contributions to the UI and table rendering
- [Louis Chauvet-Villaret](https://github.com/loulou64490) for their suggestions and help translating the app to French - [Louis Chauvet-Villaret](https://github.com/loulou64490) for their suggestions and help translating the app to French
- [CounterFlow64](https://github.com/CounterFlow64) for their help translating the app to Norwegian - [CounterFlow64](https://github.com/CounterFlow64) for their help translating the app to Norwegian

View File

@@ -80,6 +80,42 @@
<url type="contribute">https://github.com/Jeffser/Alpaca/discussions/154</url> <url type="contribute">https://github.com/Jeffser/Alpaca/discussions/154</url>
<url type="vcs-browser">https://github.com/Jeffser/Alpaca</url> <url type="vcs-browser">https://github.com/Jeffser/Alpaca</url>
<releases> <releases>
<release version="1.0.5" date="2024-08-02">
<url type="details">https://github.com/Jeffser/Alpaca/releases/tag/1.0.5</url>
<description>
<p>New</p>
<ul>
<li>Regenerate any response, even if they are incomplete</li>
<li>Support for pulling models by name:tag</li>
<li>Stable support for GGUF model files</li>
<li>Restored sidebar toggle button</li>
</ul>
<p>Fixes</p>
<ul>
<li>Reverted back to standard styles</li>
<li>Fixed generated titles having "'S" for some reason</li>
<li>Changed min width for model dropdown</li>
<li>Changed message entry shadow</li>
<li>The last model used is now restored when the user changes chat</li>
<li>Better check for message finishing</li>
</ul>
</description>
</release>
<release version="1.0.4" date="2024-08-01">
<url type="details">https://github.com/Jeffser/Alpaca/releases/tag/1.0.4</url>
<description>
<p>New</p>
<ul>
<li>Added table rendering (Thanks Nokse)</li>
</ul>
<p>Fixes</p>
<ul>
<li>Made support dialog more common</li>
<li>Dialog title on tag chooser when downloading models didn't display properly</li>
<li>Prevent chat generation from generating a title with multiple lines</li>
</ul>
</description>
</release>
<release version="1.0.3" date="2024-08-01"> <release version="1.0.3" date="2024-08-01">
<url type="details">https://github.com/Jeffser/Alpaca/releases/tag/1.0.3</url> <url type="details">https://github.com/Jeffser/Alpaca/releases/tag/1.0.3</url>
<description> <description>

View File

@@ -1,5 +1,5 @@
project('Alpaca', 'c', project('Alpaca', 'c',
version: '1.0.3', version: '1.0.5',
meson_version: '>= 0.62.0', meson_version: '>= 0.62.0',
default_options: [ 'warning_level=2', 'werror=false', ], default_options: [ 'warning_level=2', 'werror=false', ],
) )

File diff suppressed because it is too large Load Diff

1332
po/bn.po

File diff suppressed because it is too large Load Diff

1335
po/es.po

File diff suppressed because it is too large Load Diff

1233
po/fr.po

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

1310
po/ru.po

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -28,6 +28,7 @@
<file alias="icons/scalable/status/edit-find-symbolic.svg">icons/edit-find-symbolic.svg</file> <file alias="icons/scalable/status/edit-find-symbolic.svg">icons/edit-find-symbolic.svg</file>
<file alias="icons/scalable/status/edit-symbolic.svg">icons/edit-symbolic.svg</file> <file alias="icons/scalable/status/edit-symbolic.svg">icons/edit-symbolic.svg</file>
<file alias="icons/scalable/status/image-missing-symbolic.svg">icons/image-missing-symbolic.svg</file> <file alias="icons/scalable/status/image-missing-symbolic.svg">icons/image-missing-symbolic.svg</file>
<file alias="icons/scalable/status/update-symbolic.svg">icons/update-symbolic.svg</file>
<file preprocess="xml-stripblanks">window.ui</file> <file preprocess="xml-stripblanks">window.ui</file>
<file preprocess="xml-stripblanks">gtk/help-overlay.ui</file> <file preprocess="xml-stripblanks">gtk/help-overlay.ui</file>
</gresource> </gresource>

View File

@@ -224,7 +224,7 @@ def create_model_from_existing_response(self, dialog, task, dropdown):
def create_model_from_existing(self): def create_model_from_existing(self):
string_list = Gtk.StringList() string_list = Gtk.StringList()
for model in self.local_models: for model in self.local_models:
string_list.append(model) string_list.append(self.convert_model_name(model, 0))
dropdown = Gtk.DropDown() dropdown = Gtk.DropDown()
dropdown.set_model(string_list) dropdown.set_model(string_list)
@@ -257,6 +257,27 @@ def create_model_from_file(self):
file_dialog = Gtk.FileDialog(default_filter=self.file_filter_gguf) 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)) 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:
self.pull_model(model)
def create_model_from_name(self):
entry = Gtk.Entry()
entry.get_delegate().connect("insert-text", 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.choose(
parent = self,
cancellable = None,
callback = lambda dialog, task, entry=entry: create_model_from_name_response(self, dialog, task, entry)
)
# FILE CHOOSER | WORKS # FILE CHOOSER | WORKS
def attach_file_response(self, file_dialog, result): def attach_file_response(self, file_dialog, result):
@@ -378,6 +399,8 @@ def support_response(self, dialog, task):
elif res == 'support': elif res == 'support':
self.show_toast(_("Thank you!"), self.main_overlay) self.show_toast(_("Thank you!"), self.main_overlay)
os.system('xdg-open https://github.com/sponsors/Jeffser') os.system('xdg-open https://github.com/sponsors/Jeffser')
elif res == 'nope':
self.show_toast(_("Visit Alpaca's website if you change your mind!"), self.main_overlay)
self.show_support = False self.show_support = False
self.save_server_config() self.save_server_config()

View File

@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 0 16 16" width="16px"><g fill="#222222"><path d="m 7.957031 2 c -0.082031 0 -0.164062 0.003906 -0.246093 0.007812 c -0.1875 0.011719 -0.375 0.03125 -0.5625 0.0625 c -1.582032 0.226563 -3.007813 1.070313 -3.96875 2.34375 c -0.804688 1.074219 -1.183594 2.332032 -1.179688 3.585938 h 2.003906 c 0 -0.832031 0.253906 -1.671875 0.796875 -2.398438 c 1.335938 -1.777343 3.820313 -2.113281 5.597657 -0.78125 c 0.429687 0.320313 0.769531 0.734376 1.03125 1.1875 h -1.4375 c -0.550782 0 -1 0.449219 -1 1 v 1 h 6 v -6 h -1 c -0.550782 0 -1 0.449219 -1 1 v 1.6875 c -1.113282 -1.695312 -3.007813 -2.710937 -5.039063 -2.695312 z m 0 0"/><path d="m 8.035156 15.007812 c 0.082032 0 0.164063 -0.003906 0.246094 -0.007812 c 0.1875 -0.011719 0.375 -0.03125 0.5625 -0.0625 c 1.582031 -0.226562 3.007812 -1.066406 3.96875 -2.34375 c 0.804688 -1.074219 1.183594 -2.332031 1.179688 -3.585938 h -2.003907 c -0.003906 0.832032 -0.257812 1.675782 -0.796875 2.398438 c -1.335937 1.777344 -3.820312 2.113281 -5.597656 0.78125 c -0.429688 -0.320312 -0.769531 -0.734375 -1.03125 -1.1875 h 1.4375 c 0.550781 0 1 -0.449219 1 -1 v -1 h -6 v 6 h 1 c 0.550781 0 1 -0.449219 1 -1 v -1.6875 c 1.113281 1.695312 3.007812 2.710938 5.035156 2.695312 z m 0 0"/></g></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -43,7 +43,8 @@ alpaca_sources = [
'dialogs.py', 'dialogs.py',
'local_instance.py', 'local_instance.py',
'available_models.json', 'available_models.json',
'available_models_descriptions.py' 'available_models_descriptions.py',
'table_widget.py'
] ]
install_data(alpaca_sources, install_dir: moduledir) install_data(alpaca_sources, install_dir: moduledir)

View File

@@ -2,7 +2,7 @@
box-shadow: none; box-shadow: none;
border-width: 0; border-width: 0;
} }
.message_text_view { .message_text_view, .modelfile_textview {
background-color: rgba(0,0,0,0); background-color: rgba(0,0,0,0);
} }
.chat_image_button { .chat_image_button {
@@ -12,7 +12,3 @@
border-radius: 5px; border-radius: 5px;
padding: 5px; padding: 5px;
} }
.chat_row:selected {
background: mix(@theme_bg_color, @theme_selected_bg_color, 0.3);
color: mix(@window_fg_color, @theme_selected_bg_color, 0.5);
}

126
src/table_widget.py Normal file
View File

@@ -0,0 +1,126 @@
import gi
from gi.repository import Adw
from gi.repository import Gtk, GObject, Gio
import re
class MarkdownTable:
def __init__(self):
self.headers = []
self.rows = Gio.ListStore()
self.alignments = []
def __repr__(self):
table_repr = 'Headers: {}\n'.format(self.headers)
table_repr += 'Alignments: {}\n'.format(self.alignments)
table_repr += 'Rows:\n'
for row in self.rows:
table_repr += ' | '.join(row) + '\n'
return table_repr
class Row(GObject.GObject):
def __init__(self, _values):
super().__init__()
self.values = _values
def get_column_value(self, index):
return self.values[index]
class TableWidget(Gtk.Frame):
__gtype_name__ = 'TableWidget'
def __init__(self, markdown):
super().__init__()
self.table = MarkdownTable()
self.set_halign(Gtk.Align.START)
self.table_widget = Gtk.ColumnView(
show_column_separators=True,
show_row_separators=True,
reorderable=False,
)
scrolled_window = Gtk.ScrolledWindow(
vscrollbar_policy=Gtk.PolicyType.NEVER,
propagate_natural_width=True
)
self.set_child(scrolled_window)
try:
self.parse_markdown_table(markdown)
self.make_table()
scrolled_window.set_child(self.table_widget)
except:
label = Gtk.Label(
label=markdown.lstrip('\n').rstrip('\n'),
selectable=True,
margin_top=6,
margin_bottom=6,
margin_start=6,
margin_end=6
)
scrolled_window.set_child(label)
def parse_markdown_table(self, markdown_text):
# Define regex patterns for matching the table components
header_pattern = r'^\|(.+?)\|$'
separator_pattern = r'^\|(\s*[:-]+:?\s*\|)+$'
row_pattern = r'^\|(.+?)\|$'
# Split the text into lines
lines = markdown_text.strip().split('\n')
# Extract headers
header_match = re.match(header_pattern, lines[0], re.MULTILINE)
if header_match:
headers = [header.strip() for header in header_match.group(1).replace("*", "").split('|') if header.strip()]
self.table.headers = headers
# Extract alignments
separator_match = re.match(separator_pattern, lines[1], re.MULTILINE)
if separator_match:
alignments = []
separator_columns = lines[1].replace(" ", "").split('|')[1:-1]
for sep in separator_columns:
if ':' in sep:
if sep.startswith('-') and sep.endswith(':'):
alignments.append(1)
elif sep.startswith(':') and sep.endswith('-'):
alignments.append(0)
else:
alignments.append(0.5)
else:
alignments.append(0) # Default alignment is start
self.table.alignments = alignments
# Extract rows
for line in lines[2:]:
row_match = re.match(row_pattern, line, re.MULTILINE)
if row_match:
rows = line.split('|')[1:-1]
row = Row(rows)
self.table.rows.append(row)
def make_table(self):
def _on_factory_setup(_factory, list_item, align):
label = Gtk.Label(xalign=align, ellipsize=3, selectable=True)
list_item.set_child(label)
def _on_factory_bind(_factory, list_item, index):
label_widget = list_item.get_child()
row = list_item.get_item()
label_widget.set_label(row.get_column_value(index))
for index, column_name in enumerate(self.table.headers):
column = Gtk.ColumnViewColumn(title=column_name, expand=True)
factory = Gtk.SignalListItemFactory()
factory.connect("setup", _on_factory_setup, self.table.alignments[index])
factory.connect("bind", _on_factory_bind, index)
column.set_factory(factory)
self.table_widget.append_column(column)
selection = Gtk.NoSelection.new(model=self.table.rows)
self.table_widget.set_model(model=selection)

View File

@@ -28,7 +28,7 @@ from PIL import Image
from pypdf import PdfReader from pypdf import PdfReader
from datetime import datetime from datetime import datetime
from . import dialogs, local_instance, connection_handler, available_models_descriptions from . import dialogs, local_instance, connection_handler, available_models_descriptions
from .table_widget import TableWidget
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -68,11 +68,11 @@ class AlpacaWindow(Adw.ApplicationWindow):
override_HIP_VISIBLE_DEVICES = Gtk.Template.Child() override_HIP_VISIBLE_DEVICES = Gtk.Template.Child()
#Elements #Elements
regenerate_button : Gtk.Button = None
create_model_base = Gtk.Template.Child() create_model_base = Gtk.Template.Child()
create_model_name = Gtk.Template.Child() create_model_name = Gtk.Template.Child()
create_model_system = Gtk.Template.Child() create_model_system = Gtk.Template.Child()
create_model_template = Gtk.Template.Child() create_model_modelfile = Gtk.Template.Child()
create_model_dialog = Gtk.Template.Child()
temperature_spin = Gtk.Template.Child() temperature_spin = Gtk.Template.Child()
seed_spin = Gtk.Template.Child() seed_spin = Gtk.Template.Child()
keep_alive_spin = Gtk.Template.Child() keep_alive_spin = Gtk.Template.Child()
@@ -135,7 +135,7 @@ class AlpacaWindow(Adw.ApplicationWindow):
def verify_if_image_can_be_used(self, pspec=None, user_data=None): def verify_if_image_can_be_used(self, pspec=None, user_data=None):
logger.debug("Verifying if image can be used") logger.debug("Verifying if image can be used")
if self.model_drop_down.get_selected_item() == None: return True if self.model_drop_down.get_selected_item() == None: return True
selected = self.model_drop_down.get_selected_item().get_string().split(" (")[0].lower() selected = self.convert_model_name(self.model_drop_down.get_selected_item().get_string(), 1).split(":")[0]
if selected in [key for key, value in self.available_models.items() if value["image"]]: if selected in [key for key, value in self.available_models.items() if value["image"]]:
for name, content in self.attachments.items(): for name, content in self.attachments.items():
if content['type'] == 'image': if content['type'] == 'image':
@@ -183,8 +183,7 @@ class AlpacaWindow(Adw.ApplicationWindow):
self.chats['order'].remove(self.chats['selected_chat']) self.chats['order'].remove(self.chats['selected_chat'])
self.chats['order'].insert(0, self.chats['selected_chat']) self.chats['order'].insert(0, self.chats['selected_chat'])
self.save_history() self.save_history()
current_model = self.model_drop_down.get_selected_item().get_string().split(' (') current_model = self.convert_model_name(self.model_drop_down.get_selected_item().get_string(), 1)
current_model = '{}:{}'.format(current_model[0].replace(' ', '-').lower(), current_model[1][:-1])
if current_model is None: if current_model is None:
self.show_toast(_("Please select a model before chatting"), self.main_overlay) self.show_toast(_("Please select a model before chatting"), self.main_overlay)
return return
@@ -282,8 +281,10 @@ class AlpacaWindow(Adw.ApplicationWindow):
self.chats["selected_chat"] = row.get_child().get_name() self.chats["selected_chat"] = row.get_child().get_name()
self.load_history_into_chat() self.load_history_into_chat()
if len(self.chats["chats"][self.chats["selected_chat"]]["messages"].keys()) > 0: if len(self.chats["chats"][self.chats["selected_chat"]]["messages"].keys()) > 0:
last_model_used = self.chats["chats"][self.chats["selected_chat"]]["messages"][list(self.chats["chats"][self.chats["selected_chat"]]["messages"].keys())[-1]]["model"]
last_model_used = self.convert_model_name(last_model_used, 0)
for i in range(self.model_string_list.get_n_items()): for i in range(self.model_string_list.get_n_items()):
if self.model_string_list.get_string(i) == self.chats["chats"][self.chats["selected_chat"]]["messages"][list(self.chats["chats"][self.chats["selected_chat"]]["messages"].keys())[-1]]["model"]: if self.model_string_list.get_string(i) == last_model_used:
self.model_drop_down.set_selected(i) self.model_drop_down.set_selected(i)
break break
self.save_history() self.save_history()
@@ -343,19 +344,18 @@ class AlpacaWindow(Adw.ApplicationWindow):
@Gtk.Template.Callback() @Gtk.Template.Callback()
def create_model_start(self, button): def create_model_start(self, button):
base = self.create_model_base.get_subtitle() name = self.create_model_name.get_text().lower().replace(":", "")
name = self.create_model_name.get_text() modelfile_buffer = self.create_model_modelfile.get_buffer()
system = self.create_model_system.get_text() modelfile_raw = modelfile_buffer.get_text(modelfile_buffer.get_start_iter(), modelfile_buffer.get_end_iter(), False)
template = self.create_model_template.get_text() modelfile = ["FROM {}".format(self.create_model_base.get_subtitle()), "SYSTEM {}".format(self.create_model_system.get_text())]
if "/" in base: for line in modelfile_raw.split('\n'):
modelfile = f"FROM {base}\nSYSTEM {system}\nTEMPLATE {template}" if not line.startswith('SYSTEM') and not line.startswith('FROM'):
else: modelfile.append(line)
modelfile = f"FROM {base}\nSYSTEM {system}"
self.pulling_model_list_box.set_visible(True) self.pulling_model_list_box.set_visible(True)
model_row = Adw.ActionRow( model_row = Adw.ActionRow(
title = name title = name
) )
thread = threading.Thread(target=self.pull_model_process, kwargs={"model": name, "modelfile": modelfile}) thread = threading.Thread(target=self.pull_model_process, kwargs={"model": name, "modelfile": '\n'.join(modelfile)})
overlay = Gtk.Overlay() overlay = Gtk.Overlay()
progress_bar = Gtk.ProgressBar( progress_bar = Gtk.ProgressBar(
valign = 2, valign = 2,
@@ -377,7 +377,7 @@ class AlpacaWindow(Adw.ApplicationWindow):
overlay.set_child(model_row) overlay.set_child(model_row)
overlay.add_overlay(progress_bar) overlay.add_overlay(progress_bar)
self.pulling_model_list_box.append(overlay) self.pulling_model_list_box.append(overlay)
self.create_model_dialog.close() self.navigation_view_manage_models.pop()
self.manage_models_dialog.present(self) self.manage_models_dialog.present(self)
thread.start() thread.start()
@@ -415,38 +415,37 @@ class AlpacaWindow(Adw.ApplicationWindow):
self.available_model_list_box.set_visible(True) self.available_model_list_box.set_visible(True)
self.no_results_page.set_visible(False) self.no_results_page.set_visible(False)
def convert_model_name(self, name:str, mode:int) -> str: # mode=0 name:tag -> Name (tag) | mode=1 Name (tag) -> name:tag
if mode == 0: return "{} ({})".format(name.split(":")[0].replace("-", " ").title(), name.split(":")[1])
if mode == 1: return "{}:{}".format(name.split(" (")[0].replace(" ", "-").lower(), name.split(" (")[1][:-1])
def check_alphanumeric(self, editable, text, length, position): def check_alphanumeric(self, editable, text, length, position):
new_text = ''.join([char for char in text if char.isalnum() or char in ['-', '_']]) new_text = ''.join([char for char in text if char.isalnum() or char in ['-', '.', ':']])
if new_text != text: editable.stop_emission_by_name("insert-text") if new_text != text: editable.stop_emission_by_name("insert-text")
def create_model(self, model:str, file:bool): def create_model(self, model:str, file:bool):
name = "" modelfile_buffer = self.create_model_modelfile.get_buffer()
system = "" modelfile_buffer.delete(modelfile_buffer.get_start_iter(), modelfile_buffer.get_end_iter())
template = "" self.create_model_system.set_text('')
if not file: if not file:
response = connection_handler.simple_post(f"{connection_handler.url}/api/show", json.dumps({"name": model})) response = connection_handler.simple_post(f"{connection_handler.url}/api/show", json.dumps({"name": self.convert_model_name(model, 1)}))
if response.status_code == 200: if response.status_code == 200:
data = json.loads(response.text) data = json.loads(response.text)
modelfile = []
for line in data['modelfile'].split('\n'): for line in data['modelfile'].split('\n'):
if line.startswith('SYSTEM'): if line.startswith('SYSTEM'):
system = line[len('SYSTEM'):].strip() self.create_model_system.set_text(line[len('SYSTEM'):].strip())
elif line.startswith('TEMPLATE'): if not line.startswith('SYSTEM') and not line.startswith('FROM') and not line.startswith('#'):
template = line[len('TEMPLATE'):].strip() modelfile.append(line)
self.create_model_template.set_sensitive(False) self.create_model_name.set_text(self.convert_model_name(model, 1).split(':')[0] + "-custom")
name = model.split(':')[0] modelfile_buffer.insert(modelfile_buffer.get_start_iter(), '\n'.join(modelfile), len('\n'.join(modelfile).encode('utf-8')))
else:
##TODO ERROR MESSAGE
return
else: else:
self.create_model_template.set_sensitive(True) self.create_model_name.set_text(model.split("/")[-1].split(".")[0])
template = '"""{{ if .System }}<|start_header_id|>system<|end_header_id|>\n\n{{ .System }}<|eot_id|>{{ end }}{{ if .Prompt }}<|start_header_id|>user<|end_header_id|>\n\n{{ .Prompt }}<|eot_id|>{{ end }}<|start_header_id|>assistant<|end_header_id|>\n{{ .Response }}<|eot_id|>"""' self.create_model_base.set_subtitle(self.convert_model_name(model, 1))
name = model.split("/")[-1].split(".")[0] self.navigation_view_manage_models.push_by_tag('model_create_page')
self.create_model_base.set_subtitle(model)
self.create_model_name.set_text(name)
self.create_model_system.set_text(system)
self.create_model_template.set_text(template)
self.manage_models_dialog.close()
self.create_model_dialog.present(self)
def show_toast(self, message:str, overlay): def show_toast(self, message:str, overlay):
logger.info(message) logger.info(message)
@@ -574,13 +573,12 @@ Generate a title following these rules:
```PROMPT ```PROMPT
{message['content']} {message['content']}
```""" ```"""
current_model = self.model_drop_down.get_selected_item().get_string().split(' (') current_model = self.convert_model_name(self.model_drop_down.get_selected_item().get_string(), 1)
current_model = '{}:{}'.format(current_model[0].replace(' ', '-').lower(), current_model[1][:-1])
data = {"model": current_model, "prompt": prompt, "stream": False} data = {"model": current_model, "prompt": prompt, "stream": False}
if 'images' in message: data["images"] = message['images'] if 'images' in message: data["images"] = message['images']
response = connection_handler.simple_post(f"{connection_handler.url}/api/generate", data=json.dumps(data)) response = connection_handler.simple_post(f"{connection_handler.url}/api/generate", data=json.dumps(data))
new_chat_name = json.loads(response.text)["response"].strip().removeprefix("Title: ").removeprefix("title: ").strip('\'"').title() new_chat_name = json.loads(response.text)["response"].strip().removeprefix("Title: ").removeprefix("title: ").strip('\'"').replace('\n', ' ').title().replace('\'S', '\'s')
new_chat_name = new_chat_name[:50] + (new_chat_name[50:] and '...') new_chat_name = new_chat_name[:50] + (new_chat_name[50:] and '...')
self.rename_chat(label_element.get_name(), new_chat_name, label_element) self.rename_chat(label_element.get_name(), new_chat_name, label_element)
@@ -616,6 +614,11 @@ Generate a title following these rules:
css_classes = ["flat", "circular"], css_classes = ["flat", "circular"],
tooltip_text = _("Edit Message") tooltip_text = _("Edit Message")
) )
regenerate_button = Gtk.Button(
icon_name = "update-symbolic",
css_classes = ["flat", "circular"],
tooltip_text = _("Regenerate Message")
)
button_container = Gtk.Box( button_container = Gtk.Box(
orientation=0, orientation=0,
@@ -731,9 +734,10 @@ Generate a title following these rules:
delete_button.connect("clicked", lambda button, element=overlay: self.delete_message(element)) delete_button.connect("clicked", lambda button, element=overlay: self.delete_message(element))
copy_button.connect("clicked", lambda button, element=overlay: self.copy_message(element)) copy_button.connect("clicked", lambda button, element=overlay: self.copy_message(element))
edit_button.connect("clicked", lambda button, element=overlay, textview=message_text, button_container=button_container: self.edit_message(element, textview, button_container)) edit_button.connect("clicked", lambda button, element=overlay, textview=message_text, button_container=button_container: self.edit_message(element, textview, button_container))
regenerate_button.connect('clicked', lambda button, id=id, bot_message_box=message_box, bot_message_button_container=button_container : self.regenerate_message(id, bot_message_box, bot_message_button_container))
button_container.append(delete_button) button_container.append(delete_button)
button_container.append(copy_button) button_container.append(copy_button)
if not bot: button_container.append(edit_button) button_container.append(regenerate_button if bot else edit_button)
overlay.add_overlay(button_container) overlay.add_overlay(button_container)
self.chat_container.append(overlay) self.chat_container.append(overlay)
@@ -756,25 +760,25 @@ Generate a title following these rules:
else: else:
self.local_model_list_box.set_visible(True) self.local_model_list_box.set_visible(True)
for model in json.loads(response.text)['models']: for model in json.loads(response.text)['models']:
model_name = self.convert_model_name(model["name"], 0)
model_row = Adw.ActionRow( model_row = Adw.ActionRow(
title = "<b>{}</b>".format(model["name"].split(":")[0].replace("-", " ").title()), title = "<b>{}</b>".format(model_name.split(" (")[0]),
subtitle = model["name"].split(":")[1] subtitle = model_name.split(" (")[1][:-1]
) )
button = Gtk.Button( button = Gtk.Button(
icon_name = "user-trash-symbolic", icon_name = "user-trash-symbolic",
vexpand = False, vexpand = False,
valign = 3, valign = 3,
css_classes = ["error", "circular"], css_classes = ["error", "circular"],
tooltip_text = _("Remove '{} ({})'").format(model["name"].split(":")[0].replace('-', ' ').title(), model["name"].split(":")[1]) tooltip_text = _("Remove '{}'").format(model_name)
) )
button.connect("clicked", lambda button=button, model_name=model["name"]: dialogs.delete_model(self, model_name)) button.connect("clicked", lambda button=button, model_name=model["name"]: dialogs.delete_model(self, model_name))
model_row.add_suffix(button) model_row.add_suffix(button)
self.local_model_list_box.append(model_row) self.local_model_list_box.append(model_row)
self.model_string_list.append(f"{model['name'].split(':')[0].replace('-', ' ').title()} ({model['name'].split(':')[1]})") self.model_string_list.append(model_name)
self.local_models.append(model["name"]) self.local_models.append(model["name"])
self.model_drop_down.set_selected(0) #self.verify_if_image_can_be_used()
self.verify_if_image_can_be_used()
return return
else: else:
self.connection_error() self.connection_error()
@@ -820,6 +824,16 @@ Generate a title following these rules:
code_text = match.group(1) code_text = match.group(1)
parts.append({"type": "code", "text": code_text, "language": None}) parts.append({"type": "code", "text": code_text, "language": None})
pos = end pos = end
# Match tables
table_pattern = re.compile(r'((\r?\n){2}|^)([^\r\n]*\|[^\r\n]*(\r?\n)?)+(?=(\r?\n){2}|$)', re.MULTILINE)
for match in table_pattern.finditer(text):
start, end = match.span()
if pos < start:
normal_text = 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
# Extract any remaining normal text after the last code block # Extract any remaining normal text after the last code block
if pos < len(text): if pos < len(text):
normal_text = text[pos:] normal_text = text[pos:]
@@ -869,7 +883,7 @@ Generate a title following these rules:
if footer: message_buffer.insert_markup(message_buffer.get_end_iter(), footer, len(footer.encode('utf-8'))) if footer: message_buffer.insert_markup(message_buffer.get_end_iter(), footer, len(footer.encode('utf-8')))
self.bot_message_box.append(message_text) self.bot_message_box.append(message_text)
else: elif part['type'] == 'code':
language = None language = None
if part['language']: if part['language']:
language = GtkSource.LanguageManager.get_default().get_language(part['language']) language = GtkSource.LanguageManager.get_default().get_language(part['language'])
@@ -899,6 +913,9 @@ Generate a title following these rules:
code_block_box.append(source_view) code_block_box.append(source_view)
self.bot_message_box.append(code_block_box) self.bot_message_box.append(code_block_box)
self.style_manager.connect("notify::dark", self.on_theme_changed, buffer) self.style_manager.connect("notify::dark", self.on_theme_changed, buffer)
elif part['type'] == 'table':
table = TableWidget(part['text'])
self.bot_message_box.append(table)
vadjustment = self.chat_window.get_vadjustment() vadjustment = self.chat_window.get_vadjustment()
vadjustment.set_value(vadjustment.get_upper()) vadjustment.set_value(vadjustment.get_upper())
self.bot_message = None self.bot_message = None
@@ -935,9 +952,9 @@ Generate a title following these rules:
vadjustment = self.chat_window.get_vadjustment() vadjustment = self.chat_window.get_vadjustment()
if id not in self.chats["chats"][self.chats["selected_chat"]]["messages"] or vadjustment.get_value() + 50 >= vadjustment.get_upper() - vadjustment.get_page_size(): if id not in self.chats["chats"][self.chats["selected_chat"]]["messages"] or vadjustment.get_value() + 50 >= vadjustment.get_upper() - vadjustment.get_page_size():
GLib.idle_add(vadjustment.set_value, vadjustment.get_upper()) GLib.idle_add(vadjustment.set_value, vadjustment.get_upper())
if data['done']: if 'done' in data and data['done']:
formated_date = GLib.markup_escape_text(self.generate_datetime_format(datetime.strptime(self.chats["chats"][self.chats["selected_chat"]]["messages"][id]["date"], '%Y/%m/%d %H:%M:%S'))) formated_date = GLib.markup_escape_text(self.generate_datetime_format(datetime.strptime(self.chats["chats"][self.chats["selected_chat"]]["messages"][id]["date"], '%Y/%m/%d %H:%M:%S')))
text = f"\n\n{data['model'].split(':')[0].replace('-', ' ').title()} ({data['model'].split(':')[1]})\n<small>{formated_date}</small>" text = f"\n\n{self.convert_model_name(data['model'], 0)}\n<small>{formated_date}</small>"
GLib.idle_add(self.bot_message.insert_markup, self.bot_message.get_end_iter(), text, len(text.encode('utf-8'))) GLib.idle_add(self.bot_message.insert_markup, self.bot_message.get_end_iter(), text, len(text.encode('utf-8')))
self.save_history() self.save_history()
GLib.idle_add(self.bot_message_button_container.set_visible, True) GLib.idle_add(self.bot_message_button_container.set_visible, True)
@@ -945,15 +962,9 @@ Generate a title following these rules:
first_paragraph = self.bot_message.get_text(self.bot_message.get_start_iter(), self.bot_message.get_end_iter(), False).split("\n")[0] first_paragraph = self.bot_message.get_text(self.bot_message.get_start_iter(), self.bot_message.get_end_iter(), False).split("\n")[0]
GLib.idle_add(self.show_notification, self.chats["selected_chat"], first_paragraph[:100] + (first_paragraph[100:] and '...'), Gio.ThemedIcon.new("chat-message-new-symbolic")) GLib.idle_add(self.show_notification, self.chats["selected_chat"], first_paragraph[:100] + (first_paragraph[100:] and '...'), Gio.ThemedIcon.new("chat-message-new-symbolic"))
else: else:
if id not in self.chats["chats"][self.chats["selected_chat"]]["messages"]: if not self.chats["chats"][self.chats["selected_chat"]]["messages"][id]["content"] and self.loading_spinner:
GLib.idle_add(self.chat_container.remove, self.loading_spinner) GLib.idle_add(self.chat_container.remove, self.loading_spinner)
self.loading_spinner = None self.loading_spinner = None
self.chats["chats"][self.chats["selected_chat"]]["messages"][id] = {
"role": "assistant",
"model": data['model'],
"date": datetime.now().strftime("%Y/%m/%d %H:%M:%S"),
"content": ''
}
GLib.idle_add(self.bot_message.insert, self.bot_message.get_end_iter(), data['message']['content']) GLib.idle_add(self.bot_message.insert, self.bot_message.get_end_iter(), data['message']['content'])
self.chats["chats"][self.chats["selected_chat"]]["messages"][id]['content'] += data['message']['content'] self.chats["chats"][self.chats["selected_chat"]]["messages"][id]['content'] += data['message']['content']
@@ -968,17 +979,69 @@ Generate a title following these rules:
def run_message(self, messages, model, id): def run_message(self, messages, model, id):
logger.debug("Running message") logger.debug("Running message")
self.bot_message_button_container.set_visible(False) self.bot_message_button_container.set_visible(False)
response = connection_handler.stream_post(f"{connection_handler.url}/api/chat", data=json.dumps({"model": model, "messages": messages}), callback=lambda data, id=id: self.update_bot_message(data, id)) self.chats["chats"][self.chats["selected_chat"]]["messages"][id] = {
GLib.idle_add(self.add_code_blocks) "role": "assistant",
GLib.idle_add(self.switch_send_stop_button) "model": model,
GLib.idle_add(self.toggle_ui_sensitive, True) "date": datetime.now().strftime("%Y/%m/%d %H:%M:%S"),
if self.loading_spinner: "content": ''
GLib.idle_add(self.chat_container.remove, self.loading_spinner) }
self.loading_spinner = None if self.regenerate_button:
if response.status_code != 200: GLib.idle_add(self.chat_container.remove, self.regenerate_button)
try:
response = connection_handler.stream_post(f"{connection_handler.url}/api/chat", data=json.dumps({"model": model, "messages": messages}), callback=lambda data, id=id: self.update_bot_message(data, id))
if response.status_code != 200: raise Exception('Network Error')
GLib.idle_add(self.add_code_blocks)
except Exception as e:
GLib.idle_add(self.connection_error) GLib.idle_add(self.connection_error)
self.regenerate_button = Gtk.Button(
child=Adw.ButtonContent(
icon_name='update-symbolic',
label=_('Regenerate Response')
),
css_classes=["suggested-action"],
halign=3
)
GLib.idle_add(self.chat_container.append, self.regenerate_button)
self.regenerate_button.connect('clicked', lambda button, id=id, bot_message_box=self.bot_message_box, bot_message_button_container=self.bot_message_button_container : self.regenerate_message(id, bot_message_box, bot_message_button_container))
finally:
GLib.idle_add(self.switch_send_stop_button)
GLib.idle_add(self.toggle_ui_sensitive, True)
if self.loading_spinner:
GLib.idle_add(self.chat_container.remove, self.loading_spinner)
self.loading_spinner = None
def regenerate_message(self, id, bot_message_box, bot_message_button_container):
self.bot_message_button_container = bot_message_button_container
self.bot_message_view = Gtk.TextView(
editable=False,
focusable=True,
wrap_mode= Gtk.WrapMode.WORD,
margin_top=12,
margin_bottom=12,
hexpand=True,
css_classes=["flat"]
)
self.bot_message = self.bot_message_view.get_buffer()
for widget in list(bot_message_box): bot_message_box.remove(widget)
bot_message_box.append(self.bot_message_view)
history = self.convert_history_to_ollama()[:list(self.chats["chats"][self.chats["selected_chat"]]["messages"].keys()).index(id)]
if id in self.chats["chats"][self.chats["selected_chat"]]["messages"]:
del self.chats["chats"][self.chats["selected_chat"]]["messages"][id]
data = {
"model": self.convert_model_name(self.model_drop_down.get_selected_item().get_string(), 1),
"messages": history,
"options": {"temperature": self.model_tweaks["temperature"], "seed": self.model_tweaks["seed"]},
"keep_alive": f"{self.model_tweaks['keep_alive']}m"
}
self.switch_send_stop_button()
self.toggle_ui_sensitive(False)
thread = threading.Thread(target=self.run_message, args=(data['messages'], data['model'], id))
thread.start()
def pull_model_update(self, data, model_name): def pull_model_update(self, data, model_name):
if 'error' in data:
self.pulling_models[model_name]['error'] = data['error']
return
if model_name in list(self.pulling_models.keys()): if model_name in list(self.pulling_models.keys()):
if 'completed' in data and 'total' in data: if 'completed' in data and 'total' in data:
GLib.idle_add(self.pulling_models[model_name]['row'].set_subtitle, '<tt>{}%</tt>'.format(round(data['completed'] / data['total'] * 100, 2))) GLib.idle_add(self.pulling_models[model_name]['row'].set_subtitle, '<tt>{}%</tt>'.format(round(data['completed'] / data['total'] * 100, 2)))
@@ -999,28 +1062,31 @@ Generate a title following these rules:
response = connection_handler.stream_post(f"{connection_handler.url}/api/pull", data=json.dumps(data), callback=lambda data, model_name=model: self.pull_model_update(data, model_name)) response = connection_handler.stream_post(f"{connection_handler.url}/api/pull", data=json.dumps(data), callback=lambda data, model_name=model: self.pull_model_update(data, model_name))
GLib.idle_add(self.update_list_local_models) GLib.idle_add(self.update_list_local_models)
if response.status_code == 200: if response.status_code == 200 and 'error' not in self.pulling_models[model]:
GLib.idle_add(self.show_notification, _("Task Complete"), _("Model '{}' pulled successfully.").format(model), Gio.ThemedIcon.new("emblem-ok-symbolic")) GLib.idle_add(self.show_notification, _("Task Complete"), _("Model '{}' pulled successfully.").format(model), Gio.ThemedIcon.new("emblem-ok-symbolic"))
GLib.idle_add(self.show_toast, _("Model '{}' pulled successfully.").format(model), self.manage_models_overlay) GLib.idle_add(self.show_toast, _("Model '{}' pulled successfully.").format(model), self.manage_models_overlay)
GLib.idle_add(self.pulling_models[model]['overlay'].get_parent().get_parent().remove, self.pulling_models[model]['overlay'].get_parent()) elif response.status_code == 200 and self.pulling_models[model]['error']:
del self.pulling_models[model] GLib.idle_add(self.show_notification, _("Pull Model Error"), _("Failed to pull model '{}': {}").format(model, self.pulling_models[model]['error']), Gio.ThemedIcon.new("dialog-error-symbolic"))
GLib.idle_add(self.show_toast, _("Error pulling '{}': {}").format(model, self.pulling_models[model]['error']), self.manage_models_overlay)
else: else:
GLib.idle_add(self.show_notification, _("Pull Model Error"), _("Failed to pull model '{}' due to network error.").format(model), Gio.ThemedIcon.new("dialog-error-symbolic")) GLib.idle_add(self.show_notification, _("Pull Model Error"), _("Failed to pull model '{}' due to network error.").format(model), Gio.ThemedIcon.new("dialog-error-symbolic"))
GLib.idle_add(self.pulling_models[model]['overlay'].get_parent().get_parent().remove, self.pulling_models[model]['overlay'].get_parent()) GLib.idle_add(self.show_toast, _("Error pulling '{}'").format(model), self.manage_models_overlay)
del self.pulling_models[model]
GLib.idle_add(self.manage_models_dialog.close) GLib.idle_add(self.manage_models_dialog.close)
GLib.idle_add(self.connection_error) GLib.idle_add(self.connection_error)
GLib.idle_add(self.pulling_models[model]['overlay'].get_parent().get_parent().remove, self.pulling_models[model]['overlay'].get_parent())
del self.pulling_models[model]
if len(list(self.pulling_models.keys())) == 0: if len(list(self.pulling_models.keys())) == 0:
GLib.idle_add(self.pulling_model_list_box.set_visible, False) GLib.idle_add(self.pulling_model_list_box.set_visible, False)
def pull_model(self, model): def pull_model(self, model):
if model in list(self.pulling_models.keys()) or model in self.local_models or ":" not in model: return
logger.info("Pulling model") logger.info("Pulling model")
if model in list(self.pulling_models.keys()) or model in self.local_models:
return
self.pulling_model_list_box.set_visible(True) self.pulling_model_list_box.set_visible(True)
#self.pulling_model_list_box.connect('row_selected', lambda list_box, row: dialogs.stop_pull_model(self, row.get_name()) if row else None) #It isn't working for some reason #self.pulling_model_list_box.connect('row_selected', lambda list_box, row: dialogs.stop_pull_model(self, row.get_name()) if row else None) #It isn't working for some reason
model_name = self.convert_model_name(model, 0)
model_row = Adw.ActionRow( model_row = Adw.ActionRow(
title = "<b>{}</b> <small>{}</small>".format(model.split(":")[0].replace("-", " ").title(), model.split(":")[1]), title = "<b>{}</b> <small>{}</small>".format(model_name.split(" (")[0], model_name.split(" (")[1][:-1]),
name = model name = model
) )
thread = threading.Thread(target=self.pull_model_process, kwargs={"model": model, "modelfile": None}) thread = threading.Thread(target=self.pull_model_process, kwargs={"model": model, "modelfile": None})
@@ -1037,7 +1103,7 @@ Generate a title following these rules:
vexpand = False, vexpand = False,
valign = 3, valign = 3,
css_classes = ["error", "circular"], css_classes = ["error", "circular"],
tooltip_text = _("Stop Pulling '{} ({})'").format(model.split(':')[0].replace('-', ' ').title(), model.split(':')[1]) tooltip_text = _("Stop Pulling '{}'").format(model_name)
) )
button.connect("clicked", lambda button, model_name=model : dialogs.stop_pull_model(self, model_name)) button.connect("clicked", lambda button, model_name=model : dialogs.stop_pull_model(self, model_name))
model_row.add_suffix(button) model_row.add_suffix(button)
@@ -1056,7 +1122,7 @@ Generate a title following these rules:
def list_available_model_tags(self, model_name): def list_available_model_tags(self, model_name):
logger.debug("Listing available model tags") logger.debug("Listing available model tags")
self.navigation_view_manage_models.push_by_tag('model_tags_page') self.navigation_view_manage_models.push_by_tag('model_tags_page')
self.navigation_view_manage_models.find_page('model_tags_page').set_title(model_name.capitalize()) self.navigation_view_manage_models.find_page('model_tags_page').set_title(model_name.replace("-", " ").title())
self.model_link_button.set_name(self.available_models[model_name]['url']) self.model_link_button.set_name(self.available_models[model_name]['url'])
self.model_link_button.set_tooltip_text(self.available_models[model_name]['url']) self.model_link_button.set_tooltip_text(self.available_models[model_name]['url'])
self.available_model_list_box.unselect_all() self.available_model_list_box.unselect_all()
@@ -1105,7 +1171,7 @@ Generate a title following these rules:
if message['role'] == 'user': if message['role'] == 'user':
self.show_message(message['content'], False, f"\n\n<small>{formated_date}</small>", message['images'] if 'images' in message else None, message['files'] if 'files' in message else None, id=key) self.show_message(message['content'], False, f"\n\n<small>{formated_date}</small>", message['images'] if 'images' in message else None, message['files'] if 'files' in message else None, id=key)
else: else:
self.show_message(message['content'], True, f"\n\n{message['model'].split(':')[0].replace('-', ' ').title()} ({message['model'].split(':')[1]})\n<small>{formated_date}</small>", id=key) self.show_message(message['content'], True, f"\n\n{self.convert_model_name(message['model'], 0)}\n<small>{formated_date}</small>", id=key)
self.add_code_blocks() self.add_code_blocks()
self.bot_message = None self.bot_message = None
@@ -1121,6 +1187,13 @@ Generate a title following these rules:
self.chats["order"] = [] self.chats["order"] = []
for chat_name in self.chats["chats"].keys(): for chat_name in self.chats["chats"].keys():
self.chats["order"].append(chat_name) self.chats["order"].append(chat_name)
if len(self.chats["chats"][self.chats["selected_chat"]]["messages"].keys()) > 0:
last_model_used = self.chats["chats"][self.chats["selected_chat"]]["messages"][list(self.chats["chats"][self.chats["selected_chat"]]["messages"].keys())[-1]]["model"]
last_model_used = self.convert_model_name(last_model_used, 0)
for i in range(self.model_string_list.get_n_items()):
if self.model_string_list.get_string(i) == last_model_used:
self.model_drop_down.set_selected(i)
break
except Exception as e: except Exception as e:
logger.error(e) logger.error(e)
self.chats = {"chats": {}, "selected_chat": None, "order": []} self.chats = {"chats": {}, "selected_chat": None, "order": []}
@@ -1541,6 +1614,7 @@ Generate a title following these rules:
self.get_application().create_action('import_chat', lambda *_: self.import_chat(), ['<primary>i']) self.get_application().create_action('import_chat', lambda *_: self.import_chat(), ['<primary>i'])
self.get_application().create_action('create_model_from_existing', lambda *_: dialogs.create_model_from_existing(self)) self.get_application().create_action('create_model_from_existing', lambda *_: dialogs.create_model_from_existing(self))
self.get_application().create_action('create_model_from_file', lambda *_: dialogs.create_model_from_file(self)) self.get_application().create_action('create_model_from_file', lambda *_: dialogs.create_model_from_file(self))
self.get_application().create_action('create_model_from_name', lambda *_: dialogs.create_model_from_name(self))
self.get_application().create_action('delete_chat', self.chat_actions) self.get_application().create_action('delete_chat', self.chat_actions)
self.get_application().create_action('rename_chat', self.chat_actions) self.get_application().create_action('rename_chat', self.chat_actions)
self.get_application().create_action('rename_current_chat', self.current_chat_actions) self.get_application().create_action('rename_current_chat', self.current_chat_actions)
@@ -1580,7 +1654,7 @@ Generate a title following these rules:
#Support dialog #Support dialog
if 'show_support' not in data or data['show_support']: if 'show_support' not in data or data['show_support']:
if random.randint(0, 99) == 0: if random.randint(0, 49) == 0 or True:
dialogs.support(self) dialogs.support(self)
if 'show_support' in data: self.show_support = data['show_support'] if 'show_support' in data: self.show_support = data['show_support']
self.background_switch.set_active(self.run_on_background) self.background_switch.set_active(self.run_on_background)

View File

@@ -14,7 +14,6 @@
<object class="AdwBreakpoint"> <object class="AdwBreakpoint">
<condition>max-width: 800sp</condition> <condition>max-width: 800sp</condition>
<setter object="split_view_overlay" property="collapsed">true</setter> <setter object="split_view_overlay" property="collapsed">true</setter>
<setter object="show_sidebar_button" property="visible">true</setter>
</object> </object>
</child> </child>
<child> <child>
@@ -23,10 +22,8 @@
<object class="AdwBreakpoint"> <object class="AdwBreakpoint">
<condition>max-width: 500sp</condition> <condition>max-width: 500sp</condition>
<setter object="split_view_overlay" property="collapsed">true</setter> <setter object="split_view_overlay" property="collapsed">true</setter>
<setter object="show_sidebar_button" property="visible">true</setter>
<setter object="welcome_dialog" property="width-request">360</setter> <setter object="welcome_dialog" property="width-request">360</setter>
<setter object="manage_models_dialog" property="width-request">360</setter> <setter object="manage_models_dialog" property="width-request">360</setter>
<setter object="create_model_dialog" property="width-request">360</setter>
<setter object="preferences_dialog" property="width-request">360</setter> <setter object="preferences_dialog" property="width-request">360</setter>
<setter object="file_preview_dialog" property="width-request">360</setter> <setter object="file_preview_dialog" property="width-request">360</setter>
</object> </object>
@@ -80,7 +77,6 @@
<object class="AdwHeaderBar" id="header_bar"> <object class="AdwHeaderBar" id="header_bar">
<child type="start"> <child type="start">
<object class="GtkToggleButton" id="show_sidebar_button"> <object class="GtkToggleButton" id="show_sidebar_button">
<property name="visible">false</property>
<property name="icon-name">sidebar-show-symbolic</property> <property name="icon-name">sidebar-show-symbolic</property>
<property name="tooltip-text" translatable="yes">Toggle Sidebar</property> <property name="tooltip-text" translatable="yes">Toggle Sidebar</property>
<property name="active" bind-source="split_view_overlay" bind-property="show-sidebar" bind-flags="sync-create"/> <property name="active" bind-source="split_view_overlay" bind-property="show-sidebar" bind-flags="sync-create"/>
@@ -93,7 +89,7 @@
<child> <child>
<object class="GtkDropDown" id="model_drop_down"> <object class="GtkDropDown" id="model_drop_down">
<signal name="notify" handler="verify_if_image_can_be_used"/> <signal name="notify" handler="verify_if_image_can_be_used"/>
<property name="width-request">150</property> <property name="width-request">175</property>
<property name="enable-search">true</property> <property name="enable-search">true</property>
<property name="tooltip-text">Select Model</property> <property name="tooltip-text">Select Model</property>
<property name="model"> <property name="model">
@@ -217,6 +213,8 @@
<object class="GtkScrolledWindow"> <object class="GtkScrolledWindow">
<property name="max-content-height">150</property> <property name="max-content-height">150</property>
<property name="propagate-natural-height">true</property> <property name="propagate-natural-height">true</property>
<property name="margin-start">10</property>
<property name="margin-end">10</property>
<style> <style>
<class name="message_input_scroll_window"/> <class name="message_input_scroll_window"/>
</style> </style>
@@ -226,8 +224,6 @@
<class name="message_text_view"/> <class name="message_text_view"/>
</style> </style>
<property name="wrap-mode">word</property> <property name="wrap-mode">word</property>
<property name="margin-start">10</property>
<property name="margin-end">10</property>
<property name="top-margin">10</property> <property name="top-margin">10</property>
<property name="bottom-margin">10</property> <property name="bottom-margin">10</property>
<property name="hexpand">true</property> <property name="hexpand">true</property>
@@ -246,6 +242,7 @@
<style> <style>
<class name="accent"/> <class name="accent"/>
<class name="circular"/> <class name="circular"/>
<class name="suggested-action"/>
</style> </style>
<child> <child>
<object class="AdwButtonContent"> <object class="AdwButtonContent">
@@ -456,127 +453,6 @@
</child> </child>
</object> </object>
<object class="AdwDialog" id="create_model_dialog">
<property name="can-close">true</property>
<property name="width-request">400</property>
<property name="height-request">600</property>
<child>
<object class="AdwToastOverlay" id="create_model_overlay">
<child>
<object class="AdwToolbarView">
<child type="bottom">
<object class="GtkActionBar">
<property name="revealed">true</property>
<child type="end">
<object class="GtkButton">
<property name="label" translatable="yes">Create</property>
<signal name="clicked" handler="create_model_start"/>
<style>
<class name="suggested-action"/>
</style>
</object>
</child>
</object>
</child>
<child type="top">
<object class="AdwHeaderBar">
<property name="title-widget">
<object class="AdwWindowTitle">
<property name="title" translatable="yes">Create Model</property>
</object>
</property>
</object>
</child>
<child>
<object class="GtkScrolledWindow">
<property name="vexpand">true</property>
<property name="hexpand">true</property>
<child>
<object class="GtkBox">
<property name="margin-start">12</property>
<property name="margin-end">12</property>
<property name="margin-top">12</property>
<property name="margin-bottom">12</property>
<property name="orientation">1</property>
<property name="spacing">12</property>
<child>
<object class="GtkListBox">
<style>
<class name="boxed-list"/>
<class name="card"/>
</style>
<property name="selection-mode">none</property>
<child>
<object class="AdwActionRow" id="create_model_base">
<property name="title" translatable="yes">Base</property>
<property name="subtitle"/>
<style>
<class name="property"/>
</style>
</object>
</child>
</object>
</child>
<child>
<object class="GtkListBox">
<style>
<class name="boxed-list"/>
<class name="card"/>
</style>
<property name="selection-mode">none</property>
<child>
<object class="AdwEntryRow" id="create_model_name">
<property name="title" translatable="yes">Name</property>
<property name="input-purpose">alpha</property>
</object>
</child>
<child>
<object class="AdwEntryRow" id="create_model_system">
<property name="title" translatable="yes">Context</property>
<property name="input-purpose">alpha</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkListBox">
<style>
<class name="boxed-list"/>
<class name="card"/>
</style>
<property name="selection-mode">none</property>
<child>
<object class="AdwEntryRow" id="create_model_template">
<property name="title" translatable="yes">Template</property>
<property name="input-purpose">alpha</property>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="label" translatable="yes">Some models require a specific template. Please visit the model's website for more information if you're unsure.</property>
<property name="margin-start">12</property>
<property name="margin-end">12</property>
<property name="margin-top">12</property>
<property name="margin-bottom">12</property>
<property name="halign">1</property>
<property name="wrap">true</property>
<style>
<class name="caption"/>
</style>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
<object class="AdwDialog" id="manage_models_dialog"> <object class="AdwDialog" id="manage_models_dialog">
<property name="can-close">true</property> <property name="can-close">true</property>
<property name="width-request">400</property> <property name="width-request">400</property>
@@ -730,6 +606,143 @@
</property> </property>
</object> </object>
</child> </child>
<child>
<object class="AdwNavigationPage">
<property name="title" translatable="yes">Create Model</property>
<property name="tag">model_create_page</property>
<property name="child">
<object class="AdwToolbarView">
<child type="top">
<object class="AdwHeaderBar">
<child type="start">
<object class="GtkButton">
<signal name="clicked" handler="link_button_handler"/>
<property name="icon-name">globe-symbolic</property>
</object>
</child>
</object>
</child>
<property name="content">
<object class="GtkScrolledWindow">
<property name="vexpand">true</property>
<property name="hexpand">true</property>
<child>
<object class="GtkBox">
<property name="margin-start">12</property>
<property name="margin-end">12</property>
<property name="margin-top">12</property>
<property name="margin-bottom">12</property>
<property name="orientation">1</property>
<property name="spacing">12</property>
<child>
<object class="GtkListBox">
<style>
<class name="boxed-list"/>
<class name="card"/>
</style>
<property name="selection-mode">none</property>
<child>
<object class="AdwActionRow" id="create_model_base">
<property name="title" translatable="yes">Base</property>
<property name="sensitive">false</property>
<property name="subtitle"/>
<style>
<class name="property"/>
</style>
</object>
</child>
</object>
</child>
<child>
<object class="GtkListBox">
<style>
<class name="boxed-list"/>
<class name="card"/>
</style>
<property name="selection-mode">none</property>
<child>
<object class="AdwEntryRow" id="create_model_name">
<property name="title" translatable="yes">Name</property>
<property name="input-purpose">alpha</property>
</object>
</child>
<child>
<object class="AdwEntryRow" id="create_model_system">
<property name="title" translatable="yes">Context</property>
<property name="input-purpose">alpha</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkListBox">
<style>
<class name="boxed-list"/>
<class name="card"/>
</style>
<property name="selection-mode">none</property>
<child>
<object class="GtkBox">
<property name="height-request">140</property>
<property name="margin-top">10</property>
<property name="margin-bottom">10</property>
<property name="margin-start">10</property>
<property name="margin-end">10</property>
<style>
<class name="card"/>
</style>
<child>
<object class="GtkScrolledWindow">
<property name="margin-start">10</property>
<property name="margin-end">10</property>
<child>
<object class="GtkTextView" id="create_model_modelfile">
<style>
<class name="modelfile_textview"/>
</style>
<property name="wrap-mode">word</property>
<property name="top-margin">10</property>
<property name="bottom-margin">10</property>
<property name="hexpand">true</property>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="label" translatable="yes">Some models require a modelfile, Alpaca fills FROM and SYSTEM (context) instructions automatically. Please visit the model's website or Ollama documentation for more information if you're unsure.</property>
<property name="margin-top">10</property>
<property name="margin-bottom">10</property>
<property name="margin-start">10</property>
<property name="margin-end">10</property>
<property name="halign">1</property>
<property name="wrap">true</property>
<style>
<class name="caption"/>
</style>
</object>
</child>
</object>
</child>
<child>
<object class="GtkButton">
<property name="label" translatable="yes">Create</property>
<signal name="clicked" handler="create_model_start"/>
<style>
<class name="suggested-action"/>
</style>
</object>
</child>
</object>
</child>
</object>
</property>
</object>
</property>
</object>
</child>
</object> </object>
</child> </child>
</object> </object>
@@ -1085,9 +1098,13 @@ By downloading any model you accept their license agreement available on the mod
<attribute name="action">app.create_model_from_existing</attribute> <attribute name="action">app.create_model_from_existing</attribute>
</item> </item>
<item> <item>
<attribute name="label" translatable="yes">From GGUF File (Experimental)</attribute> <attribute name="label" translatable="yes">From GGUF File</attribute>
<attribute name="action">app.create_model_from_file</attribute> <attribute name="action">app.create_model_from_file</attribute>
</item> </item>
<item>
<attribute name="label" translatable="yes">From Name</attribute>
<attribute name="action">app.create_model_from_name</attribute>
</item>
</section> </section>
</menu> </menu>
<object class="GtkFileFilter" id="file_filter_attachments"> <object class="GtkFileFilter" id="file_filter_attachments">