Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a4d26b2bda | ||
|
|
d7961f2510 | ||
|
|
45f5214ec9 | ||
|
|
5e1e770aee | ||
|
|
5eb18d00d3 | ||
|
|
bcbfd44e1f | ||
|
|
073c619d89 | ||
|
|
71ab8cfba4 | ||
|
|
0ec812099c | ||
|
|
f7f05a0538 | ||
|
|
82a0ab0d9e | ||
|
|
60b24da482 | ||
|
|
66d209e4c6 | ||
|
|
0c9ab4e17e | ||
|
|
fff3a68b29 | ||
|
|
580a104894 | ||
|
|
3fe9b6e121 | ||
|
|
6979d1a775 | ||
|
|
90385ce461 | ||
|
|
c579e6ec99 | ||
|
|
b8d93cfd17 | ||
|
|
359f28f5c1 | ||
|
|
6e8bff8493 | ||
|
|
ad25d5a50f |
@@ -15,6 +15,7 @@ Alpaca is an [Ollama](https://github.com/ollama/ollama) client where you can man
|
||||
- Talk to multiple models in the same conversation
|
||||
- Pull and delete models from the app
|
||||
- Image recognition
|
||||
- Document recognition (plain text files)
|
||||
- Code highlighting
|
||||
- Multiple conversations
|
||||
- Notifications
|
||||
@@ -22,13 +23,12 @@ Alpaca is an [Ollama](https://github.com/ollama/ollama) client where you can man
|
||||
- Delete messages
|
||||
|
||||
## Future features!
|
||||
- Document recognition (Probably only Markdown because it's really easy for AI to read)
|
||||
- YouTube recognition (Ask questions about a YouTube video using the transcript)
|
||||
- Edit messages
|
||||
- Snap Package (maybe)
|
||||
|
||||
## Screenies
|
||||
Login to Ollama instance | Chatting with models | Managing models
|
||||
Code highlighting | Chatting with models | Managing models
|
||||
:-------------------------:|:-------------------------:|:-------------------------:
|
||||
 |  | 
|
||||
|
||||
@@ -50,11 +50,14 @@ For now Alpaca doesn't offer a way to do this from the GUI but it's really simpl
|
||||
Go to `~/.var/app/com.jeffser.Alpaca/config/server.json` and change the `"local_port"` value, by default it is `11435`.
|
||||
|
||||
### Backup all the chats
|
||||
The chat data is located in `~/.var/app/com.jeffser.Alpaca/config/chats.json` you can copy that file wherever you want to.
|
||||
The chat data is located in `~/.var/app/com.jeffser.Alpaca/data/chats` you can copy that directory wherever you want to.
|
||||
|
||||
### Force showing the welcome dialog
|
||||
To do that you just need to delete the file `~/.var/app/com.jeffser.Alpaca/config/server.json`, this won't affect your saved chats or models.
|
||||
|
||||
### Add/Change environment variables for Ollama
|
||||
You can change anything except `$HOME` and `$OLLAMA_HOST`, to do this go to `~/.var/app/com.jeffser.Alpaca/config/server.json` and change `ollama_overrides` accordingly, some overrides are available to change on the GUI.
|
||||
|
||||
---
|
||||
|
||||
## Thanks
|
||||
|
||||
@@ -80,16 +80,16 @@
|
||||
"sources": [
|
||||
{
|
||||
"type": "file",
|
||||
"url": "https://github.com/ollama/ollama/releases/download/v0.1.38/ollama-linux-amd64",
|
||||
"sha256": "c3360812503a9756a507ebb9d78667e2b21800a760b45046bc48a8f3b81972f4",
|
||||
"url": "https://github.com/ollama/ollama/releases/download/v0.1.39/ollama-linux-amd64",
|
||||
"sha256": "4d19be355842a6297c93ab5ada139fe396126736bf3c3882a879dc245dffe3af",
|
||||
"only-arches": [
|
||||
"x86_64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"url": "https://github.com/ollama/ollama/releases/download/v0.1.38/ollama-linux-arm64",
|
||||
"sha256": "f2d091afe665b2d5ba8b68e2473d36cdfaf80c61c7d2844a0a8f533c4e62f547",
|
||||
"url": "https://github.com/ollama/ollama/releases/download/v0.1.39/ollama-linux-arm64",
|
||||
"sha256": "60ec2d36928c11d6c6d84fe91451308a46aafaedbdba44664e5a6080009ce097",
|
||||
"only-arches": [
|
||||
"aarch64"
|
||||
]
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
<li>Pull and delete models from the app</li>
|
||||
<li>Have multiple conversations</li>
|
||||
<li>Image recognition (Only available with compatible models)</li>
|
||||
<li>Plain text documents recognition</li>
|
||||
<li>Import and export chats</li>
|
||||
</ul>
|
||||
<p>Disclaimer</p>
|
||||
@@ -63,6 +64,30 @@
|
||||
<url type="homepage">https://github.com/Jeffser/Alpaca</url>
|
||||
<url type="donation">https://github.com/sponsors/Jeffser</url>
|
||||
<releases>
|
||||
<release version="0.9.4" date="2024-06-04">
|
||||
<url type="details">https://github.com/Jeffser/Alpaca/releases/tag/0.9.4</url>
|
||||
<description>
|
||||
<p>Huge Update</p>
|
||||
<ul>
|
||||
<li>Added: Support for plain text files</li>
|
||||
<li>Added: New backend system for storing messages</li>
|
||||
<li>Added: Support for changing Ollama's overrides</li>
|
||||
<li>General Optimization</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="0.9.3" date="2024-06-01">
|
||||
<url type="details">https://github.com/Jeffser/Alpaca/releases/tag/0.9.3</url>
|
||||
<description>
|
||||
<p>Big Update</p>
|
||||
<ul>
|
||||
<li>Added: Support for GGUF models (experimental)</li>
|
||||
<li>Added: Support for customization and creation of models</li>
|
||||
<li>Fixed: Icons don't appear on non Gnome systems</li>
|
||||
<li>Update Ollama to v0.1.39</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="0.9.2" date="2024-05-30">
|
||||
<url type="details">https://github.com/Jeffser/Alpaca/releases/tag/0.9.2</url>
|
||||
<description>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
project('Alpaca', 'c',
|
||||
version: '0.9.2',
|
||||
version: '0.9.4',
|
||||
meson_version: '>= 0.62.0',
|
||||
default_options: [ 'warning_level=2', 'werror=false', ],
|
||||
)
|
||||
|
||||
543
po/alpaca.pot
657
po/pt_BR.po
574
po/pt_BR.po~
@@ -6,7 +6,24 @@
|
||||
<file alias="icons/scalable/status/library-symbolic.svg">icons/library-symbolic.svg</file>
|
||||
<file alias="icons/scalable/status/paper-plane-symbolic.svg">icons/paper-plane-symbolic.svg</file>
|
||||
<file alias="icons/scalable/status/globe-symbolic.svg">icons/globe-symbolic.svg</file>
|
||||
<file alias="icons/scalable/status/controls-big-symbolic.svg">icons/controls-big-symbolic.svg</file>
|
||||
<file alias="icons/scalable/status/chat-message-new-symbolic.svg">icons/chat-message-new-symbolic.svg</file>
|
||||
<file alias="icons/scalable/status/dialog-warning-symbolic.svg">icons/dialog-warning-symbolic.svg</file>
|
||||
<file alias="icons/scalable/status/document-edit-symbolic.svg">icons/document-edit-symbolic.svg</file>
|
||||
<file alias="icons/scalable/status/edit-copy-symbolic.svg">icons/edit-copy-symbolic.svg</file>
|
||||
<file alias="icons/scalable/status/folder-download-symbolic.svg">icons/folder-download-symbolic.svg</file>
|
||||
<file alias="icons/scalable/status/image-x-generic-symbolic.svg">icons/image-x-generic-symbolic.svg</file>
|
||||
<file alias="icons/scalable/status/media-playback-stop-symbolic.svg">icons/media-playback-stop-symbolic.svg</file>
|
||||
<file alias="icons/scalable/status/open-menu-symbolicsvg">icons/open-menu-symbolic.svg</file>
|
||||
<file alias="icons/scalable/status/preferences-other-symbolic.svg">icons/preferences-other-symbolic.svg</file>
|
||||
<file alias="icons/scalable/status/preferences-system-symbolic.svg">icons/preferences-system-symbolic.svg</file>
|
||||
<file alias="icons/scalable/status/sidebar-show-symbolic.svg">icons/sidebar-show-symbolic.svg</file>
|
||||
<file alias="icons/scalable/status/user-trash-symbolic.svg">icons/user-trash-symbolic.svg</file>
|
||||
<file alias="icons/scalable/status/view-more-symbolic.svg">icons/view-more-symbolic.svg</file>
|
||||
<file alias="icons/scalable/status/document-open-symbolic.svg">icons/document-open-symbolic.svg</file>
|
||||
<file alias="icons/scalable/status/list-add-symbolic.svg">icons/list-add-symbolic.svg</file>
|
||||
<file alias="icons/scalable/status/brain-augemnted-symbolic.svg">icons/brain-augemnted-symbolic.svg</file>
|
||||
<file alias="icons/scalable/status/chain-link-loose-symbolic.svg">icons/chain-link-loose-symbolic.svg</file>
|
||||
<file alias="icons/scalable/status/document-text-symbolic.svg">icons/document-text-symbolic.svg</file>
|
||||
<file preprocess="xml-stripblanks">window.ui</file>
|
||||
<file preprocess="xml-stripblanks">gtk/help-overlay.ui</file>
|
||||
</gresource>
|
||||
|
||||
@@ -16,9 +16,15 @@ class SearchProvider:
|
||||
return
|
||||
|
||||
print("ALPACA D-Bus connection obtained")
|
||||
|
||||
interface_info = self.get_interface_info()
|
||||
self.registration_id = self.connection.register_object(DBUS_OBJECT_PATH, interface_info, self.handle_method_call, None)
|
||||
|
||||
self.registration_id = self.connection.register_object(
|
||||
DBUS_OBJECT_PATH,
|
||||
interface_info,
|
||||
self.handle_method_call,
|
||||
None,
|
||||
None
|
||||
)
|
||||
|
||||
if self.registration_id > 0:
|
||||
print(f"ALPACA Object registered with ID: {self.registration_id}")
|
||||
@@ -28,28 +34,38 @@ class SearchProvider:
|
||||
def get_interface_info(self):
|
||||
print("ALPACA get_interface_info")
|
||||
xml = """
|
||||
<node>
|
||||
<interface name='org.gnome.Shell.SearchProvider2'>
|
||||
<method name='GetInitialResultSet'>
|
||||
<arg type='as' name='terms' direction='in'/>
|
||||
<arg type='as' name='results' direction='out'/>
|
||||
</method>
|
||||
<method name='GetSubsearchResultSet'>
|
||||
<arg type='as' name='previous_results' direction='in'/>
|
||||
<arg type='as' name='terms' direction='in'/>
|
||||
<arg type='as' name='results' direction='out'/>
|
||||
</method>
|
||||
<method name='GetResultMetas'>
|
||||
<arg type='as' name='identifiers' direction='in'/>
|
||||
<arg type='a{sv}' name='metas' direction='out'/>
|
||||
</method>
|
||||
<method name='ActivateResult'>
|
||||
<arg type='s' name='identifier' direction='in'/>
|
||||
<arg type='as' name='terms' direction='in'/>
|
||||
<arg type='i' name='timestamp' direction='in'/>
|
||||
</method>
|
||||
</interface>
|
||||
</node>
|
||||
<node>
|
||||
<interface name="org.gnome.Shell.SearchProvider2">
|
||||
|
||||
<method name="GetInitialResultSet">
|
||||
<arg type="as" name="terms" direction="in" />
|
||||
<arg type="as" name="results" direction="out" />
|
||||
</method>
|
||||
|
||||
<method name="GetSubsearchResultSet">
|
||||
<arg type="as" name="previous_results" direction="in" />
|
||||
<arg type="as" name="terms" direction="in" />
|
||||
<arg type="as" name="results" direction="out" />
|
||||
</method>
|
||||
|
||||
<method name="GetResultMetas">
|
||||
<arg type="as" name="identifiers" direction="in" />
|
||||
<arg type="aa{sv}" name="metas" direction="out" />
|
||||
</method>
|
||||
|
||||
<method name="ActivateResult">
|
||||
<arg type="s" name="identifier" direction="in" />
|
||||
<arg type="as" name="terms" direction="in" />
|
||||
<arg type="u" name="timestamp" direction="in" />
|
||||
</method>
|
||||
|
||||
<method name="LaunchSearch">
|
||||
<arg type="as" name="terms" direction="in" />
|
||||
<arg type="u" name="timestamp" direction="in" />
|
||||
</method>
|
||||
|
||||
</interface>
|
||||
</node>
|
||||
"""
|
||||
return Gio.DBusNodeInfo.new_for_xml(xml).interfaces[0]
|
||||
|
||||
|
||||
@@ -13,6 +13,19 @@ def simple_get(connection_url:str) -> dict:
|
||||
except Exception as e:
|
||||
return {"status": "error", "status_code": 0}
|
||||
|
||||
def simple_post(connection_url:str, data) -> dict:
|
||||
try:
|
||||
headers = {
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
response = requests.post(connection_url, headers=headers, data=data, stream=False)
|
||||
if response.status_code == 200:
|
||||
return {"status": "ok", "text": response.text, "status_code": response.status_code}
|
||||
else:
|
||||
return {"status": "error", "status_code": response.status_code}
|
||||
except Exception as e:
|
||||
return {"status": "error", "status_code": 0}
|
||||
|
||||
def simple_delete(connection_url:str, data) -> dict:
|
||||
try:
|
||||
response = requests.delete(connection_url, json=data)
|
||||
@@ -38,4 +51,3 @@ def stream_post(connection_url:str, data, callback:callable) -> dict:
|
||||
return {"status": "error", "status_code": response.status_code}
|
||||
except Exception as e:
|
||||
return {"status": "error", "status_code": 0}
|
||||
|
||||
|
||||
@@ -177,14 +177,14 @@ def pull_model(self, model_name):
|
||||
|
||||
# REMOVE IMAGE | WORKS
|
||||
|
||||
def remove_image_response(self, dialog, task):
|
||||
def remove_attached_file_response(self, dialog, task, button):
|
||||
if dialog.choose_finish(task) == 'remove':
|
||||
self.remove_image()
|
||||
self.remove_attached_file(button)
|
||||
|
||||
def remove_image(self):
|
||||
def remove_attached_file(self, button):
|
||||
dialog = Adw.AlertDialog(
|
||||
heading=_("Remove Image"),
|
||||
body=_("Are you sure you want to remove image?"),
|
||||
heading=_("Remove File"),
|
||||
body=_("Are you sure you want to remove file?"),
|
||||
close_response="cancel"
|
||||
)
|
||||
dialog.add_response("cancel", _("Cancel"))
|
||||
@@ -193,10 +193,10 @@ def remove_image(self):
|
||||
dialog.choose(
|
||||
parent = self,
|
||||
cancellable = None,
|
||||
callback = lambda dialog, task: remove_image_response(self, dialog, task)
|
||||
callback = lambda dialog, task, button=button: remove_attached_file_response(self, dialog, task, button)
|
||||
)
|
||||
|
||||
# RECONNECT REMOTE |
|
||||
# RECONNECT REMOTE | WORKS
|
||||
|
||||
def reconnect_remote_response(self, dialog, task, entry):
|
||||
response = dialog.choose_finish(task)
|
||||
@@ -227,3 +227,58 @@ def reconnect_remote(self, current_url):
|
||||
cancellable = None,
|
||||
callback = lambda dialog, task, entry=entry: reconnect_remote_response(self, dialog, task, entry)
|
||||
)
|
||||
|
||||
# CREATE MODEL | WORKS
|
||||
|
||||
def create_model_from_existing_response(self, dialog, task, dropdown):
|
||||
model = dropdown.get_selected_item().get_string()
|
||||
if dialog.choose_finish(task) == 'accept' and model:
|
||||
self.create_model(model, False)
|
||||
|
||||
def create_model_from_existing(self):
|
||||
string_list = Gtk.StringList()
|
||||
for model in self.local_models:
|
||||
string_list.append(model)
|
||||
|
||||
dropdown = Gtk.DropDown()
|
||||
dropdown.set_model(string_list)
|
||||
dialog = Adw.AlertDialog(
|
||||
heading=_("Select Model"),
|
||||
body=_("This model will be used as the base for the new model"),
|
||||
extra_child=dropdown
|
||||
)
|
||||
dialog.add_response("cancel", _("Cancel"))
|
||||
dialog.add_response("accept", _("Accept"))
|
||||
dialog.set_response_appearance("accept", Adw.ResponseAppearance.SUGGESTED)
|
||||
dialog.choose(
|
||||
parent = self,
|
||||
cancellable = None,
|
||||
callback = lambda dialog, task, dropdown=dropdown: create_model_from_existing_response(self, dialog, task, dropdown)
|
||||
)
|
||||
|
||||
def create_model_from_file_response(self, file_dialog, result):
|
||||
try: file = file_dialog.open_finish(result)
|
||||
except: return
|
||||
try:
|
||||
self.create_model(file.get_path(), True)
|
||||
except Exception as e:
|
||||
self.show_toast("error", 5, self.main_overlay) ##TODO NEW ERROR MESSAGE
|
||||
|
||||
def create_model_from_file(self):
|
||||
file_dialog = Gtk.FileDialog(default_filter=self.file_filter_gguf)
|
||||
file_dialog.open(self, None, lambda file_dialog, result: create_model_from_file_response(self, file_dialog, result))
|
||||
|
||||
# FILE CHOOSER | WORKS
|
||||
|
||||
def attach_file_response(self, file_dialog, result, file_type):
|
||||
try: file = file_dialog.open_finish(result)
|
||||
except: return
|
||||
self.attach_file(file.get_path(), file_type)
|
||||
|
||||
|
||||
def attach_file(self, filter, file_type):
|
||||
if file_type == 'image' and not self.verify_if_image_can_be_used():
|
||||
self.show_toast('error', 8, self.main_overlay)
|
||||
return
|
||||
file_dialog = Gtk.FileDialog(default_filter=filter)
|
||||
file_dialog.open(self, None, lambda file_dialog, result, file_type=file_type: attach_file_response(self, file_dialog, result, file_type))
|
||||
|
||||
2
src/icons/brain-augemnted-symbolic.svg
Normal 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 5.976562 2 c 0.546876 0 1 0.453125 1 1 v 10 c 0 0.546875 -0.453124 1 -1 1 h -0.976562 c -1.652344 0 -3 -1.347656 -3 -3 v -6 c 0 -1.652344 1.347656 -3 3 -3 z m -5.976562 3 v 6 c 0 2.765625 2.234375 5 5 5 h 0.976562 c 1.660157 0 3 -1.339844 3 -3 v -10 c 0 -1.660156 -1.339843 -3 -3 -3 h -0.976562 c -2.765625 0 -5 2.234375 -5 5 z m 0 0"/><path d="m 1.488281 8.996094 h 1.511719 c 1.101562 0 1.988281 -0.886719 1.988281 -1.984375 v -0.515625 c 0 -0.273438 -0.222656 -0.5 -0.5 -0.5 c -0.273437 0 -0.5 0.226562 -0.5 0.5 v 0.515625 c 0 0.542969 -0.445312 0.984375 -0.988281 0.984375 h -1.511719 c -0.273437 0 -0.5 0.226562 -0.5 0.5 c 0 0.277344 0.226563 0.5 0.5 0.5 z m 0 0"/><path d="m 7.5 9.992188 h -1.511719 c -1.101562 0 -1.988281 0.886718 -1.988281 1.984374 v 0.515626 c 0 0.273437 0.222656 0.5 0.5 0.5 s 0.5 -0.226563 0.5 -0.5 v -0.515626 c 0 -0.539062 0.445312 -0.984374 0.988281 -0.984374 h 1.511719 c 0.277344 0 0.5 -0.226563 0.5 -0.5 c 0 -0.277344 -0.222656 -0.5 -0.5 -0.5 z m 0 0"/><path d="m 11.015625 14 h -1.035156 c -0.546875 0 -1 -0.453125 -1 -1 v -10 c 0 -0.546875 0.453125 -1 1 -1 h 1.035156 v -2 h -1.035156 c -1.664063 0 -3 1.339844 -3 3 v 10 c 0 1.660156 1.335937 3 3 3 h 1.035156 z m 0 0"/><path d="m 10 5 h 2.242188 l 2.148437 -2.6875 l -0.78125 -0.625 l -2 2.5 l 0.390625 -0.1875 h -2 z m 0 0"/><path d="m 10 11 h 2 l -0.390625 -0.1875 l 2 2.5 l 0.78125 -0.625 l -2.148437 -2.6875 h -2.242188 z m 0 0"/><path d="m 14.488281 1.976562 c -0.265625 0 -0.488281 -0.21875 -0.488281 -0.488281 c 0 -0.265625 0.222656 -0.488281 0.488281 -0.488281 c 0.269531 0 0.488281 0.222656 0.488281 0.488281 c 0 0.269531 -0.21875 0.488281 -0.488281 0.488281 z m 0 -1.976562 c -0.824219 0 -1.488281 0.664062 -1.488281 1.488281 s 0.664062 1.488281 1.488281 1.488281 s 1.488281 -0.664062 1.488281 -1.488281 s -0.664062 -1.488281 -1.488281 -1.488281 z m 0 0"/><path d="m 14.488281 13.976562 c -0.265625 0 -0.488281 -0.21875 -0.488281 -0.488281 c 0 -0.265625 0.222656 -0.488281 0.488281 -0.488281 c 0.269531 0 0.488281 0.222656 0.488281 0.488281 c 0 0.269531 -0.21875 0.488281 -0.488281 0.488281 z m 0 -1.976562 c -0.824219 0 -1.488281 0.664062 -1.488281 1.488281 s 0.664062 1.488281 1.488281 1.488281 s 1.488281 -0.664062 1.488281 -1.488281 s -0.664062 -1.488281 -1.488281 -1.488281 z m 0 0"/><path d="m 14.488281 7.976562 c -0.265625 0 -0.488281 -0.21875 -0.488281 -0.488281 c 0 -0.265625 0.222656 -0.488281 0.488281 -0.488281 c 0.269531 0 0.488281 0.222656 0.488281 0.488281 c 0 0.269531 -0.21875 0.488281 -0.488281 0.488281 z m 0 -1.976562 c -0.824219 0 -1.488281 0.664062 -1.488281 1.488281 s 0.664062 1.488281 1.488281 1.488281 s 1.488281 -0.664062 1.488281 -1.488281 s -0.664062 -1.488281 -1.488281 -1.488281 z m 0 0"/></g><path d="m 10 7.53125 h 4" fill="none" stroke="#222222"/><path d="m 4.5 4 h 3 c 0.277344 0 0.5 0.222656 0.5 0.5 s -0.222656 0.5 -0.5 0.5 h -3 c -0.277344 0 -0.5 -0.222656 -0.5 -0.5 s 0.222656 -0.5 0.5 -0.5 z m 0 0" fill="#222222"/></svg>
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
2
src/icons/chain-link-loose-symbolic.svg
Normal 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 2.683594 9.777344 c -1.570313 -0.542969 -2.683594 -2.039063 -2.683594 -3.777344 c 0 -2.199219 1.800781 -4 4 -4 h 3 c 2.199219 0 4 1.800781 4 4 c 0 1.640625 -0.992188 3.070312 -2.421875 3.679688 l -0.785156 -1.839844 c 0.710937 -0.304688 1.207031 -1 1.207031 -1.839844 c 0 -1.125 -0.875 -2 -2 -2 h -3 c -1.125 0 -2 0.875 -2 2 c 0 0.890625 0.558594 1.621094 1.339844 1.890625 z m 0 0"/><path d="m 8 14 c -2.199219 0 -4 -1.800781 -4 -4 c 0 -1.621094 0.96875 -3.03125 2.367188 -3.65625 l 0.816406 1.828125 c -0.699219 0.3125 -1.183594 1 -1.183594 1.828125 c 0 1.125 0.875 2 2 2 h 3 c 1.125 0 2 -0.875 2 -2 c 0 -0.867188 -0.53125 -1.582031 -1.277344 -1.867188 l 0.714844 -1.867187 c 1.503906 0.574219 2.5625 2.039063 2.5625 3.734375 c 0 2.199219 -1.800781 4 -4 4 z m 0 0"/></g></svg>
|
||||
|
After Width: | Height: | Size: 934 B |
4
src/icons/chat-message-new-symbolic.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m 3 1 c -1.644531 0 -3 1.355469 -3 3 v 6 c 0 1.644531 1.355469 3 3 3 h 1 v 3 l 3 -3 v -1 c 0 -0.550781 -0.449219 -1 -1 -1 h -3 c -0.570312 0 -1 -0.429688 -1 -1 v -6 c 0 -0.554688 0.445312 -1 1 -1 h 10 c 0.554688 0 1 0.445312 1 1 v 4 c 0 0.550781 0.449219 1 1 1 s 1 -0.449219 1 -1 v -4 c 0 -1.644531 -1.355469 -3 -3 -3 z m 8 7 v 3 h -3 v 2 h 3 v 3 h 2 v -3 h 3 v -2 h -3 v -3 z m 0 0" fill="#2e3436"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 548 B |
@@ -1,2 +0,0 @@
|
||||
<?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 4.550781 1 c -1.9375 0 -3.5 1.5625 -3.5 3.5 s 1.5625 3.5 3.5 3.5 h 7 c 1.941407 0 3.5 -1.5625 3.5 -3.5 s -1.558593 -3.5 -3.5 -3.5 z m 7 1 c 1.386719 0 2.5 1.113281 2.5 2.5 c 0 1.382812 -1.113281 2.5 -2.5 2.5 c -1.382812 0 -2.5 -1.117188 -2.5 -2.5 c 0 -1.386719 1.117188 -2.5 2.5 -2.5 z m 0 0"/><path d="m 4.550781 9 c -1.9375 0 -3.5 1.5625 -3.5 3.5 s 1.5625 3.5 3.5 3.5 h 7 c 1.941407 0 3.5 -1.5625 3.5 -3.5 s -1.558593 -3.5 -3.5 -3.5 z m 0 1 c 1.386719 0 2.5 1.113281 2.5 2.5 c 0 1.382812 -1.113281 2.5 -2.5 2.5 c -1.382812 0 -2.5 -1.117188 -2.5 -2.5 c 0 -1.386719 1.117188 -2.5 2.5 -2.5 z m 0 0" fill-opacity="0.35"/></g></svg>
|
||||
|
Before Width: | Height: | Size: 785 B |
4
src/icons/dialog-warning-symbolic.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m 7.90625 0.09375 c -0.527344 -0.0273438 -1.039062 0.28125 -1.4375 0.96875 l -6.25 11.59375 c -0.535156 0.964844 0.046875 2.34375 1.09375 2.34375 h 13.15625 c 0.980469 0 1.902344 -1.160156 1.21875 -2.34375 l -6.3125 -11.53125 c -0.398438 -0.644531 -0.941406 -1.003906 -1.46875 -1.03125 z m 1.09375 3.90625 v 5 c 0.007812 0.527344 -0.472656 1 -1 1 s -1.007812 -0.472656 -1 -1 v -5 z m -1 7 c 0.550781 0 1 0.449219 1 1 s -0.449219 1 -1 1 s -1 -0.449219 -1 -1 s 0.449219 -1 1 -1 z m 0 0" fill="#2e3436"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 649 B |
4
src/icons/document-edit-symbolic.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m 12.277344 0.832031 c -0.578125 0.007813 -1.167969 0.230469 -1.691406 0.753907 l -9 9 c -0.375 0.375 -0.585938 0.882812 -0.585938 1.414062 v 3 h 3 c 0.53125 0 1.039062 -0.210938 1.414062 -0.585938 l 9 -9 c 1.789063 -1.789062 0.082032 -4.390624 -1.890624 -4.570312 c -0.082032 -0.011719 -0.164063 -0.011719 -0.246094 -0.011719 z m -1.777344 3.605469 l 1.0625 1.0625 l -7.0625 7.0625 l -1.0625 -1.0625 z m 0 0" fill="#2e3436"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 574 B |
7
src/icons/document-open-symbolic.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="#2e3436">
|
||||
<path d="m 3 1 c -1.644531 0 -3 1.355469 -3 3 v 8 c 0 1.644531 1.355469 3 3 3 h 8.882812 c 0.832032 0 1.578126 -0.402344 2.054688 -0.9375 c 0.472656 -0.53125 0.738281 -1.167969 0.910156 -1.800781 l 0.972656 -2.609375 c 0.390626 -1.449219 -0.09375 -2.652344 -0.820312 -3.167969 c -0.484375 -0.34375 -0.714844 -0.292969 -1 -0.324219 v -1.160156 c 0 -0.855469 -0.558594 -1.589844 -1.09375 -1.828125 c -0.53125 -0.238281 -1.011719 -0.167969 -1.011719 -0.167969 l 0.105469 -0.003906 h -3.585938 l -1.707031 -1.707031 c -0.1875 -0.1875 -0.441406 -0.292969 -0.707031 -0.292969 z m 0 2 h 2.585938 l 1.707031 1.707031 c 0.1875 0.1875 0.441406 0.292969 0.707031 0.292969 h 4 c 0.035156 0 0.070312 -0.003906 0.105469 -0.007812 c 0 0 0.019531 0.019531 -0.011719 0.003906 c -0.035156 -0.011719 -0.09375 -0.25 -0.09375 0.003906 v 2 c 0 0.550781 0.449219 1 1 1 c 1 0 1.046875 0.703125 0.886719 1.128906 l -0.972657 2.609375 c -0.117187 0.4375 -0.296874 0.800781 -0.472656 0.996094 c -0.175781 0.199219 -0.285156 0.265625 -0.558594 0.265625 h -8.882812 c -0.570312 0 -1 -0.429688 -1 -1 v -8 c 0 -0.570312 0.46875 -0.792969 1 -1 z m 0 0"/>
|
||||
<path d="m 7 6 l 0.042969 0.003906 c -0.914063 -0.042968 -1.75 0.390625 -2.195313 0.96875 c -0.710937 1.222656 -1.15625 2.277344 -1.800781 3.71875 c -0.171875 0.523438 0.117187 1.089844 0.640625 1.261719 c 0.527344 0.171875 1.09375 -0.117187 1.261719 -0.640625 c 0.488281 -1.011719 0.921875 -1.816406 1.339843 -2.808594 c 0.210938 -0.503906 0.703126 -0.492187 0.898438 -0.503906 h 5.8125 c 0.550781 0 1 -0.449219 1 -1 s -0.449219 -1 -1 -1 z m 0 0"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
2
src/icons/document-text-symbolic.svg
Normal 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 9 2 v 5 h 4 v -1 z m -4 2 v 1 h 3 v -1 z m 0 2 v 1 h 3 v -1 z m 0 2 v 1 h 6 v -1 z m 0 2 v 1 h 6 v -1 z m 0 2 v 1 h 6 v -1 z m 0 0"/><path d="m 2 13 c 0 1.660156 1.339844 3 3 3 h 6 c 1.660156 0 3 -1.339844 3 -3 v -6 c 0 -0.90625 -0.359375 -1.773438 -1 -2.414062 l -2.585938 -2.585938 c -0.640624 -0.640625 -1.507812 -1 -2.414062 -1 h -3 c -1.660156 0 -3 1.339844 -3 3 z m 3 -10 h 3 c 0.375 0 0.734375 0.148438 1 0.414062 l 2.585938 2.585938 c 0.265624 0.265625 0.414062 0.625 0.414062 1 v 6 c 0 0.546875 -0.453125 1 -1 1 h -6 c -0.546875 0 -1 -0.453125 -1 -1 v -9 c 0 -0.546875 0.453125 -1 1 -1 z m 0 0"/></g></svg>
|
||||
|
After Width: | Height: | Size: 771 B |
4
src/icons/edit-copy-symbolic.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m 0 3 c 0 -1.644531 1.355469 -3 3 -3 h 5 c 1.644531 0 3 1.355469 3 3 c 0 0.550781 -0.449219 1 -1 1 s -1 -0.449219 -1 -1 c 0 -0.570312 -0.429688 -1 -1 -1 h -5 c -0.570312 0 -1 0.429688 -1 1 v 5 c 0 0.570312 0.429688 1 1 1 c 0.550781 0 1 0.449219 1 1 s -0.449219 1 -1 1 c -1.644531 0 -3 -1.355469 -3 -3 z m 5 5 c 0 -1.644531 1.355469 -3 3 -3 h 5 c 1.644531 0 3 1.355469 3 3 v 5 c 0 1.644531 -1.355469 3 -3 3 h -5 c -1.644531 0 -3 -1.355469 -3 -3 z m 2 0 v 5 c 0 0.570312 0.429688 1 1 1 h 5 c 0.570312 0 1 -0.429688 1 -1 v -5 c 0 -0.570312 -0.429688 -1 -1 -1 h -5 c -0.570312 0 -1 0.429688 -1 1 z m 0 0" fill="#2e3436"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 765 B |
4
src/icons/folder-download-symbolic.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m 8 0 c -0.550781 0 -1 0.449219 -1 1 v 8.585938 l -2.292969 -2.292969 c -0.1875 -0.1875 -0.441406 -0.292969 -0.707031 -0.292969 s -0.519531 0.105469 -0.707031 0.292969 c -0.390625 0.390625 -0.390625 1.023437 0 1.414062 l 4 4 c 0.390625 0.390625 1.023437 0.390625 1.414062 0 l 4 -4 c 0.390625 -0.390625 0.390625 -1.023437 0 -1.414062 s -1.023437 -0.390625 -1.414062 0 l -2.292969 2.292969 v -8.585938 c 0 -0.550781 -0.449219 -1 -1 -1 z m -6 14 v 2 h 12 v -2 z m 0 0" fill="#2e3436"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 630 B |
7
src/icons/image-x-generic-symbolic.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="#2e3436">
|
||||
<path d="m 6 5 c -0.550781 0 -1 0.449219 -1 1 s 0.449219 1 1 1 s 1 -0.449219 1 -1 s -0.449219 -1 -1 -1 z m 3.5 3 l -2 2 l -1.5 -1 l -2 2 v 0.5 c 0 0.5 0.5 0.5 0.5 0.5 h 7 s 0.472656 -0.035156 0.5 -0.5 v -1 z m 0 0"/>
|
||||
<path d="m 4 1 c -1.644531 0 -3 1.355469 -3 3 v 8 c 0 1.644531 1.355469 3 3 3 h 8 c 1.644531 0 3 -1.355469 3 -3 v -8 c 0 -1.644531 -1.355469 -3 -3 -3 z m 0 2 h 8 c 0.570312 0 1 0.429688 1 1 v 8 c 0 0.570312 -0.429688 1 -1 1 h -8 c -0.570312 0 -1 -0.429688 -1 -1 v -8 c 0 -0.570312 0.429688 -1 1 -1 z m 0 0"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 708 B |
4
src/icons/list-add-symbolic.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m 7 1 v 6 h -6 v 2 h 6 v 6 h 2 v -6 h 6 v -2 h -6 v -6 z m 0 0" fill="#2e3436"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 228 B |
4
src/icons/media-playback-stop-symbolic.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m 3.5 2 h 9 c 0.828125 0 1.5 0.671875 1.5 1.5 v 9 c 0 0.828125 -0.671875 1.5 -1.5 1.5 h -9 c -0.828125 0 -1.5 -0.671875 -1.5 -1.5 v -9 c 0 -0.828125 0.671875 -1.5 1.5 -1.5 z m 0 0" fill="#2e3436"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 345 B |
8
src/icons/open-menu-symbolic.svg
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="#2e3436">
|
||||
<path d="m 1 2 h 14 v 2 h -14 z m 0 0"/>
|
||||
<path d="m 1 7 h 14 v 2 h -14 z m 0 0"/>
|
||||
<path d="m 1 12 h 14 v 2 h -14 z m 0 0"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 314 B |
4
src/icons/preferences-other-symbolic.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m 12.5 0.0234375 c -0.292969 -0.0039063 -0.59375 0.0195313 -0.882812 0.0937505 c -1.867188 0.488281 -2.984376 2.382812 -2.496094 4.234374 c 0.101562 0.386719 0.289062 0.742188 0.5 1.054688 c -0.109375 0.015625 -1.625 2.59375 -1.625 2.59375 l 3.410156 0.023438 l 0.800781 -1.117188 c 0.378907 0.03125 0.78125 0.023438 1.171875 -0.078125 c 1.863282 -0.484375 2.980469 -2.382813 2.496094 -4.234375 c -0.105469 -0.386719 -0.292969 -0.738281 -0.503906 -1.054688 l -1.359375 2.304688 c -0.28125 0.476562 -0.882813 0.632812 -1.355469 0.355469 l -0.875 -0.507813 c -0.476562 -0.277344 -0.636719 -0.875 -0.355469 -1.347656 l 1.363281 -2.3085938 c -0.097656 -0.0039062 -0.195312 -0.0117187 -0.289062 -0.0117187 z m -8.875 0.6914065 c -0.011719 0 -0.019531 0 -0.027344 0.003906 c -0.230468 0.046875 -0.445312 0.132812 -0.65625 0.21875 c -0.019531 0.625 0.085938 1.441406 -0.15625 1.65625 c -0.242187 0.210938 -1.039062 0.035156 -1.652344 -0.03125 c -0.132812 0.238281 -0.238281 0.484375 -0.3125 0.75 c 0.476563 0.394531 1.136719 0.839844 1.152344 1.15625 c 0.015625 0.320312 -0.625 0.804688 -1.058594 1.25 c 0.101563 0.257812 0.25 0.492188 0.40625 0.71875 c 0.605469 -0.125 1.359376 -0.375 1.621094 -0.1875 c 0.261719 0.1875 0.261719 1.003906 0.34375 1.625 c 0.246094 0.074219 0.507813 0.105469 0.777344 0.125 c 0.28125 -0.554688 0.566406 -1.320312 0.875 -1.40625 c 0.316406 -0.089844 0.960938 0.457031 1.5 0.78125 c 0.214844 -0.152344 0.410156 -0.335938 0.589844 -0.53125 c -0.253906 -0.574219 -0.71875 -1.292969 -0.589844 -1.59375 c 0.125 -0.300781 0.96875 -0.46875 1.558594 -0.6875 c 0.003906 -0.074219 0.03125 -0.144531 0.03125 -0.21875 c 0 -0.191406 -0.035156 -0.378906 -0.0625 -0.5625 c -0.605469 -0.160156 -1.464844 -0.242188 -1.621094 -0.53125 c -0.160156 -0.289062 0.238281 -1.0625 0.433594 -1.65625 c -0.199219 -0.179688 -0.417969 -0.335938 -0.652344 -0.46875 c -0.503906 0.375 -1.085938 0.996094 -1.40625 0.9375 c -0.316406 -0.054688 -0.660156 -0.820312 -0.996094 -1.34375 c -0.03125 0.003906 -0.066406 0 -0.097656 -0.003906 z m 0.59375 1.878906 c 0.929688 0 1.6875 0.753906 1.6875 1.6875 s -0.757812 1.6875 -1.6875 1.6875 s -1.683594 -0.753906 -1.683594 -1.6875 s 0.753906 -1.6875 1.683594 -1.6875 z m 8.621094 6.332031 c -0.144532 0 -0.273438 0.023438 -0.359375 0.074219 h -9.050781 c -0.246094 -0.105469 -0.746094 -0.046875 -0.964844 0.179688 l -2.160156 2.113281 c -0.191407 0.175781 -0.30078175 0.484375 -0.304688 0.707031 c -0.0546875 0.886719 1 1.320312 1.636719 0.792969 l 1.363281 -1.332031 l 0.003906 3.539062 c 0 0.570312 0.457032 1 0.996094 1 h 7.992188 c 0.523437 0 0.996093 -0.445312 0.996093 -0.9375 l -0.003906 -3.601562 l 1.335937 1.308593 c 0.636719 0.527344 1.6875 0.09375 1.636719 -0.792969 c -0.003906 -0.222656 -0.113281 -0.53125 -0.300781 -0.703124 l -2.164062 -2.117188 c -0.144532 -0.152344 -0.417969 -0.226562 -0.652344 -0.230469 z m 0 0" fill="#2e3436"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
4
src/icons/preferences-system-symbolic.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m 13.855469 0 l -1.539063 1.4375 c -0.453125 0.421875 -0.53125 1.148438 -0.269531 1.707031 l -5.886719 5.996094 c -0.011718 0 -0.019531 0 -0.03125 0 c -0.257812 -0.128906 -0.550781 -0.183594 -0.839844 -0.148437 c -0.328124 0.046874 -0.632812 0.199218 -0.867187 0.441406 l -3.945313 3.996094 c -0.3906245 0.375 -0.5468745 0.933593 -0.4062495 1.457031 c 0.1406255 0.523437 0.5546875 0.929687 1.0820315 1.058593 c 0.527344 0.132813 1.082031 -0.03125 1.453125 -0.425781 l 3.945312 -3.996093 c 0.472657 -0.453126 0.59375 -1.15625 0.296875 -1.738282 l 5.890625 -5.964844 c 0.558594 0.25 1.273438 0.148438 1.707031 -0.289062 l 1.414063 -1.5625 z m -10.308594 0.0898438 c -0.398437 0 -0.785156 0.0937502 -1.140625 0.2187502 l 1.882812 1.878906 c 0.390626 0.382812 0.390626 1 0 1.386719 l -0.710937 0.707031 c -0.386719 0.386719 -1 0.386719 -1.390625 0 l -1.882812 -1.878906 c -0.125 0.355468 -0.2187505 0.742187 -0.2187505 1.140625 c 0 1.90625 1.5507815 3.453125 3.4609375 3.453125 c 0.402344 0 0.789063 -0.09375 1.144531 -0.21875 l 1.175782 1.171875 h 0.058593 l 2.070313 -2.0625 l -1.203125 -1.203125 c 0.125 -0.359375 0.214843 -0.742188 0.214843 -1.140625 c 0 -1.90625 -1.546874 -3.4531252 -3.460937 -3.4531252 z m 6.550781 7.8906252 l -2.070312 2.066406 c 0.011718 0.027344 0.023437 0.058594 0.03125 0.089844 l 1.144531 1.140625 c -0.125 0.355468 -0.21875 0.742187 -0.21875 1.140625 c 0 1.902343 1.550781 3.449219 3.460937 3.449219 c 0.433594 0 0.855469 -0.101563 1.238282 -0.246094 l -2.007813 -2 c -0.386719 -0.386719 -0.386719 -1.035156 0 -1.417969 l 0.679688 -0.679687 c 0.195312 -0.191407 0.457031 -0.308594 0.710937 -0.308594 s 0.515625 0.117187 0.710938 0.308594 l 1.945312 1.941406 c 0.105469 -0.328125 0.183594 -0.683594 0.183594 -1.046875 c 0 -1.90625 -1.546875 -3.453125 -3.460938 -3.453125 c -0.398437 0 -0.785156 0.09375 -1.140624 0.21875 z m 0 0" fill="#2e3436"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
8
src/icons/sidebar-show-symbolic.svg
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="#2e3436">
|
||||
<path d="m 6.5 14 v -12 h -5 v 12 z m 0 0" fill-opacity="0.34902"/>
|
||||
<path d="m 3 1 c -1.644531 0 -3 1.355469 -3 3 v 8 c 0 1.644531 1.355469 3 3 3 h 10 c 1.644531 0 3 -1.355469 3 -3 v -8 c 0 -1.644531 -1.355469 -3 -3 -3 z m 0 2 h 10 c 0.570312 0 1 0.429688 1 1 v 8 c 0 0.570312 -0.429688 1 -1 1 h -10 c -0.570312 0 -1 -0.429688 -1 -1 v -8 c 0 -0.570312 0.429688 -1 1 -1 z m 0 0"/>
|
||||
<path d="m 6 2 h 1 v 12 h -1 z m 0 0"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 610 B |
10
src/icons/user-trash-symbolic.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="#2e3436">
|
||||
<path d="m 1 3 h 14 c 0.550781 0 1 0.449219 1 1 s -0.449219 1 -1 1 h -14 c -0.550781 0 -1 -0.449219 -1 -1 s 0.449219 -1 1 -1 z m 0 0"/>
|
||||
<path d="m 4 4 v -1.5 c 0 -1.386719 1.113281 -2.5 2.5 -2.5 h 2.980469 c 1.382812 0 2.5 1.113281 2.5 2.5 v 1.5 h -2 v -1.5 c 0 -0.269531 -0.230469 -0.5 -0.5 -0.5 h -2.980469 c -0.269531 0 -0.5 0.230469 -0.5 0.5 v 1.5 z m 0 0"/>
|
||||
<path d="m 4 4 v 9 c 0 0.546875 0.453125 1 1 1 h 6 c 0.546875 0 1 -0.453125 1 -1 v -9 h 2 v 9 c 0 1.660156 -1.339844 3 -3 3 h -6 c -1.660156 0 -3 -1.339844 -3 -3 v -9 z m 0 0"/>
|
||||
<path d="m 7 7 v 5 c 0 0.277344 -0.222656 0.5 -0.5 0.5 s -0.5 -0.222656 -0.5 -0.5 v -5 c 0 -0.277344 0.222656 -0.5 0.5 -0.5 s 0.5 0.222656 0.5 0.5 z m 0 0"/>
|
||||
<path d="m 10 7 v 5 c 0 0.277344 -0.222656 0.5 -0.5 0.5 s -0.5 -0.222656 -0.5 -0.5 v -5 c 0 -0.277344 0.222656 -0.5 0.5 -0.5 s 0.5 0.222656 0.5 0.5 z m 0 0"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
4
src/icons/view-more-symbolic.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m 7.996094 0 c -1.105469 0 -2 0.894531 -2 2 s 0.894531 2 2 2 c 1.101562 0 2 -0.894531 2 -2 s -0.898438 -2 -2 -2 z m 0 6 c -1.105469 0 -2 0.894531 -2 2 s 0.894531 2 2 2 c 1.101562 0 2 -0.894531 2 -2 s -0.898438 -2 -2 -2 z m 0 6 c -1.105469 0 -2 0.894531 -2 2 s 0.894531 2 2 2 c 1.101562 0 2 -0.894531 2 -2 s -0.898438 -2 -2 -2 z m 0 0" fill="#2e3436"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 499 B |
@@ -5,16 +5,25 @@ from time import sleep
|
||||
instance = None
|
||||
port = 11435
|
||||
data_dir = os.getenv("XDG_DATA_HOME")
|
||||
overrides = {}
|
||||
|
||||
def start():
|
||||
instance = subprocess.Popen(["/app/bin/ollama", "serve"], env={**os.environ, 'OLLAMA_HOST': f"127.0.0.1:{port}", "HOME": data_dir}, stderr=subprocess.PIPE, text=True)
|
||||
global instance, overrides
|
||||
params = overrides.copy()
|
||||
params["OLLAMA_HOST"] = f"127.0.0.1:{port}" # You can't change this directly sorry :3
|
||||
params["HOME"] = data_dir
|
||||
instance = subprocess.Popen(["/app/bin/ollama", "serve"], env={**os.environ, **params}, stderr=subprocess.PIPE, text=True)
|
||||
print("Starting Alpaca's Ollama instance...")
|
||||
sleep(1)
|
||||
print("Started Alpaca's Ollama instance")
|
||||
|
||||
def stop():
|
||||
if instance: instance.kill()
|
||||
print("Stopped Alpaca's Ollama instance")
|
||||
global instance
|
||||
if instance:
|
||||
instance.kill()
|
||||
instance.wait()
|
||||
instance = None
|
||||
print("Stopped Alpaca's Ollama instance")
|
||||
|
||||
def reset():
|
||||
stop()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# main.py
|
||||
#
|
||||
# Copyright 2024 Unknown
|
||||
# Copyright 2024 Jeffser
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@@ -47,7 +47,7 @@ class AlpacaApplication(Adw.Application):
|
||||
application_name='Alpaca',
|
||||
application_icon='com.jeffser.Alpaca',
|
||||
developer_name='Jeffry Samuel Eduarte Rojas',
|
||||
version='0.9.2',
|
||||
version='0.9.4',
|
||||
developers=['Jeffser https://jeffser.com'],
|
||||
designers=['Jeffser https://jeffser.com'],
|
||||
translator_credits='Alex K (Russian) https://github.com/alexkdeveloper\nJeffser (Spanish) https://jeffser.com\nDaimar Stein (Brazilian Portuguese) https://github.com/not-a-dev-stein\nLouis Chauvet-Villaret (French) https://github.com/loulou64490',
|
||||
|
||||
@@ -26,14 +26,14 @@ configure_file(
|
||||
install_mode: 'r-xr-xr-x'
|
||||
)
|
||||
|
||||
configure_file(
|
||||
input: 'alpaca_search_provider.in',
|
||||
output: 'alpaca_search_provider',
|
||||
configuration: conf,
|
||||
install: true,
|
||||
install_dir: get_option('bindir'),
|
||||
install_mode: 'r-xr-xr-x'
|
||||
)
|
||||
#configure_file(
|
||||
#input: 'alpaca_search_provider.in',
|
||||
#output: 'alpaca_search_provider',
|
||||
#configuration: conf,
|
||||
#install: true,
|
||||
#install_dir: get_option('bindir'),
|
||||
#install_mode: 'r-xr-xr-x'
|
||||
#)
|
||||
|
||||
alpaca_sources = [
|
||||
'__init__.py',
|
||||
@@ -42,7 +42,8 @@ alpaca_sources = [
|
||||
'connection_handler.py',
|
||||
'available_models.py',
|
||||
'dialogs.py',
|
||||
'local_instance.py'
|
||||
'local_instance.py',
|
||||
'update_history.py'
|
||||
]
|
||||
|
||||
install_data(alpaca_sources, install_dir: moduledir)
|
||||
|
||||
38
src/update_history.py
Normal file
@@ -0,0 +1,38 @@
|
||||
# update_history.py
|
||||
# This script updates the old chats.json file to the structure needed for the new version
|
||||
import os, json, base64
|
||||
from PIL import Image
|
||||
import io
|
||||
|
||||
|
||||
def update(self):
|
||||
old_data = None
|
||||
new_data = {"chats": {}}
|
||||
with open(os.path.join(self.config_dir, "chats.json"), 'r') as f:
|
||||
old_data = json.load(f)["chats"]
|
||||
for chat_name, content in old_data.items():
|
||||
directory = os.path.join(self.data_dir, "chats", chat_name)
|
||||
if not os.path.exists(directory): os.makedirs(directory)
|
||||
new_messages = {}
|
||||
for message in content['messages']:
|
||||
message_id = self.generate_uuid()
|
||||
if 'images' in message:
|
||||
if not os.path.exists(os.path.join(directory, message_id)): os.makedirs(os.path.join(directory, message_id))
|
||||
new_images = []
|
||||
for image in message['images']:
|
||||
file_name = f"{self.generate_uuid()}.png"
|
||||
decoded = base64.b64decode(image)
|
||||
buffer = io.BytesIO(decoded)
|
||||
im = Image.open(buffer)
|
||||
im.save(os.path.join(directory, message_id, file_name))
|
||||
new_images.append(file_name)
|
||||
message['images'] = new_images
|
||||
new_messages[message_id] = message
|
||||
new_data['chats'][chat_name] = {}
|
||||
new_data['chats'][chat_name]['messages'] = new_messages
|
||||
|
||||
with open(os.path.join(self.data_dir, "chats", "chats.json"), "w+") as f:
|
||||
json.dump(new_data, f, indent=6)
|
||||
|
||||
os.remove(os.path.join(self.config_dir, "chats.json"))
|
||||
|
||||
566
src/window.py
@@ -1,6 +1,6 @@
|
||||
# window.py
|
||||
#
|
||||
# Copyright 2024 Unknown
|
||||
# Copyright 2024 Jeffser
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@@ -21,18 +21,20 @@ import gi
|
||||
gi.require_version('GtkSource', '5')
|
||||
gi.require_version('GdkPixbuf', '2.0')
|
||||
from gi.repository import Adw, Gtk, Gdk, GLib, GtkSource, Gio, GdkPixbuf
|
||||
import json, requests, threading, os, re, base64, sys, gettext, locale, webbrowser, subprocess
|
||||
import json, requests, threading, os, re, base64, sys, gettext, locale, webbrowser, subprocess, uuid, shutil, tarfile, tempfile
|
||||
from time import sleep
|
||||
from io import BytesIO
|
||||
from PIL import Image
|
||||
from datetime import datetime
|
||||
from .available_models import available_models
|
||||
from . import dialogs, local_instance, connection_handler
|
||||
from . import dialogs, local_instance, connection_handler, update_history
|
||||
|
||||
@Gtk.Template(resource_path='/com/jeffser/Alpaca/window.ui')
|
||||
class AlpacaWindow(Adw.ApplicationWindow):
|
||||
config_dir = os.getenv("XDG_CONFIG_HOME")
|
||||
data_dir = os.getenv("XDG_DATA_HOME")
|
||||
app_dir = os.getenv("FLATPAK_DEST")
|
||||
cache_dir = os.getenv("XDG_CACHE_HOME")
|
||||
|
||||
__gtype_name__ = 'AlpacaWindow'
|
||||
|
||||
@@ -50,10 +52,20 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
||||
model_tweaks = {"temperature": 0.7, "seed": 0, "keep_alive": 5}
|
||||
local_models = []
|
||||
pulling_models = {}
|
||||
chats = {"chats": {_("New Chat"): {"messages": []}}, "selected_chat": "New Chat"}
|
||||
attached_image = {"path": None, "base64": None}
|
||||
chats = {"chats": {_("New Chat"): {"messages": {}}}, "selected_chat": "New Chat"}
|
||||
attachments = {}
|
||||
|
||||
#Override elements
|
||||
override_HSA_OVERRIDE_GFX_VERSION = Gtk.Template.Child()
|
||||
override_CUDA_VISIBLE_DEVICES = Gtk.Template.Child()
|
||||
override_HIP_VISIBLE_DEVICES = Gtk.Template.Child()
|
||||
|
||||
#Elements
|
||||
create_model_base = Gtk.Template.Child()
|
||||
create_model_name = Gtk.Template.Child()
|
||||
create_model_system = Gtk.Template.Child()
|
||||
create_model_template = Gtk.Template.Child()
|
||||
create_model_dialog = Gtk.Template.Child()
|
||||
temperature_spin = Gtk.Template.Child()
|
||||
seed_spin = Gtk.Template.Child()
|
||||
keep_alive_spin = Gtk.Template.Child()
|
||||
@@ -62,6 +74,8 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
||||
bot_message : Gtk.TextBuffer = None
|
||||
bot_message_box : Gtk.Box = None
|
||||
bot_message_view : Gtk.TextView = None
|
||||
file_preview_dialog = Gtk.Template.Child()
|
||||
file_preview_text_view = Gtk.Template.Child()
|
||||
welcome_dialog = Gtk.Template.Child()
|
||||
welcome_carousel = Gtk.Template.Child()
|
||||
welcome_previous_button = Gtk.Template.Child()
|
||||
@@ -73,9 +87,13 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
||||
message_text_view = Gtk.Template.Child()
|
||||
send_button = Gtk.Template.Child()
|
||||
stop_button = Gtk.Template.Child()
|
||||
image_button = Gtk.Template.Child()
|
||||
chats_menu_button = Gtk.Template.Child()
|
||||
attachment_container = Gtk.Template.Child()
|
||||
attachment_box = Gtk.Template.Child()
|
||||
file_filter_image = Gtk.Template.Child()
|
||||
file_filter_json = Gtk.Template.Child()
|
||||
file_filter_tar = Gtk.Template.Child()
|
||||
file_filter_gguf = Gtk.Template.Child()
|
||||
file_filter_text = Gtk.Template.Child()
|
||||
model_drop_down = Gtk.Template.Child()
|
||||
model_string_list = Gtk.Template.Child()
|
||||
|
||||
@@ -102,7 +120,8 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
||||
_("Could not pull model"),
|
||||
_("Cannot open image"),
|
||||
_("Cannot delete chat because it's the only one left"),
|
||||
_("There was an error with the local Ollama instance, so it has been reset")
|
||||
_("There was an error with the local Ollama instance, so it has been reset"),
|
||||
_("Image recognition is only available on specific models")
|
||||
],
|
||||
"info": [
|
||||
_("Please select a model before chatting"),
|
||||
@@ -127,22 +146,19 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
||||
if self.model_drop_down.get_selected_item() == None: return True
|
||||
selected = self.model_drop_down.get_selected_item().get_string().split(":")[0]
|
||||
if selected in ['llava', 'bakllava', 'moondream', 'llava-llama3']:
|
||||
self.image_button.set_sensitive(True)
|
||||
self.image_button.set_tooltip_text(_("Upload image"))
|
||||
for name, content in self.attachments.items():
|
||||
if content['type'] == 'image':
|
||||
content['button'].set_css_classes(["flat"])
|
||||
return True
|
||||
else:
|
||||
self.image_button.set_sensitive(False)
|
||||
self.image_button.set_tooltip_text(_("Only available on selected models"))
|
||||
self.image_button.set_css_classes(["circular"])
|
||||
self.attached_image = {"path": None, "base64": None}
|
||||
for name, content in self.attachments.items():
|
||||
if content['type'] == 'image':
|
||||
content['button'].set_css_classes(["flat", "error"])
|
||||
return False
|
||||
|
||||
@Gtk.Template.Callback()
|
||||
def stop_message(self, button=None):
|
||||
if self.loading_spinner: self.chat_container.remove(self.loading_spinner)
|
||||
if self.verify_if_image_can_be_used(): self.image_button.set_sensitive(True)
|
||||
self.image_button.set_css_classes(["circular"])
|
||||
self.attached_image = {"path": None, "base64": None}
|
||||
self.toggle_ui_sensitive(True)
|
||||
self.switch_send_stop_button()
|
||||
self.bot_message = None
|
||||
@@ -151,37 +167,59 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
||||
|
||||
@Gtk.Template.Callback()
|
||||
def send_message(self, button=None):
|
||||
if self.bot_message: return
|
||||
if not self.message_text_view.get_buffer().get_text(self.message_text_view.get_buffer().get_start_iter(), self.message_text_view.get_buffer().get_end_iter(), False): return
|
||||
current_model = self.model_drop_down.get_selected_item()
|
||||
if current_model is None:
|
||||
self.show_toast("info", 0, self.main_overlay)
|
||||
return
|
||||
id = self.generate_uuid()
|
||||
|
||||
attached_images = []
|
||||
attached_files = {}
|
||||
can_use_images = self.verify_if_image_can_be_used()
|
||||
for name, content in self.attachments.items():
|
||||
if content["type"] == 'image' and can_use_images: attached_images.append(name)
|
||||
else: attached_files[name] = content['type']
|
||||
if not os.path.exists(os.path.join(self.data_dir, "chats", self.chats['selected_chat'], id)):
|
||||
os.makedirs(os.path.join(self.data_dir, "chats", self.chats['selected_chat'], id))
|
||||
shutil.copy(content['path'], os.path.join(self.data_dir, "chats", self.chats['selected_chat'], id, name))
|
||||
content["button"].get_parent().remove(content["button"])
|
||||
self.attachments = {}
|
||||
self.attachment_box.set_visible(False)
|
||||
|
||||
#{"path": file_path, "type": file_type, "content": content}
|
||||
|
||||
formated_datetime = datetime.now().strftime("%Y/%m/%d %H:%M")
|
||||
self.chats["chats"][self.chats["selected_chat"]]["messages"].append({
|
||||
|
||||
self.chats["chats"][self.chats["selected_chat"]]["messages"][id] = {
|
||||
"role": "user",
|
||||
"model": "User",
|
||||
"date": formated_datetime,
|
||||
"content": self.message_text_view.get_buffer().get_text(self.message_text_view.get_buffer().get_start_iter(), self.message_text_view.get_buffer().get_end_iter(), False)
|
||||
})
|
||||
}
|
||||
if len(attached_images) > 0:
|
||||
self.chats["chats"][self.chats["selected_chat"]]["messages"][id]['images'] = attached_images
|
||||
if len(attached_files.keys()) > 0:
|
||||
self.chats["chats"][self.chats["selected_chat"]]["messages"][id]['files'] = attached_files
|
||||
data = {
|
||||
"model": current_model.get_string(),
|
||||
"messages": self.chats["chats"][self.chats["selected_chat"]]["messages"],
|
||||
"messages": self.convert_history_to_ollama(),
|
||||
"options": {"temperature": self.model_tweaks["temperature"], "seed": self.model_tweaks["seed"]},
|
||||
"keep_alive": f"{self.model_tweaks['keep_alive']}m"
|
||||
}
|
||||
if self.verify_if_image_can_be_used() and self.attached_image["base64"] is not None:
|
||||
data["messages"][-1]["images"] = [self.attached_image["base64"]]
|
||||
self.switch_send_stop_button()
|
||||
self.toggle_ui_sensitive(False)
|
||||
self.image_button.set_sensitive(False)
|
||||
|
||||
self.show_message(self.message_text_view.get_buffer().get_text(self.message_text_view.get_buffer().get_start_iter(), self.message_text_view.get_buffer().get_end_iter(), False), False, f"\n\n<small>{formated_datetime}</small>", self.attached_image["base64"], id=len(self.chats["chats"][self.chats["selected_chat"]]["messages"])-1)
|
||||
#self.attachments[name] = {"path": file_path, "type": file_type, "content": content}
|
||||
self.show_message(self.message_text_view.get_buffer().get_text(self.message_text_view.get_buffer().get_start_iter(), self.message_text_view.get_buffer().get_end_iter(), False), False, f"\n\n<small>{formated_datetime}</small>", attached_images, attached_files, id=id)
|
||||
self.message_text_view.get_buffer().set_text("", 0)
|
||||
self.loading_spinner = Gtk.Spinner(spinning=True, margin_top=12, margin_bottom=12, hexpand=True)
|
||||
self.chat_container.append(self.loading_spinner)
|
||||
self.show_message("", True, id=len(self.chats["chats"][self.chats["selected_chat"]]["messages"]))
|
||||
bot_id=self.generate_uuid()
|
||||
self.show_message("", True, id=bot_id)
|
||||
|
||||
thread = threading.Thread(target=self.run_message, args=(data['messages'], data['model']))
|
||||
thread = threading.Thread(target=self.run_message, args=(data['messages'], data['model'], bot_id))
|
||||
thread.start()
|
||||
|
||||
@Gtk.Template.Callback()
|
||||
@@ -208,22 +246,14 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
||||
if not self.verify_connection():
|
||||
self.connection_error()
|
||||
|
||||
@Gtk.Template.Callback()
|
||||
def open_image(self, button):
|
||||
if "destructive-action" in button.get_css_classes():
|
||||
dialogs.remove_image(self)
|
||||
else:
|
||||
file_dialog = Gtk.FileDialog(default_filter=self.file_filter_image)
|
||||
file_dialog.open(self, None, self.load_image)
|
||||
|
||||
@Gtk.Template.Callback()
|
||||
def chat_changed(self, listbox, row):
|
||||
if row and row.get_name() != self.chats["selected_chat"]:
|
||||
self.chats["selected_chat"] = row.get_name()
|
||||
self.load_history_into_chat()
|
||||
if len(self.chats["chats"][self.chats["selected_chat"]]["messages"]) > 0:
|
||||
if len(self.chats["chats"][self.chats["selected_chat"]]["messages"].keys()) > 0:
|
||||
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"][-1]["model"]:
|
||||
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"]:
|
||||
self.model_drop_down.set_selected(i)
|
||||
break
|
||||
|
||||
@@ -263,9 +293,93 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
||||
else: value = round(value, 1)
|
||||
if self.model_tweaks[spin.get_name()] is not None and self.model_tweaks[spin.get_name()] != value:
|
||||
self.model_tweaks[spin.get_name()] = value
|
||||
print(self.model_tweaks)
|
||||
self.save_server_config()
|
||||
|
||||
@Gtk.Template.Callback()
|
||||
def create_model_start(self, button):
|
||||
base = self.create_model_base.get_subtitle()
|
||||
name = self.create_model_name.get_text()
|
||||
system = self.create_model_system.get_text()
|
||||
template = self.create_model_template.get_text()
|
||||
if "/" in base:
|
||||
modelfile = f"FROM {base}\nSYSTEM {system}\nTEMPLATE {template}"
|
||||
else:
|
||||
modelfile = f"FROM {base}\nSYSTEM {system}"
|
||||
self.pulling_model_list_box.set_visible(True)
|
||||
model_row = Adw.ActionRow(
|
||||
title = name
|
||||
)
|
||||
thread = threading.Thread(target=self.pull_model_process, kwargs={"model": name, "modelfile": modelfile})
|
||||
overlay = Gtk.Overlay()
|
||||
progress_bar = Gtk.ProgressBar(
|
||||
valign = 2,
|
||||
show_text = False,
|
||||
margin_start = 10,
|
||||
margin_end = 10,
|
||||
css_classes = ["osd", "horizontal", "bottom"]
|
||||
)
|
||||
button = Gtk.Button(
|
||||
icon_name = "media-playback-stop-symbolic",
|
||||
vexpand = False,
|
||||
valign = 3,
|
||||
css_classes = ["error"]
|
||||
)
|
||||
button.connect("clicked", lambda button, model_name=name : dialogs.stop_pull_model(self, model_name))
|
||||
model_row.add_suffix(button)
|
||||
self.pulling_models[name] = {"row": model_row, "progress_bar": progress_bar, "overlay": overlay}
|
||||
overlay.set_child(model_row)
|
||||
overlay.add_overlay(progress_bar)
|
||||
self.pulling_model_list_box.append(overlay)
|
||||
self.create_model_dialog.close()
|
||||
self.manage_models_dialog.present(self)
|
||||
thread.start()
|
||||
|
||||
@Gtk.Template.Callback()
|
||||
def override_changed(self, entry):
|
||||
name = entry.get_name()
|
||||
value = entry.get_text()
|
||||
if (not value and name not in local_instance.overrides) or (value and value in local_instance.overrides and local_instance.overrides[name] == value): return
|
||||
if not value: del local_instance.overrides[name]
|
||||
else: local_instance.overrides[name] = value
|
||||
self.save_server_config()
|
||||
if not self.run_remote: local_instance.reset()
|
||||
|
||||
@Gtk.Template.Callback()
|
||||
def link_button_handler(self, button):
|
||||
webbrowser.open(button.get_name())
|
||||
|
||||
def check_alphanumeric(self, editable, text, length, position):
|
||||
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")
|
||||
|
||||
def create_model(self, model:str, file:bool):
|
||||
name = ""
|
||||
system = ""
|
||||
template = ""
|
||||
if not file:
|
||||
response = connection_handler.simple_post(f"{connection_handler.url}/api/show", json.dumps({"name": model}))
|
||||
if 'text' in response:
|
||||
data = json.loads(response['text'])
|
||||
|
||||
for line in data['modelfile'].split('\n'):
|
||||
if line.startswith('SYSTEM'):
|
||||
system = line[len('SYSTEM'):].strip()
|
||||
elif line.startswith('TEMPLATE'):
|
||||
template = line[len('TEMPLATE'):].strip()
|
||||
self.create_model_template.set_sensitive(False)
|
||||
name = model.split(':')[0]
|
||||
else:
|
||||
self.create_model_template.set_sensitive(True)
|
||||
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|>"""'
|
||||
name = model.split("/")[-1].split(".")[0]
|
||||
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_type:str, message_id:int, overlay):
|
||||
if message_type not in self.toast_messages or message_id > len(self.toast_messages[message_type] or message_id < 0):
|
||||
message_type = "error"
|
||||
@@ -284,20 +398,49 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
||||
self.get_application().send_notification(None, notification)
|
||||
|
||||
def delete_message(self, message_element):
|
||||
message_index = int(message_element.get_name())
|
||||
if message_index < len(self.chats["chats"][self.chats["selected_chat"]]["messages"]):
|
||||
self.chats["chats"][self.chats["selected_chat"]]["messages"][message_index] = None
|
||||
self.chat_container.remove(message_element)
|
||||
self.save_history()
|
||||
id = message_element.get_name()
|
||||
del self.chats["chats"][self.chats["selected_chat"]]["messages"][id]
|
||||
self.chat_container.remove(message_element)
|
||||
if os.path.exists(os.path.join(self.data_dir, "chats", self.chats['selected_chat'], id)):
|
||||
print("deleting " + id)
|
||||
shutil.rmtree(os.path.join(self.data_dir, "chats", self.chats['selected_chat'], id))
|
||||
self.save_history()
|
||||
|
||||
def copy_message(self, message_element):
|
||||
message_index = int(message_element.get_name())
|
||||
print(message_index)
|
||||
id = message_element.get_name()
|
||||
clipboard = Gdk.Display().get_default().get_clipboard()
|
||||
clipboard.set(self.chats["chats"][self.chats["selected_chat"]]["messages"][message_index]["content"])
|
||||
clipboard.set(self.chats["chats"][self.chats["selected_chat"]]["messages"][id]["content"])
|
||||
self.show_toast("info", 5, self.main_overlay)
|
||||
|
||||
def show_message(self, msg:str, bot:bool, footer:str=None, image_base64:str=None, id:int=-1):
|
||||
def preview_file(self, file_path, file_type):
|
||||
content = self.get_content_of_file(file_path, file_type)
|
||||
buffer = self.file_preview_text_view.get_buffer()
|
||||
buffer.delete(buffer.get_start_iter(), buffer.get_end_iter())
|
||||
buffer.insert(buffer.get_start_iter(), content, len(content))
|
||||
self.file_preview_dialog.set_title(os.path.basename(file_path))
|
||||
self.file_preview_dialog.present(self)
|
||||
|
||||
def convert_history_to_ollama(self):
|
||||
messages = []
|
||||
for id, message in self.chats["chats"][self.chats["selected_chat"]]["messages"].items():
|
||||
new_message = message.copy()
|
||||
if 'files' in message and len(message['files']) > 0:
|
||||
del new_message['files']
|
||||
new_message['content'] = ''
|
||||
for name, file_type in message['files'].items():
|
||||
file_path = os.path.join(self.data_dir, "chats", self.chats['selected_chat'], id, name)
|
||||
new_message['content'] += f"```[{name}]\n{self.get_content_of_file(file_path, file_type)}\n```"
|
||||
new_message['content'] += message['content']
|
||||
if 'images' in message and len(message['images']) > 0:
|
||||
new_message['images'] = []
|
||||
for name in message['images']:
|
||||
file_path = os.path.join(self.data_dir, "chats", self.chats['selected_chat'], id, name)
|
||||
new_message['images'].append(self.get_content_of_file(file_path, 'image'))
|
||||
messages.append(new_message)
|
||||
return messages
|
||||
|
||||
|
||||
def show_message(self, msg:str, bot:bool, footer:str=None, images:list=None, files:dict=None, id:str=None):
|
||||
message_text = Gtk.TextView(
|
||||
editable=False,
|
||||
focusable=True,
|
||||
@@ -340,23 +483,63 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
||||
)
|
||||
message_text.set_valign(Gtk.Align.CENTER)
|
||||
|
||||
if image_base64 is not None:
|
||||
image_data = base64.b64decode(image_base64)
|
||||
loader = GdkPixbuf.PixbufLoader.new()
|
||||
loader.write(image_data)
|
||||
loader.close()
|
||||
if images and len(images) > 0:
|
||||
image_container = Gtk.Box(
|
||||
orientation=0,
|
||||
spacing=12
|
||||
)
|
||||
image_scroller = Gtk.ScrolledWindow(
|
||||
margin_top=10,
|
||||
margin_start=10,
|
||||
margin_end=10,
|
||||
hexpand=True,
|
||||
height_request = 240,
|
||||
child=image_container
|
||||
)
|
||||
for image in images:
|
||||
image_data = base64.b64decode(self.get_content_of_file(os.path.join(self.data_dir, "chats", self.chats['selected_chat'], id, image), "image"))
|
||||
loader = GdkPixbuf.PixbufLoader.new()
|
||||
loader.write(image_data)
|
||||
loader.close()
|
||||
pixbuf = loader.get_pixbuf()
|
||||
texture = Gdk.Texture.new_for_pixbuf(pixbuf)
|
||||
image = Gtk.Image.new_from_paintable(texture)
|
||||
image.set_size_request(240, 240)
|
||||
image.set_hexpand(False)
|
||||
image.set_css_classes(["flat"])
|
||||
image_container.append(image)
|
||||
message_box.append(image_scroller)
|
||||
|
||||
pixbuf = loader.get_pixbuf()
|
||||
texture = Gdk.Texture.new_for_pixbuf(pixbuf)
|
||||
if files and len(files) > 0:
|
||||
file_container = Gtk.Box(
|
||||
orientation=0,
|
||||
spacing=12
|
||||
)
|
||||
file_scroller = Gtk.ScrolledWindow(
|
||||
margin_top=10,
|
||||
margin_start=10,
|
||||
margin_end=10,
|
||||
hexpand=True,
|
||||
child=file_container
|
||||
)
|
||||
for name, file_type in files.items():
|
||||
shown_name='.'.join(name.split(".")[:-1])[:20] + (name[20:] and '..') + f".{name.split('.')[-1]}"
|
||||
|
||||
image = Gtk.Image.new_from_paintable(texture)
|
||||
image.set_size_request(240, 240)
|
||||
image.set_margin_top(10)
|
||||
image.set_margin_start(10)
|
||||
image.set_margin_end(10)
|
||||
image.set_hexpand(False)
|
||||
image.set_css_classes(["flat"])
|
||||
message_box.append(image)
|
||||
button_content = Adw.ButtonContent(
|
||||
label=shown_name,
|
||||
icon_name="document-text-symbolic"
|
||||
)
|
||||
button = Gtk.Button(
|
||||
vexpand=False,
|
||||
valign=3,
|
||||
name=name,
|
||||
css_classes=["flat"],
|
||||
tooltip_text=name,
|
||||
child=button_content
|
||||
)
|
||||
button.connect("clicked", lambda button, file_path=os.path.join(self.data_dir, "chats", self.chats['selected_chat'], id, name), file_type=file_type: self.preview_file(file_path, file_type))
|
||||
file_container.append(button)
|
||||
message_box.append(file_scroller)
|
||||
|
||||
message_box.append(message_text)
|
||||
overlay = Gtk.Overlay(css_classes=["message"], name=id)
|
||||
@@ -376,7 +559,7 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
||||
|
||||
def update_list_local_models(self):
|
||||
self.local_models = []
|
||||
response = connection_handler.simple_get(connection_handler.url + "/api/tags")
|
||||
response = connection_handler.simple_get(f"{connection_handler.url}/api/tags")
|
||||
for i in range(self.model_string_list.get_n_items() -1, -1, -1):
|
||||
self.model_string_list.remove(i)
|
||||
if response['status'] == 'ok':
|
||||
@@ -410,7 +593,7 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
||||
|
||||
def save_server_config(self):
|
||||
with open(os.path.join(self.config_dir, "server.json"), "w+") as f:
|
||||
json.dump({'remote_url': self.remote_url, 'run_remote': self.run_remote, 'local_port': local_instance.port, 'run_on_background': self.run_on_background, 'model_tweaks': self.model_tweaks}, f)
|
||||
json.dump({'remote_url': self.remote_url, 'run_remote': self.run_remote, 'local_port': local_instance.port, 'run_on_background': self.run_on_background, 'model_tweaks': self.model_tweaks, 'ollama_overrides': local_instance.overrides}, f, indent=6)
|
||||
|
||||
def verify_connection(self):
|
||||
response = connection_handler.simple_get(connection_handler.url)
|
||||
@@ -534,12 +717,12 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
||||
clipboard.set(text)
|
||||
self.show_toast("info", 4, self.main_overlay)
|
||||
|
||||
def update_bot_message(self, data):
|
||||
def update_bot_message(self, data, id):
|
||||
if self.bot_message is None:
|
||||
self.save_history()
|
||||
sys.exit()
|
||||
vadjustment = self.chat_window.get_vadjustment()
|
||||
if self.chats["chats"][self.chats["selected_chat"]]["messages"][-1]['role'] == "user" or vadjustment.get_value() + 50 >= vadjustment.get_upper() - vadjustment.get_page_size():
|
||||
if (id in self.chats["chats"][self.chats["selected_chat"]]["messages"] and self.chats["chats"][self.chats["selected_chat"]]["messages"][id]['role'] == "user") or vadjustment.get_value() + 50 >= vadjustment.get_upper() - vadjustment.get_page_size():
|
||||
GLib.idle_add(vadjustment.set_value, vadjustment.get_upper())
|
||||
if data['done']:
|
||||
formated_datetime = datetime.now().strftime("%Y/%m/%d %H:%M")
|
||||
@@ -547,37 +730,36 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
||||
GLib.idle_add(self.bot_message.insert_markup, self.bot_message.get_end_iter(), text, len(text))
|
||||
self.save_history()
|
||||
else:
|
||||
if self.chats["chats"][self.chats["selected_chat"]]["messages"][-1]['role'] == "user":
|
||||
if id not in self.chats["chats"][self.chats["selected_chat"]]["messages"]:
|
||||
GLib.idle_add(self.chat_container.remove, self.loading_spinner)
|
||||
self.loading_spinner = None
|
||||
self.chats["chats"][self.chats["selected_chat"]]["messages"].append({
|
||||
self.chats["chats"][self.chats["selected_chat"]]["messages"][id] = {
|
||||
"role": "assistant",
|
||||
"model": data['model'],
|
||||
"date": datetime.now().strftime("%Y/%m/%d %H:%M"),
|
||||
"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"][-1]['content'] += data['message']['content']
|
||||
self.chats["chats"][self.chats["selected_chat"]]["messages"][id]['content'] += data['message']['content']
|
||||
|
||||
def toggle_ui_sensitive(self, status):
|
||||
for element in [self.chat_list_box, self.add_chat_button]:
|
||||
for element in [self.chat_list_box, self.add_chat_button, self.chats_menu_button]:
|
||||
element.set_sensitive(status)
|
||||
|
||||
def switch_send_stop_button(self):
|
||||
self.stop_button.set_visible(self.send_button.get_visible())
|
||||
self.send_button.set_visible(not self.send_button.get_visible())
|
||||
|
||||
def run_message(self, messages, model):
|
||||
response = connection_handler.stream_post(f"{connection_handler.url}/api/chat", data=json.dumps({"model": model, "messages": messages}), callback=self.update_bot_message)
|
||||
def run_message(self, messages, model, id):
|
||||
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))
|
||||
GLib.idle_add(self.add_code_blocks)
|
||||
GLib.idle_add(self.switch_send_stop_button)
|
||||
GLib.idle_add(self.toggle_ui_sensitive, True)
|
||||
if self.verify_if_image_can_be_used(): GLib.idle_add(self.image_button.set_sensitive, True)
|
||||
GLib.idle_add(self.image_button.set_css_classes, ["circular"])
|
||||
self.attached_image = {"path": None, "base64": None}
|
||||
if self.loading_spinner:
|
||||
GLib.idle_add(self.chat_container.remove, self.loading_spinner)
|
||||
self.loading_spinner = None
|
||||
if response['status'] == 'error':
|
||||
GLib.idle_add(self.connection_error)
|
||||
print(response)
|
||||
|
||||
def pull_model_update(self, data, model_name):
|
||||
if model_name in list(self.pulling_models.keys()):
|
||||
@@ -588,9 +770,14 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
||||
if len(list(self.pulling_models.keys())) == 0:
|
||||
GLib.idle_add(self.pulling_model_list_box.set_visible, False)
|
||||
|
||||
def pull_model_process(self, model):
|
||||
data = {"name":model}
|
||||
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))
|
||||
def pull_model_process(self, model, modelfile):
|
||||
response = {}
|
||||
if modelfile:
|
||||
data = {"name": model, "modelfile": modelfile}
|
||||
response = connection_handler.stream_post(f"{connection_handler.url}/api/create", data=json.dumps(data), callback=lambda data, model_name=model: self.pull_model_update(data, model_name))
|
||||
else:
|
||||
data = {"name": model}
|
||||
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)
|
||||
|
||||
if response['status'] == 'ok':
|
||||
@@ -618,7 +805,7 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
||||
model_row = Adw.ActionRow(
|
||||
title = model
|
||||
)
|
||||
thread = threading.Thread(target=self.pull_model_process, kwargs={"model": model})
|
||||
thread = threading.Thread(target=self.pull_model_process, kwargs={"model": model, "modelfile": None})
|
||||
overlay = Gtk.Overlay()
|
||||
progress_bar = Gtk.ProgressBar(
|
||||
valign = 2,
|
||||
@@ -634,7 +821,6 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
||||
css_classes = ["error"]
|
||||
)
|
||||
button.connect("clicked", lambda button, model_name=model : dialogs.stop_pull_model(self, model_name))
|
||||
#model_row.add_suffix(progress_bar)
|
||||
model_row.add_suffix(button)
|
||||
self.pulling_models[model] = {"row": model_row, "progress_bar": progress_bar, "overlay": overlay}
|
||||
overlay.set_child(model_row)
|
||||
@@ -668,70 +854,47 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
||||
self.available_model_list_box.append(model)
|
||||
|
||||
def save_history(self):
|
||||
with open(os.path.join(self.config_dir, "chats.json"), "w+") as f:
|
||||
with open(os.path.join(self.data_dir, "chats", "chats.json"), "w+") as f:
|
||||
json.dump(self.chats, f, indent=4)
|
||||
|
||||
def load_history_into_chat(self):
|
||||
for widget in list(self.chat_container): self.chat_container.remove(widget)
|
||||
for i, message in enumerate(self.chats['chats'][self.chats["selected_chat"]]['messages']):
|
||||
for key, message in self.chats['chats'][self.chats["selected_chat"]]['messages'].items():
|
||||
if message:
|
||||
if message['role'] == 'user':
|
||||
self.show_message(message['content'], False, f"\n\n<small>{message['date']}</small>", message['images'][0] if 'images' in message and len(message['images']) > 0 else None, id=i)
|
||||
self.show_message(message['content'], False, f"\n\n<small>{message['date']}</small>", message['images'] if 'images' in message else None, message['files'] if 'files' in message else None, id=key)
|
||||
else:
|
||||
self.show_message(message['content'], True, f"\n\n<small>{message['model']}\t|\t{message['date']}</small>", id=i)
|
||||
self.show_message(message['content'], True, f"\n\n<small>{message['model']}\t|\t{message['date']}</small>", id=key)
|
||||
self.add_code_blocks()
|
||||
self.bot_message = None
|
||||
|
||||
def load_history(self):
|
||||
if os.path.exists(os.path.join(self.config_dir, "chats.json")):
|
||||
if os.path.exists(os.path.join(self.data_dir, "chats", "chats.json")):
|
||||
try:
|
||||
with open(os.path.join(self.config_dir, "chats.json"), "r") as f:
|
||||
with open(os.path.join(self.data_dir, "chats", "chats.json"), "r") as f:
|
||||
self.chats = json.load(f)
|
||||
if "selected_chat" not in self.chats or self.chats["selected_chat"] not in self.chats["chats"]: self.chats["selected_chat"] = list(self.chats["chats"].keys())[0]
|
||||
if len(list(self.chats["chats"].keys())) == 0: self.chats["chats"][_("New Chat")] = {"messages": []}
|
||||
for chat_name, content in self.chats['chats'].items():
|
||||
for i, content in enumerate(content['messages']):
|
||||
if not content: del self.chats['chats'][chat_name]['messages'][i]
|
||||
if len(list(self.chats["chats"].keys())) == 0: self.chats["chats"][_("New Chat")] = {"messages": {}}
|
||||
except Exception as e:
|
||||
self.chats = {"chats": {_("New Chat"): {"messages": []}}, "selected_chat": _("New Chat")}
|
||||
self.chats = {"chats": {_("New Chat"): {"messages": {}}}, "selected_chat": _("New Chat")}
|
||||
self.load_history_into_chat()
|
||||
|
||||
def load_image(self, file_dialog, result):
|
||||
try: file = file_dialog.open_finish(result)
|
||||
except: return
|
||||
try:
|
||||
self.attached_image["path"] = file.get_path()
|
||||
with Image.open(self.attached_image["path"]) as img:
|
||||
width, height = img.size
|
||||
max_size = 240
|
||||
if width > height:
|
||||
new_width = max_size
|
||||
new_height = int((max_size / width) * height)
|
||||
def generate_numbered_name(self, chat_name:str, compare_list:list) -> str:
|
||||
if chat_name in compare_list:
|
||||
for i in range(len(compare_list)):
|
||||
if "." in chat_name:
|
||||
if f"{'.'.join(chat_name.split('.')[:-1])} {i+1}.{chat_name.split('.')[-1]}" not in compare_list:
|
||||
chat_name = f"{'.'.join(chat_name.split('.')[:-1])} {i+1}.{chat_name.split('.')[-1]}"
|
||||
break
|
||||
else:
|
||||
new_height = max_size
|
||||
new_width = int((max_size / height) * width)
|
||||
resized_img = img.resize((new_width, new_height), Image.LANCZOS)
|
||||
with BytesIO() as output:
|
||||
resized_img.save(output, format="PNG")
|
||||
image_data = output.getvalue()
|
||||
self.attached_image["base64"] = base64.b64encode(image_data).decode("utf-8")
|
||||
|
||||
self.image_button.set_css_classes(["destructive-action", "circular"])
|
||||
except Exception as e:
|
||||
self.show_toast("error", 5, self.main_overlay)
|
||||
|
||||
def remove_image(self):
|
||||
self.image_button.set_css_classes(["circular"])
|
||||
self.attached_image = {"path": None, "base64": None}
|
||||
|
||||
def generate_numbered_chat_name(self, chat_name) -> str:
|
||||
if chat_name in self.chats["chats"]:
|
||||
for i in range(len(list(self.chats["chats"].keys()))):
|
||||
if chat_name + f" {i+1}" not in self.chats["chats"]:
|
||||
chat_name += f" {i+1}"
|
||||
break
|
||||
if f"{chat_name} {i+1}" not in compare_list:
|
||||
chat_name = f"{chat_name} {i+1}"
|
||||
break
|
||||
return chat_name
|
||||
|
||||
def generate_uuid(self) -> str:
|
||||
return f"{datetime.today().strftime('%Y%m%d%H%M%S%f')}{uuid.uuid4().hex}"
|
||||
|
||||
def clear_chat(self):
|
||||
for widget in list(self.chat_container): self.chat_container.remove(widget)
|
||||
self.chats["chats"][self.chats["selected_chat"]]["messages"] = []
|
||||
@@ -739,22 +902,26 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
||||
|
||||
def delete_chat(self, chat_name):
|
||||
del self.chats['chats'][chat_name]
|
||||
if os.path.exists(os.path.join(self.data_dir, "chats", self.chats['selected_chat'])):
|
||||
shutil.rmtree(os.path.join(self.data_dir, "chats", self.chats['selected_chat']))
|
||||
self.save_history()
|
||||
self.update_chat_list()
|
||||
if len(self.chats['chats'])==0:
|
||||
self.new_chat()
|
||||
|
||||
def rename_chat(self, old_chat_name, new_chat_name, label_element):
|
||||
new_chat_name = self.generate_numbered_chat_name(new_chat_name)
|
||||
new_chat_name = self.generate_numbered_name(new_chat_name, self.chats["chats"].keys())
|
||||
self.chats["chats"][new_chat_name] = self.chats["chats"][old_chat_name]
|
||||
del self.chats["chats"][old_chat_name]
|
||||
if os.path.exists(os.path.join(self.data_dir, "chats", old_chat_name)):
|
||||
shutil.move(os.path.join(self.data_dir, "chats", old_chat_name), os.path.join(self.data_dir, "chats", new_chat_name))
|
||||
label_element.set_label(new_chat_name)
|
||||
label_element.get_parent().set_name(new_chat_name)
|
||||
self.save_history()
|
||||
|
||||
def new_chat(self):
|
||||
chat_name = self.generate_numbered_chat_name(_("New Chat"))
|
||||
self.chats["chats"][chat_name] = {"messages": []}
|
||||
chat_name = self.generate_numbered_name(_("New Chat"), self.chats["chats"].keys())
|
||||
self.chats["chats"][chat_name] = {"messages": {}}
|
||||
self.save_history()
|
||||
self.new_chat_element(chat_name, True)
|
||||
|
||||
@@ -763,7 +930,7 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
||||
del self.pulling_models[model_name]
|
||||
|
||||
def delete_model(self, model_name):
|
||||
response = connection_handler.simple_delete(connection_handler.url + "/api/delete", data={"name": model_name})
|
||||
response = connection_handler.simple_delete(f"{connection_handler.url}/api/delete", data={"name": model_name})
|
||||
self.update_list_local_models()
|
||||
if response['status'] == 'ok':
|
||||
self.show_toast("good", 0, self.manage_models_overlay)
|
||||
@@ -860,35 +1027,74 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
||||
|
||||
def on_export_current_chat(self, file_dialog, result):
|
||||
file = file_dialog.save_finish(result)
|
||||
data_to_export = {self.chats["selected_chat"]: self.chats["chats"][self.chats["selected_chat"]]}
|
||||
file.replace_contents_async(
|
||||
json.dumps(data_to_export, indent=4).encode("UTF-8"),
|
||||
etag=None,
|
||||
make_backup=False,
|
||||
flags=Gio.FileCreateFlags.NONE,
|
||||
cancellable=None,
|
||||
callback=self.on_replace_contents
|
||||
)
|
||||
if not file: return
|
||||
json_data = json.dumps({self.chats["selected_chat"]: self.chats["chats"][self.chats["selected_chat"]]}, 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, f"{self.chats['selected_chat']}")
|
||||
with tarfile.open(tar_path, "w") as tar:
|
||||
tar.add(json_path, arcname="data.json")
|
||||
directory = os.path.join(self.data_dir, "chats", self.chats['selected_chat'])
|
||||
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_current_chat(self):
|
||||
file_dialog = Gtk.FileDialog(initial_name=f"{self.chats['selected_chat']}.json")
|
||||
file_dialog = Gtk.FileDialog(initial_name=f"{self.chats['selected_chat']}.tar")
|
||||
file_dialog.save(parent=self, cancellable=None, callback=self.on_export_current_chat)
|
||||
|
||||
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)
|
||||
data, _ = data_stream.read_until('\0', None)
|
||||
data = json.loads(data)
|
||||
chat_name = list(data.keys())[0]
|
||||
chat_content = data[chat_name]
|
||||
self.chats['chats'][chat_name] = chat_content
|
||||
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") as json_file:
|
||||
data = json.load(json_file)
|
||||
for chat_name, chat_content in data.items():
|
||||
new_chat_name = self.generate_numbered_name(chat_name, list(self.chats['chats'].keys()))
|
||||
self.chats['chats'][new_chat_name] = chat_content
|
||||
src_path = os.path.join(temp_dir, chat_name)
|
||||
if os.path.exists(src_path) and os.path.isdir(src_path):
|
||||
dest_path = os.path.join(self.data_dir, "chats", new_chat_name)
|
||||
shutil.copytree(src_path, dest_path)
|
||||
|
||||
|
||||
self.update_chat_list()
|
||||
self.save_history()
|
||||
self.show_toast("good", 3, self.main_overlay)
|
||||
|
||||
def import_chat(self):
|
||||
file_dialog = Gtk.FileDialog(default_filter=self.file_filter_json)
|
||||
file_dialog = Gtk.FileDialog(default_filter=self.file_filter_tar)
|
||||
file_dialog.open(self, None, self.on_chat_imported)
|
||||
|
||||
def switch_run_on_background(self):
|
||||
@@ -896,9 +1102,64 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
||||
self.set_hide_on_close(self.run_on_background)
|
||||
self.verify_connection()
|
||||
|
||||
def get_content_of_file(self, file_path, file_type):
|
||||
if file_type == 'image':
|
||||
try:
|
||||
with Image.open(file_path) as img:
|
||||
width, height = img.size
|
||||
max_size = 240
|
||||
if width > height:
|
||||
new_width = max_size
|
||||
new_height = int((max_size / width) * height)
|
||||
else:
|
||||
new_height = max_size
|
||||
new_width = int((max_size / height) * width)
|
||||
resized_img = img.resize((new_width, new_height), Image.LANCZOS)
|
||||
with BytesIO() as output:
|
||||
resized_img.save(output, format="PNG")
|
||||
image_data = output.getvalue()
|
||||
return base64.b64encode(image_data).decode("utf-8")
|
||||
except Exception as e:
|
||||
self.show_toast("error", 5, self.main_overlay)
|
||||
elif file_type == 'plain_text':
|
||||
with open(file_path, 'r') as f:
|
||||
return f.read()
|
||||
|
||||
def remove_attached_file(self, button):
|
||||
del self.attachments[button.get_name()]
|
||||
button.get_parent().remove(button)
|
||||
if len(self.attachments) == 0: self.attachment_box.set_visible(False)
|
||||
|
||||
def attach_file(self, file_path, file_type):
|
||||
name = self.generate_numbered_name(os.path.basename(file_path), self.attachments.keys())
|
||||
content = self.get_content_of_file(file_path, file_type)
|
||||
|
||||
shown_name='.'.join(name.split(".")[:-1])[:20] + (name[20:] and '..') + f".{name.split('.')[-1]}"
|
||||
|
||||
button_content = Adw.ButtonContent(
|
||||
label=shown_name,
|
||||
icon_name={"image": "image-x-generic-symbolic", "plain_text": "document-text-symbolic"}[file_type]
|
||||
)
|
||||
button = Gtk.Button(
|
||||
vexpand=True,
|
||||
valign=3,
|
||||
name=name,
|
||||
css_classes=["flat"],
|
||||
tooltip_text=name,
|
||||
child=button_content
|
||||
)
|
||||
|
||||
self.attachments[name] = {"path": file_path, "type": file_type, "content": content, "button": button}
|
||||
button.connect("clicked", lambda button: dialogs.remove_attached_file(self, button))
|
||||
self.attachment_container.append(button)
|
||||
self.attachment_box.set_visible(True)
|
||||
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
GtkSource.init()
|
||||
if os.path.exists(os.path.join(self.config_dir, "chats.json")) and not os.path.exists(os.path.join(self.data_dir, "chats", "chats.json")):
|
||||
update_history.update(self)
|
||||
self.set_help_overlay(self.shortcut_window)
|
||||
self.get_application().set_accels_for_action("win.show-help-overlay", ['<primary>slash'])
|
||||
self.get_application().create_action('new_chat', lambda *_: self.new_chat(), ['<primary>n'])
|
||||
@@ -906,8 +1167,13 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
||||
self.get_application().create_action('send', lambda *_: self.send_message(self), ['Return'])
|
||||
self.get_application().create_action('export_current_chat', lambda *_: self.export_current_chat())
|
||||
self.get_application().create_action('import_chat', lambda *_: self.import_chat())
|
||||
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('attach_image', lambda *_: dialogs.attach_file(self, self.file_filter_image, "image"))
|
||||
self.get_application().create_action('attach_plain_text', lambda *_: dialogs.attach_file(self, self.file_filter_text, "plain_text"))
|
||||
self.add_chat_button.connect("clicked", lambda button : self.new_chat())
|
||||
|
||||
self.create_model_name.get_delegate().connect("insert-text", self.check_alphanumeric)
|
||||
self.remote_connection_entry.connect("entry-activated", lambda entry : entry.set_css_classes([]))
|
||||
self.remote_connection_switch.connect("notify", lambda pspec, user_data : self.connection_switched())
|
||||
self.background_switch.connect("notify", lambda pspec, user_data : self.switch_run_on_background())
|
||||
@@ -923,6 +1189,16 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
||||
self.temperature_spin.set_value(self.model_tweaks['temperature'])
|
||||
self.seed_spin.set_value(self.model_tweaks['seed'])
|
||||
self.keep_alive_spin.set_value(self.model_tweaks['keep_alive'])
|
||||
#Overrides
|
||||
if "ollama_overrides" in data: local_instance.overrides = data['ollama_overrides']
|
||||
for element in [
|
||||
self.override_HSA_OVERRIDE_GFX_VERSION,
|
||||
self.override_CUDA_VISIBLE_DEVICES,
|
||||
self.override_HIP_VISIBLE_DEVICES]:
|
||||
override = element.get_name()
|
||||
if override in local_instance.overrides:
|
||||
element.set_text(local_instance.overrides[override])
|
||||
|
||||
|
||||
self.background_switch.set_active(self.run_on_background)
|
||||
self.set_hide_on_close(self.run_on_background)
|
||||
|
||||
466
src/window.ui
@@ -24,7 +24,9 @@
|
||||
<setter object="split_view_overlay" property="collapsed">true</setter>
|
||||
<setter object="welcome_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="file_preview_dialog" property="width-request">360</setter>
|
||||
</object>
|
||||
</child>
|
||||
<property name="content">
|
||||
@@ -47,7 +49,7 @@
|
||||
<object class="GtkMenuButton" id="chats_menu_button">
|
||||
<property name="direction">1</property>
|
||||
<property name="halign">3</property>
|
||||
<property name="menu-model">chats_button_menu</property>
|
||||
<property name="menu-model">chats_menu</property>
|
||||
<property name="icon-name">view-more-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
@@ -162,92 +164,115 @@
|
||||
<object class="AdwClamp">
|
||||
<property name="maximum-size">1000</property>
|
||||
<property name="tightening-threshold">800</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">0</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>
|
||||
<child>
|
||||
<object class="GtkButton" id="image_button">
|
||||
<signal name="clicked" handler="open_image"/>
|
||||
<property name="vexpand">false</property>
|
||||
<property name="valign">3</property>
|
||||
<property name="sensitive">false</property>
|
||||
<property name="tooltip-text" translatable="yes">Only available on selected models</property>
|
||||
<child>
|
||||
<object class="AdwButtonContent">
|
||||
<property name="icon-name">image-x-generic-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<style>
|
||||
<class name="card"/>
|
||||
<class name="message-input"/>
|
||||
</style>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="margin-start">12</property>
|
||||
<property name="margin-end">12</property>
|
||||
<style>
|
||||
<class name="message-input"/>
|
||||
<class name="undershoot-bottom"/>
|
||||
<class name="undershoot-top"/>
|
||||
</style>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">1</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>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="attachment_box">
|
||||
<property name="visible">false</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="attachment_container">
|
||||
<property name="orientation">0</property>
|
||||
<property name="vexpand">false</property>
|
||||
<property name="spacing">12</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">0</property>
|
||||
<property name="spacing">12</property>
|
||||
<child>
|
||||
<object class="GtkMenuButton" id="attachment_button">
|
||||
<property name="menu-model">attachment_menu</property>
|
||||
<property name="direction">0</property>
|
||||
<property name="vexpand">false</property>
|
||||
<property name="valign">3</property>
|
||||
<property name="tooltip-text" translatable="yes">Attach file</property>
|
||||
<style>
|
||||
<class name="circular"/>
|
||||
</style>
|
||||
<child>
|
||||
<object class="AdwButtonContent">
|
||||
<property name="icon-name">chain-link-loose-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<style>
|
||||
<class name="card"/>
|
||||
<class name="message-input"/>
|
||||
</style>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="margin-start">12</property>
|
||||
<property name="margin-end">12</property>
|
||||
<style>
|
||||
<class name="message-input"/>
|
||||
<class name="undershoot-bottom"/>
|
||||
<class name="undershoot-top"/>
|
||||
</style>
|
||||
<child>
|
||||
<object class="GtkTextView" id="message_text_view">
|
||||
<property name="wrap-mode">word</property>
|
||||
<property name="top-margin">6</property>
|
||||
<property name="bottom-margin">6</property>
|
||||
<property name="hexpand">true</property>
|
||||
<style>
|
||||
<class name="message-input"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTextView" id="message_text_view">
|
||||
<property name="wrap-mode">word</property>
|
||||
<property name="top-margin">6</property>
|
||||
<property name="bottom-margin">6</property>
|
||||
<property name="hexpand">true</property>
|
||||
<object class="GtkButton" id="send_button">
|
||||
<signal name="clicked" handler="send_message"/>
|
||||
<property name="vexpand">false</property>
|
||||
<property name="valign">3</property>
|
||||
<property name="tooltip-text" translatable="yes">Send message</property>
|
||||
<style>
|
||||
<class name="message-input"/>
|
||||
<class name="suggested-action"/>
|
||||
<class name="circular"/>
|
||||
</style>
|
||||
<child>
|
||||
<object class="AdwButtonContent">
|
||||
<property name="icon-name">paper-plane-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="stop_button">
|
||||
<signal name="clicked" handler="stop_message"/>
|
||||
<property name="vexpand">false</property>
|
||||
<property name="valign">3</property>
|
||||
<property name="visible">false</property>
|
||||
<style>
|
||||
<class name="destructive-action"/>
|
||||
<class name="circular"/>
|
||||
</style>
|
||||
<child>
|
||||
<object class="AdwButtonContent">
|
||||
<property name="icon-name">media-playback-stop-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="send_button">
|
||||
<signal name="clicked" handler="send_message"/>
|
||||
<property name="vexpand">false</property>
|
||||
<property name="valign">3</property>
|
||||
<style>
|
||||
<class name="suggested-action"/>
|
||||
<class name="circular"/>
|
||||
</style>
|
||||
<child>
|
||||
<object class="AdwButtonContent">
|
||||
<property name="icon-name">paper-plane-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="stop_button">
|
||||
<signal name="clicked" handler="stop_message"/>
|
||||
<property name="vexpand">false</property>
|
||||
<property name="valign">3</property>
|
||||
<property name="visible">false</property>
|
||||
<style>
|
||||
<class name="destructive-action"/>
|
||||
<class name="circular"/>
|
||||
</style>
|
||||
<child>
|
||||
<object class="AdwButtonContent">
|
||||
<property name="icon-name">media-playback-stop-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
@@ -301,7 +326,7 @@
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwPreferencesPage" id="model_page">
|
||||
<property name="title" translatable="yes">Advanced Model Settings</property>
|
||||
<property name="title" translatable="yes">Model</property>
|
||||
<property name="icon-name">preferences-other-symbolic</property>
|
||||
<child>
|
||||
<object class="AdwPreferencesGroup">
|
||||
@@ -357,6 +382,199 @@
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwPreferencesPage" id="instance_page">
|
||||
<property name="title" translatable="yes">Ollama Instance</property>
|
||||
<property name="icon-name">brain-augemnted-symbolic</property>
|
||||
<child>
|
||||
<object class="AdwPreferencesGroup">
|
||||
<property name="title" translatable="yes">Ollama Overrides</property>
|
||||
<property name="description" translatable="yes">Manage the arguments used on Ollama, any changes on this page only applies to the integrated instance, the instance will restart if you make changes</property>
|
||||
<child>
|
||||
<object class="AdwEntryRow" id="override_HSA_OVERRIDE_GFX_VERSION">
|
||||
<signal name="apply" handler="override_changed"/>
|
||||
<property name="name">HSA_OVERRIDE_GFX_VERSION</property>
|
||||
<property name="title" translatable="no">HSA_OVERRIDE_GFX_VERSION</property>
|
||||
<property name="show-apply-button">true</property>
|
||||
<child type="prefix">
|
||||
<object class="GtkButton">
|
||||
<signal name="clicked" handler="link_button_handler"/>
|
||||
<property name="name">https://github.com/ollama/ollama/blob/main/docs/gpu.md#overrides</property>
|
||||
<property name="vexpand">false</property>
|
||||
<property name="valign">3</property>
|
||||
<property name="icon-name">globe-symbolic</property>
|
||||
<style>
|
||||
<class name="success"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwEntryRow" id="override_CUDA_VISIBLE_DEVICES">
|
||||
<signal name="apply" handler="override_changed"/>
|
||||
<property name="name">CUDA_VISIBLE_DEVICES</property>
|
||||
<property name="title" translatable="no">CUDA_VISIBLE_DEVICES</property>
|
||||
<property name="show-apply-button">true</property>
|
||||
<child type="prefix">
|
||||
<object class="GtkButton">
|
||||
<signal name="clicked" handler="link_button_handler"/>
|
||||
<property name="name">https://github.com/ollama/ollama/blob/main/docs/gpu.md#gpu-selection</property>
|
||||
<property name="vexpand">false</property>
|
||||
<property name="valign">3</property>
|
||||
<property name="icon-name">globe-symbolic</property>
|
||||
<style>
|
||||
<class name="success"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwEntryRow" id="override_HIP_VISIBLE_DEVICES">
|
||||
<signal name="apply" handler="override_changed"/>
|
||||
<property name="name">HIP_VISIBLE_DEVICES</property>
|
||||
<property name="title" translatable="no">HIP_VISIBLE_DEVICES</property>
|
||||
<property name="show-apply-button">true</property>
|
||||
<child type="prefix">
|
||||
<object class="GtkButton">
|
||||
<signal name="clicked" handler="link_button_handler"/>
|
||||
<property name="name">https://github.com/ollama/ollama/blob/main/docs/gpu.md#gpu-selection-1</property>
|
||||
<property name="vexpand">false</property>
|
||||
<property name="valign">3</property>
|
||||
<property name="icon-name">globe-symbolic</property>
|
||||
<style>
|
||||
<class name="success"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</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"></property>
|
||||
<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">
|
||||
@@ -369,6 +587,14 @@
|
||||
<object class="AdwToolbarView">
|
||||
<child type="top">
|
||||
<object class="AdwHeaderBar">
|
||||
<child type="start">
|
||||
<object class="GtkMenuButton">
|
||||
<property name="primary">True</property>
|
||||
<property name="icon-name">list-add-symbolic</property>
|
||||
<property name="tooltip-text" translatable="yes">Create model</property>
|
||||
<property name="menu-model">create_model_menu</property>
|
||||
</object>
|
||||
</child>
|
||||
<property name="title-widget">
|
||||
<object class="AdwWindowTitle">
|
||||
<property name="title" translatable="yes">Manage models</property>
|
||||
@@ -425,6 +651,37 @@
|
||||
</child>
|
||||
</object>
|
||||
|
||||
<object class="AdwDialog" id="file_preview_dialog">
|
||||
<property name="can-close">true</property>
|
||||
<property name="width-request">450</property>
|
||||
<property name="height-request">450</property>
|
||||
<child>
|
||||
<object class="AdwToolbarView">
|
||||
<child type="top">
|
||||
<object class="AdwHeaderBar">
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="hexpand">true</property>
|
||||
<property name="vexpand">true</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>
|
||||
<child>
|
||||
<object class="GtkTextView" id="file_preview_text_view">
|
||||
<property name="hexpand">true</property>
|
||||
<property name="vexpand">true</property>
|
||||
<property name="editable">false</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
|
||||
<object class="AdwDialog" id="welcome_dialog">
|
||||
<property name="can-close">false</property>
|
||||
<property name="width-request">450</property>
|
||||
@@ -622,7 +879,7 @@
|
||||
</item>
|
||||
</section>
|
||||
</menu>
|
||||
<menu id="chats_button_menu">
|
||||
<menu id="chats_menu">
|
||||
<section>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Export current chat</attribute>
|
||||
@@ -634,6 +891,44 @@
|
||||
</item>
|
||||
</section>
|
||||
</menu>
|
||||
<menu id="create_model_menu">
|
||||
<section>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">From existing model</attribute>
|
||||
<attribute name="action">app.create_model_from_existing</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">From GGUF file (Testing)</attribute>
|
||||
<attribute name="action">app.create_model_from_file</attribute>
|
||||
</item>
|
||||
</section>
|
||||
</menu>
|
||||
<menu id="attachment_menu">
|
||||
<section>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Plain text file</attribute>
|
||||
<attribute name="action">app.attach_plain_text</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Image</attribute>
|
||||
<attribute name="action">app.attach_image</attribute>
|
||||
</item>
|
||||
</section>
|
||||
</menu>
|
||||
<object class="GtkFileFilter" id="file_filter_text">
|
||||
<suffixes>
|
||||
<suffix></suffix>
|
||||
<suffix>txt</suffix>
|
||||
<suffix>md</suffix>
|
||||
<suffix>html</suffix>
|
||||
<suffix>css</suffix>
|
||||
<suffix>js</suffix>
|
||||
<suffix>py</suffix>
|
||||
<suffix>java</suffix>
|
||||
<suffix>json</suffix>
|
||||
<suffix>xml</suffix>
|
||||
</suffixes>
|
||||
</object>
|
||||
<object class="GtkFileFilter" id="file_filter_image">
|
||||
<mime-types>
|
||||
<mime-type>image/svg+xml</mime-type>
|
||||
@@ -643,11 +938,16 @@
|
||||
<mime-type>image/gif</mime-type>
|
||||
</mime-types>
|
||||
</object>
|
||||
<object class="GtkFileFilter" id="file_filter_json">
|
||||
<object class="GtkFileFilter" id="file_filter_tar">
|
||||
<mime-types>
|
||||
<mime-type>application/json</mime-type>
|
||||
<mime-type>application/x-tar</mime-type>
|
||||
</mime-types>
|
||||
</object>
|
||||
<object class="GtkFileFilter" id="file_filter_gguf">
|
||||
<suffixes>
|
||||
<suffix>gguf</suffix>
|
||||
</suffixes>
|
||||
</object>
|
||||
<object class="GtkShortcutsWindow" id="shortcut_window">
|
||||
<property name="modal">1</property>
|
||||
<child>
|
||||
|
||||