Compare commits
90 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a7b6e6bbce | ||
|
|
801c10fb77 | ||
|
|
50520b8474 | ||
|
|
b66e2102d3 | ||
|
|
8c0f1fd4d5 | ||
|
|
8b851d1b56 | ||
|
|
f36d6e1b29 | ||
|
|
eecac162ef | ||
|
|
82e7a3a9e1 | ||
|
|
f0505a0242 | ||
|
|
11dd13b430 | ||
|
|
b8fe222052 | ||
|
|
47d19a58aa | ||
|
|
fd67afbf33 | ||
|
|
d06e08a64e | ||
|
|
77b08d9e52 | ||
|
|
9451bf88d0 | ||
|
|
82bb50d663 | ||
|
|
edc3053774 | ||
|
|
1320ddb7d4 | ||
|
|
d95f06a230 | ||
|
|
938ace91c1 | ||
|
|
175cfad81c | ||
|
|
2f399dbb64 | ||
|
|
27558b85af | ||
|
|
bcc1f3fa65 | ||
|
|
fd92a86c5e | ||
|
|
3b95d369b8 | ||
|
|
a12920d801 | ||
|
|
2dd63df533 | ||
|
|
cea1aa5028 | ||
|
|
54b96d4e3a | ||
|
|
a470136476 | ||
|
|
7d35cb08dd | ||
|
|
1f03f1032e | ||
|
|
9e2b55a249 | ||
|
|
0fbb94cd72 | ||
|
|
004b3f8574 | ||
|
|
7d1931dd17 | ||
|
|
8b7f41afa7 | ||
|
|
4bc0832865 | ||
|
|
a66c6d5f40 | ||
|
|
33b7cae24d | ||
|
|
47f5c88ef2 | ||
|
|
ffe382aee2 | ||
|
|
919f71ee78 | ||
|
|
404d4476ae | ||
|
|
f2b243cd5f | ||
|
|
c2fae41355 | ||
|
|
8fda2cde9e | ||
|
|
930380cdce | ||
|
|
5b788ffe15 | ||
|
|
521c2bdde5 | ||
|
|
eee73b1218 | ||
|
|
87d6da26c9 | ||
|
|
2029cd5cd2 | ||
|
|
36be752ee6 | ||
|
|
5b3586789f | ||
|
|
6ce670e643 | ||
|
|
dd70e8139c | ||
|
|
3ac0936d1a | ||
|
|
1477bacf6a | ||
|
|
d339a18901 | ||
|
|
f9460416d9 | ||
|
|
a9112cf3da | ||
|
|
c873b49700 | ||
|
|
3c553e37d8 | ||
|
|
0c47fbb1f7 | ||
|
|
476138ef53 | ||
|
|
385ca4f0fa | ||
|
|
46fd642789 | ||
|
|
e48249c7c9 | ||
|
|
9e8535e97e | ||
|
|
a794c63a5a | ||
|
|
f3610a46a2 | ||
|
|
20fd2cf6e3 | ||
|
|
7bf345d09d | ||
|
|
17e9560449 | ||
|
|
c02e6a565e | ||
|
|
7fbc9b9bde | ||
|
|
416e97d488 | ||
|
|
753060d9f3 | ||
|
|
972c53000c | ||
|
|
2b948a49a0 | ||
|
|
7999548738 | ||
|
|
d4d13b793f | ||
|
|
210b6f0d89 | ||
|
|
7f5894b274 | ||
|
|
2dc24ab945 | ||
|
|
2f153c9974 |
2
.github/workflows/flatpak-builder.yml
vendored
2
.github/workflows/flatpak-builder.yml
vendored
@@ -13,6 +13,6 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: flatpak/flatpak-github-actions/flatpak-builder@v6
|
||||
with:
|
||||
bundle: Alpaca.flatpak
|
||||
bundle: com.jeffser.Alpaca.flatpak
|
||||
manifest-path: com.jeffser.Alpaca.json
|
||||
cache-key: flatpak-builder-${{ github.sha }}
|
||||
|
||||
4
CODE_OF_CONDUCT.md
Normal file
4
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,4 @@
|
||||
Alpaca follows [GNOME's code of conduct](https://conduct.gnome.org/), please make sure to read it before interacting in any way with this repository.
|
||||
To report any misconduct please reach out via private message on
|
||||
- X (formally Twitter): [@jeffrysamuer](https://x.com/jeffrysamuer)
|
||||
- Mastodon: [@jeffser@floss.social](https://floss.social/@jeffser)
|
||||
30
CONTRIBUTING.md
Normal file
30
CONTRIBUTING.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# Contributing Rules
|
||||
|
||||
## Translations
|
||||
|
||||
If you want to translate or contribute on existing translations please read [this discussion](https://github.com/Jeffser/Alpaca/discussions/153).
|
||||
|
||||
## Code
|
||||
|
||||
1) Before contributing code make sure there's an open [issue](https://github.com/Jeffser/Alpaca/issues) for that particular problem or feature.
|
||||
2) Ask to contribute on the responses to the issue.
|
||||
3) Wait for [my](https://github.com/Jeffser) approval, I might have already started working on that issue.
|
||||
4) Test your code before submitting a pull request.
|
||||
|
||||
## Q&A
|
||||
|
||||
### Do I need to comment my code?
|
||||
|
||||
There's no need to add comments if the code is easy to read by itself.
|
||||
|
||||
### What if I need help or I don't understand the existing code?
|
||||
|
||||
You can reach out on the issue, I'll try to answer as soon as possible.
|
||||
|
||||
### What IDE should I use?
|
||||
|
||||
I use Gnome Builder but you can use whatever you want.
|
||||
|
||||
### Can I be credited?
|
||||
|
||||
You might be credited on the GitHub repository in the [thanks](https://github.com/Jeffser/Alpaca/blob/main/README.md#thanks) section of the README.
|
||||
@@ -46,6 +46,7 @@ Language | Contributors
|
||||
🇳🇴 Norwegian | [CounterFlow64](https://github.com/CounterFlow64)
|
||||
🇮🇳 Bengali | [Aritra Saha](https://github.com/olumolu)
|
||||
🇨🇳 Simplified Chinese | [Yuehao Sui](https://github.com/8ar10der) , [Aleksana](https://github.com/Aleksanaa)
|
||||
🇮🇳 Hindi | [Aritra Saha](https://github.com/olumolu)
|
||||
|
||||
---
|
||||
|
||||
|
||||
12
SECURITY.md
Normal file
12
SECURITY.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Packaging
|
||||
|
||||
Alpaca only supports [Flatpak](https://flatpak.org/) packaging officially, any other packaging methods might not behave as expected.
|
||||
|
||||
## Official Versions
|
||||
|
||||
The only ways Alpaca is being distributed officially are:
|
||||
|
||||
- [Alpaca's GitHub Repository Releases Page](https://github.com/Jeffser/Alpaca/releases)
|
||||
- [Flathub](https://flathub.org/apps/com.jeffser.Alpaca)
|
||||
@@ -70,9 +70,7 @@
|
||||
<caption>Multiple models being downloaded</caption>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
<content_rating type="oars-1.1">
|
||||
<content_attribute id="money-purchasing">mild</content_attribute>
|
||||
</content_rating>
|
||||
<content_rating type="oars-1.1" />
|
||||
<url type="bugtracker">https://github.com/Jeffser/Alpaca/issues</url>
|
||||
<url type="homepage">https://jeffser.com/alpaca/</url>
|
||||
<url type="donation">https://github.com/sponsors/Jeffser</url>
|
||||
@@ -80,6 +78,59 @@
|
||||
<url type="contribute">https://github.com/Jeffser/Alpaca/discussions/154</url>
|
||||
<url type="vcs-browser">https://github.com/Jeffser/Alpaca</url>
|
||||
<releases>
|
||||
<release version="1.1.1" date="2024-08-12">
|
||||
<url type="details">https://github.com/Jeffser/Alpaca/releases/tag/1.1.1</url>
|
||||
<description>
|
||||
<p>New</p>
|
||||
<ul>
|
||||
<li>New duplicate chat option</li>
|
||||
<li>Changed model selector appearance</li>
|
||||
<li>Message entry is focused on launch and chat change</li>
|
||||
<li>Message is focused when it's being edited</li>
|
||||
<li>Added loading spinner when regenerating a message</li>
|
||||
<li>Added Ollama debugging to 'About Alpaca' dialog</li>
|
||||
<li>Changed YouTube transcription dialog appearance and behavior</li>
|
||||
</ul>
|
||||
<p>Fixes</p>
|
||||
<ul>
|
||||
<li>CTRL+W and CTRL+Q stops local instance before closing the app</li>
|
||||
<li>Changed appearance of 'Open Model Manager' button on welcome screen</li>
|
||||
<li>Fixed message generation not working consistently</li>
|
||||
<li>Fixed message edition not working consistently</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="1.1.0" date="2024-08-10">
|
||||
<url type="details">https://github.com/Jeffser/Alpaca/releases/tag/1.1.0</url>
|
||||
<description>
|
||||
<p>New</p>
|
||||
<ul>
|
||||
<li>Model manager opens faster</li>
|
||||
<li>Delete chat option in secondary menu</li>
|
||||
<li>New model selector popup</li>
|
||||
<li>Standard shortcuts</li>
|
||||
<li>Model manager is navigable with keyboard</li>
|
||||
<li>Changed sidebar collapsing behavior</li>
|
||||
<li>Focus indicators on messages</li>
|
||||
<li>Welcome screen</li>
|
||||
<li>Give message entry focus at launch</li>
|
||||
<li>Generally better code</li>
|
||||
</ul>
|
||||
<p>Fixes</p>
|
||||
<ul>
|
||||
<li>Better width for dialogs</li>
|
||||
<li>Better compatibility with screen readers</li>
|
||||
<li>Fixed message regenerator</li>
|
||||
<li>Removed 'Featured models' from welcome dialog</li>
|
||||
<li>Added default buttons to dialogs</li>
|
||||
<li>Fixed import / export of chats</li>
|
||||
<li>Changed Python2 title to Python on code blocks</li>
|
||||
<li>Prevent regeneration of title when the user changed it to a custom title</li>
|
||||
<li>Show date on stopped messages</li>
|
||||
<li>Fix clear chat error</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="1.0.6" date="2024-08-04">
|
||||
<url type="details">https://github.com/Jeffser/Alpaca/releases/tag/1.0.6</url>
|
||||
<description>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
project('Alpaca', 'c',
|
||||
version: '1.0.6',
|
||||
version: '1.1.1',
|
||||
meson_version: '>= 0.62.0',
|
||||
default_options: [ 'warning_level=2', 'werror=false', ],
|
||||
)
|
||||
|
||||
@@ -4,4 +4,5 @@ pt_BR
|
||||
fr
|
||||
nb_NO
|
||||
bn
|
||||
zh_CN
|
||||
zh_CN
|
||||
hi
|
||||
|
||||
1024
po/alpaca.pot
1024
po/alpaca.pot
File diff suppressed because it is too large
Load Diff
1046
po/nb_NO.po
1046
po/nb_NO.po
File diff suppressed because it is too large
Load Diff
1016
po/pt_BR.po
1016
po/pt_BR.po
File diff suppressed because it is too large
Load Diff
1044
po/zh_CN.po
1044
po/zh_CN.po
File diff suppressed because it is too large
Load Diff
@@ -29,6 +29,7 @@
|
||||
<file alias="icons/scalable/status/edit-symbolic.svg">icons/edit-symbolic.svg</file>
|
||||
<file alias="icons/scalable/status/image-missing-symbolic.svg">icons/image-missing-symbolic.svg</file>
|
||||
<file alias="icons/scalable/status/update-symbolic.svg">icons/update-symbolic.svg</file>
|
||||
<file alias="icons/scalable/status/down-symbolic.svg">icons/down-symbolic.svg</file>
|
||||
<file preprocess="xml-stripblanks">window.ui</file>
|
||||
<file preprocess="xml-stripblanks">gtk/help-overlay.ui</file>
|
||||
</gresource>
|
||||
|
||||
@@ -28,6 +28,7 @@ def clear_chat(self):
|
||||
dialog.add_response("cancel", _("Cancel"))
|
||||
dialog.add_response("clear", _("Clear"))
|
||||
dialog.set_response_appearance("clear", Adw.ResponseAppearance.DESTRUCTIVE)
|
||||
dialog.set_default_response("clear")
|
||||
dialog.choose(
|
||||
parent = self,
|
||||
cancellable = None,
|
||||
@@ -49,6 +50,7 @@ def delete_chat(self, chat_name):
|
||||
dialog.add_response("cancel", _("Cancel"))
|
||||
dialog.add_response("delete", _("Delete"))
|
||||
dialog.set_response_appearance("delete", Adw.ResponseAppearance.DESTRUCTIVE)
|
||||
dialog.set_default_response("delete")
|
||||
dialog.choose(
|
||||
parent = self,
|
||||
cancellable = None,
|
||||
@@ -74,10 +76,10 @@ def rename_chat(self, chat_name, label_element):
|
||||
extra_child=entry,
|
||||
close_response="cancel"
|
||||
)
|
||||
entry.connect("activate", lambda dialog, old_chat_name=chat_name, entry=entry, label_element=label_element: rename_chat_response(self, dialog, None, old_chat_name, entry, label_element))
|
||||
dialog.add_response("cancel", _("Cancel"))
|
||||
dialog.add_response("rename", _("Rename"))
|
||||
dialog.set_response_appearance("rename", Adw.ResponseAppearance.SUGGESTED)
|
||||
dialog.set_default_response("rename")
|
||||
dialog.choose(
|
||||
parent = self,
|
||||
cancellable = None,
|
||||
@@ -102,10 +104,10 @@ def new_chat(self):
|
||||
extra_child=entry,
|
||||
close_response="cancel"
|
||||
)
|
||||
entry.connect("activate", lambda dialog, entry: new_chat_response(self, dialog, None, entry))
|
||||
dialog.add_response("cancel", _("Cancel"))
|
||||
dialog.add_response("create", _("Create"))
|
||||
dialog.set_response_appearance("create", Adw.ResponseAppearance.SUGGESTED)
|
||||
dialog.set_default_response("create")
|
||||
dialog.choose(
|
||||
parent = self,
|
||||
cancellable = None,
|
||||
@@ -128,6 +130,7 @@ def stop_pull_model(self, model_name):
|
||||
dialog.add_response("cancel", _("Cancel"))
|
||||
dialog.add_response("stop", _("Stop"))
|
||||
dialog.set_response_appearance("stop", Adw.ResponseAppearance.DESTRUCTIVE)
|
||||
dialog.set_default_response("stop")
|
||||
dialog.choose(
|
||||
parent = self.manage_models_dialog,
|
||||
cancellable = None,
|
||||
@@ -149,6 +152,7 @@ def delete_model(self, model_name):
|
||||
dialog.add_response("cancel", _("Cancel"))
|
||||
dialog.add_response("delete", _("Delete"))
|
||||
dialog.set_response_appearance("delete", Adw.ResponseAppearance.DESTRUCTIVE)
|
||||
dialog.set_default_response("delete")
|
||||
dialog.choose(
|
||||
parent = self.manage_models_dialog,
|
||||
cancellable = None,
|
||||
@@ -171,6 +175,7 @@ def remove_attached_file(self, name):
|
||||
dialog.add_response("cancel", _("Cancel"))
|
||||
dialog.add_response("remove", _("Remove"))
|
||||
dialog.set_response_appearance("remove", Adw.ResponseAppearance.DESTRUCTIVE)
|
||||
dialog.set_default_response("remove")
|
||||
dialog.choose(
|
||||
parent = self,
|
||||
cancellable = None,
|
||||
@@ -210,11 +215,11 @@ def reconnect_remote(self, current_url, current_bearer_token):
|
||||
body=_("The remote instance has disconnected"),
|
||||
extra_child=container
|
||||
)
|
||||
#entry.connect("activate", lambda entry, dialog: reconnect_remote_response(self, dialog, None, entry))
|
||||
dialog.add_response("close", _("Close Alpaca"))
|
||||
dialog.add_response("local", _("Use local instance"))
|
||||
dialog.add_response("remote", _("Connect"))
|
||||
dialog.set_response_appearance("remote", Adw.ResponseAppearance.SUGGESTED)
|
||||
dialog.set_default_response("remote")
|
||||
dialog.choose(
|
||||
parent = self,
|
||||
cancellable = None,
|
||||
@@ -243,6 +248,7 @@ def create_model_from_existing(self):
|
||||
dialog.add_response("cancel", _("Cancel"))
|
||||
dialog.add_response("accept", _("Accept"))
|
||||
dialog.set_response_appearance("accept", Adw.ResponseAppearance.SUGGESTED)
|
||||
dialog.set_default_response("accept")
|
||||
dialog.choose(
|
||||
parent = self,
|
||||
cancellable = None,
|
||||
@@ -271,7 +277,7 @@ def create_model_from_name_response(self, dialog, task, entry):
|
||||
|
||||
def create_model_from_name(self):
|
||||
entry = Gtk.Entry()
|
||||
entry.get_delegate().connect("insert-text", self.check_alphanumeric)
|
||||
entry.get_delegate().connect("insert-text", lambda *_ : self.check_alphanumeric(*_, ['-', '.', ':', '_', '/']))
|
||||
dialog = Adw.AlertDialog(
|
||||
heading=_("Pull Model"),
|
||||
body=_("Input the name of the model in this format\nname:tag"),
|
||||
@@ -280,6 +286,7 @@ def create_model_from_name(self):
|
||||
dialog.add_response("cancel", _("Cancel"))
|
||||
dialog.add_response("accept", _("Accept"))
|
||||
dialog.set_response_appearance("accept", Adw.ResponseAppearance.SUGGESTED)
|
||||
dialog.set_default_response("accept")
|
||||
dialog.choose(
|
||||
parent = self,
|
||||
cancellable = None,
|
||||
@@ -323,7 +330,7 @@ def youtube_caption_response(self, dialog, task, video_url, caption_drop_down):
|
||||
yt = YouTube(video_url)
|
||||
text = "{}\n{}\n{}\n\n".format(yt.title, yt.author, yt.watch_url)
|
||||
selected_caption = caption_drop_down.get_selected_item().get_string()
|
||||
for event in yt.captions[selected_caption.split(' | ')[1]].json_captions['events']:
|
||||
for event in yt.captions[selected_caption.split('(')[1][:-1]].json_captions['events']:
|
||||
text += "{}\n".format(event['segs'][0]['utf8'].replace('\n', '\\n'))
|
||||
if not os.path.exists(os.path.join(self.cache_dir, 'tmp/youtube')):
|
||||
os.makedirs(os.path.join(self.cache_dir, 'tmp/youtube'))
|
||||
@@ -341,9 +348,9 @@ def youtube_caption(self, video_url):
|
||||
return
|
||||
caption_list = Gtk.StringList()
|
||||
for caption in captions:
|
||||
caption_list.append("{} | {}".format(caption.name, caption.code))
|
||||
caption_list.append("{} ({})".format(caption.name.title(), caption.code))
|
||||
caption_drop_down = Gtk.DropDown(
|
||||
enable_search=True,
|
||||
enable_search=len(captions) > 10,
|
||||
model=caption_list
|
||||
)
|
||||
dialog = Adw.AlertDialog(
|
||||
@@ -355,6 +362,7 @@ def youtube_caption(self, video_url):
|
||||
dialog.add_response("cancel", _("Cancel"))
|
||||
dialog.add_response("accept", _("Accept"))
|
||||
dialog.set_response_appearance("accept", Adw.ResponseAppearance.SUGGESTED)
|
||||
dialog.set_default_response("accept")
|
||||
dialog.choose(
|
||||
parent = self,
|
||||
cancellable = None,
|
||||
@@ -393,6 +401,7 @@ def attach_website(self, url):
|
||||
dialog.add_response("cancel", _("Cancel"))
|
||||
dialog.add_response("accept", _("Accept"))
|
||||
dialog.set_response_appearance("accept", Adw.ResponseAppearance.SUGGESTED)
|
||||
dialog.set_default_response("accept")
|
||||
dialog.choose(
|
||||
parent = self,
|
||||
cancellable = None,
|
||||
|
||||
2
src/icons/down-symbolic.svg
Normal file
2
src/icons/down-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"><path d="m 2.292969 6.707031 l 5 5 c 0.390625 0.390625 1.023437 0.390625 1.414062 0 l 5 -5 c 0.390625 -0.390625 0.390625 -1.023437 0 -1.414062 s -1.023437 -0.390625 -1.414062 0 l -4.292969 4.292969 l -4.292969 -4.292969 c -0.390625 -0.390625 -1.023437 -0.390625 -1.414062 0 s -0.390625 1.023437 0 1.414062 z m 0 0" fill="#222222" fill-rule="evenodd"/></svg>
|
||||
|
After Width: | Height: | Size: 484 B |
@@ -3,6 +3,7 @@
|
||||
Handles running, stopping and resetting the integrated Ollama instance
|
||||
"""
|
||||
import subprocess
|
||||
import threading
|
||||
import os
|
||||
from time import sleep
|
||||
from logging import getLogger
|
||||
@@ -15,6 +16,14 @@ instance = None
|
||||
port = 11435
|
||||
overrides = {}
|
||||
|
||||
def log_output(pipe):
|
||||
with open(os.path.join(data_dir, 'tmp.log'), 'a') as f:
|
||||
with pipe:
|
||||
for line in iter(pipe.readline, ''):
|
||||
print(line, end='')
|
||||
f.write(line)
|
||||
f.flush()
|
||||
|
||||
def start():
|
||||
if not os.path.isdir(os.path.join(cache_dir, 'tmp/ollama')):
|
||||
os.mkdir(os.path.join(cache_dir, 'tmp/ollama'))
|
||||
@@ -23,7 +32,9 @@ def start():
|
||||
params["OLLAMA_HOST"] = f"127.0.0.1:{port}" # You can't change this directly sorry :3
|
||||
params["HOME"] = data_dir
|
||||
params["TMPDIR"] = os.path.join(cache_dir, 'tmp/ollama')
|
||||
instance = subprocess.Popen(["ollama", "serve"], env={**os.environ, **params}, stderr=subprocess.PIPE, text=True)
|
||||
instance = subprocess.Popen(["ollama", "serve"], env={**os.environ, **params}, stderr=subprocess.PIPE, stdout=subprocess.PIPE, text=True)
|
||||
threading.Thread(target=log_output, args=(instance.stdout,)).start()
|
||||
threading.Thread(target=log_output, args=(instance.stderr,)).start()
|
||||
logger.info("Starting Alpaca's Ollama instance...")
|
||||
logger.debug(params)
|
||||
sleep(1)
|
||||
|
||||
@@ -43,7 +43,8 @@ translators = [
|
||||
'CounterFlow64 (Norwegian) https://github.com/CounterFlow64',
|
||||
'Aritra Saha (Bengali) https://github.com/olumolu',
|
||||
'Yuehao Sui (Simplified Chinese) https://github.com/8ar10der',
|
||||
'Aleksana (Simplified Chinese) https://github.com/Aleksanaa'
|
||||
'Aleksana (Simplified Chinese) https://github.com/Aleksanaa',
|
||||
'Aritra Saha (Hindi) https://github.com/olumolu'
|
||||
]
|
||||
|
||||
class AlpacaApplication(Adw.Application):
|
||||
@@ -52,7 +53,7 @@ class AlpacaApplication(Adw.Application):
|
||||
def __init__(self, version):
|
||||
super().__init__(application_id='com.jeffser.Alpaca',
|
||||
flags=Gio.ApplicationFlags.DEFAULT_FLAGS)
|
||||
self.create_action('quit', lambda *_: self.quit(), ['<primary>w'])
|
||||
self.create_action('quit', lambda *_: self.props.active_window.closing_app(None), ['<primary>w', '<primary>q'])
|
||||
self.create_action('preferences', lambda *_: AlpacaWindow.show_preferences_dialog(self.props.active_window), ['<primary>comma'])
|
||||
self.create_action('about', self.on_about_action)
|
||||
self.version = version
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
.message_input_scroll_window > * {
|
||||
box-shadow: none;
|
||||
border-width: 0;
|
||||
}
|
||||
.message_text_view, .modelfile_textview {
|
||||
background-color: rgba(0,0,0,0);
|
||||
}
|
||||
@@ -12,3 +8,23 @@
|
||||
border-radius: 5px;
|
||||
padding: 5px;
|
||||
}
|
||||
.model_list_box {
|
||||
padding: 0;
|
||||
}
|
||||
.manage_models_button {
|
||||
padding: 6px 8px 6px 8px;
|
||||
font-weight: 400;
|
||||
}
|
||||
.model_list_box > * {
|
||||
margin: 0;
|
||||
}
|
||||
.user_message, .response_message {
|
||||
padding: 12px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
.user_message:focus, .response_message:focus, .editing_message_textview:focus, .code_block:focus {
|
||||
box-shadow: 0 0 1px 2px mix(@accent_color, @window_bg_color, 0.5);
|
||||
}
|
||||
.model_popover {
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
543
src/window.py
543
src/window.py
@@ -19,7 +19,7 @@
|
||||
"""
|
||||
Handles the main window
|
||||
"""
|
||||
import json, threading, os, re, base64, sys, gettext, uuid, shutil, tarfile, tempfile, logging
|
||||
import json, threading, os, re, base64, sys, gettext, uuid, shutil, tarfile, tempfile, logging, random
|
||||
from io import BytesIO
|
||||
from PIL import Image
|
||||
from pypdf import PdfReader
|
||||
@@ -64,6 +64,30 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
||||
pulling_models = {}
|
||||
chats = {"chats": {_("New Chat"): {"messages": {}}}, "selected_chat": "New Chat", "order": []}
|
||||
attachments = {}
|
||||
possible_prompts = [
|
||||
"What can you do?",
|
||||
"Give me a pancake recipe",
|
||||
"Why is the sky blue?",
|
||||
"Can you tell me a joke?",
|
||||
"Give me a healthy breakfast recipe",
|
||||
"How to make a pizza",
|
||||
"Can you write a poem?",
|
||||
"Can you write a story?",
|
||||
"What is GNU-Linux?",
|
||||
"Which is the best Linux distro?",
|
||||
"Why is Pluto not a planet?",
|
||||
"What is a black-hole?",
|
||||
"Tell me how to stay fit",
|
||||
"Write a conversation between sun and Earth",
|
||||
"Why is the grass green?",
|
||||
"Write an Haïku about AI",
|
||||
"What is the meaning of life?",
|
||||
"Explain quantum physics in simple terms",
|
||||
"Explain the theory of relativity",
|
||||
"Explain how photosynthesis works",
|
||||
"Recommend a film about nature",
|
||||
"What is nostalgia?"
|
||||
]
|
||||
|
||||
#Override elements
|
||||
override_HSA_OVERRIDE_GFX_VERSION = Gtk.Template.Child()
|
||||
@@ -107,8 +131,6 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
||||
file_filter_gguf = Gtk.Template.Child()
|
||||
file_filter_attachments = Gtk.Template.Child()
|
||||
attachment_button = Gtk.Template.Child()
|
||||
model_drop_down = Gtk.Template.Child()
|
||||
model_string_list = Gtk.Template.Child()
|
||||
chat_right_click_menu = Gtk.Template.Child()
|
||||
model_tag_list_box = Gtk.Template.Child()
|
||||
navigation_view_manage_models = Gtk.Template.Child()
|
||||
@@ -118,6 +140,10 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
||||
model_searchbar = Gtk.Template.Child()
|
||||
no_results_page = Gtk.Template.Child()
|
||||
model_link_button = Gtk.Template.Child()
|
||||
model_list_box = Gtk.Template.Child()
|
||||
model_popover = Gtk.Template.Child()
|
||||
model_selector_button = Gtk.Template.Child()
|
||||
chat_welcome_screen : Adw.StatusPage = None
|
||||
|
||||
manage_models_dialog = Gtk.Template.Child()
|
||||
pulling_model_list_box = Gtk.Template.Child()
|
||||
@@ -136,38 +162,29 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
||||
|
||||
style_manager = Adw.StyleManager()
|
||||
|
||||
@Gtk.Template.Callback()
|
||||
def verify_if_image_can_be_used(self, pspec=None, user_data=None):
|
||||
logger.debug("Verifying if image can be used")
|
||||
if self.model_drop_down.get_selected_item() == None:
|
||||
return True
|
||||
selected = self.convert_model_name(self.model_drop_down.get_selected_item().get_string(), 1).split(":")[0]
|
||||
if selected in [key for key, value in self.available_models.items() if value["image"]]:
|
||||
for name, content in self.attachments.items():
|
||||
if content['type'] == 'image':
|
||||
content['button'].set_css_classes(["flat"])
|
||||
return True
|
||||
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)
|
||||
self.loading_spinner.get_parent().remove(self.loading_spinner)
|
||||
message_id = list(self.chats["chats"][self.chats["selected_chat"]]["messages"])[-1]
|
||||
formated_date = GLib.markup_escape_text(self.generate_datetime_format(datetime.strptime(self.chats["chats"][self.chats["selected_chat"]]["messages"][message_id]["date"], '%Y/%m/%d %H:%M:%S')))
|
||||
text = f"\n\n{self.convert_model_name(self.chats['chats'][self.chats['selected_chat']]['messages'][message_id]['model'], 0)}\n<small>{formated_date}</small>"
|
||||
self.bot_message.insert_markup(self.bot_message.get_end_iter(), text, len(text.encode('utf-8')))
|
||||
self.add_code_blocks()
|
||||
self.bot_message_button_container.set_visible(True)
|
||||
self.toggle_ui_sensitive(True)
|
||||
self.switch_send_stop_button()
|
||||
self.switch_send_stop_button(True)
|
||||
self.bot_message = None
|
||||
self.bot_message_box = None
|
||||
self.bot_message_view = None
|
||||
self.bot_message_button_container = None
|
||||
self.save_history()
|
||||
|
||||
@Gtk.Template.Callback()
|
||||
def send_message(self, button=None):
|
||||
if self.editing_message:
|
||||
self.editing_message["button_container"].set_visible(True)
|
||||
self.editing_message["text_view"].set_css_classes(["flat"])
|
||||
self.editing_message["text_view"].set_css_classes(["flat", "user_message"])
|
||||
self.editing_message["text_view"].set_cursor_visible(False)
|
||||
self.editing_message["text_view"].set_editable(False)
|
||||
buffer = self.editing_message["text_view"].get_buffer()
|
||||
@@ -178,8 +195,7 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
||||
self.editing_message = None
|
||||
self.save_history()
|
||||
self.show_toast(_("Message edited successfully"), self.main_overlay)
|
||||
|
||||
if self.bot_message or self.get_focus() not in (self.message_text_view, self.send_button):
|
||||
if button and not button.get_visible():
|
||||
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
|
||||
@@ -191,7 +207,7 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
||||
self.chats['order'].remove(self.chats['selected_chat'])
|
||||
self.chats['order'].insert(0, self.chats['selected_chat'])
|
||||
self.save_history()
|
||||
current_model = self.convert_model_name(self.model_drop_down.get_selected_item().get_string(), 1)
|
||||
current_model = self.get_current_model(1)
|
||||
if current_model is None:
|
||||
self.show_toast(_("Please select a model before chatting"), self.main_overlay)
|
||||
return
|
||||
@@ -232,7 +248,7 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
||||
"options": {"temperature": self.model_tweaks["temperature"], "seed": self.model_tweaks["seed"]},
|
||||
"keep_alive": f"{self.model_tweaks['keep_alive']}m"
|
||||
}
|
||||
self.switch_send_stop_button()
|
||||
self.switch_send_stop_button(False)
|
||||
self.toggle_ui_sensitive(False)
|
||||
|
||||
#self.attachments[name] = {"path": file_path, "type": file_type, "content": content}
|
||||
@@ -245,6 +261,9 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
||||
bot_id=self.generate_uuid()
|
||||
self.show_message("", True, message_id=bot_id)
|
||||
|
||||
if self.chat_welcome_screen:
|
||||
self.chat_container.remove(self.chat_welcome_screen)
|
||||
|
||||
thread = threading.Thread(target=self.run_message, args=(data['messages'], data['model'], bot_id))
|
||||
thread.start()
|
||||
if len(data['messages']) == 1:
|
||||
@@ -286,13 +305,14 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
||||
if row and row.get_child().get_name() != self.chats["selected_chat"]:
|
||||
self.chats["selected_chat"] = row.get_child().get_name()
|
||||
self.load_history_into_chat()
|
||||
if len(self.chats["chats"][self.chats["selected_chat"]]["messages"].keys()) > 0:
|
||||
if len(self.chats["chats"][self.chats["selected_chat"]]["messages"]) > 0:
|
||||
last_model_used = self.chats["chats"][self.chats["selected_chat"]]["messages"][list(self.chats["chats"][self.chats["selected_chat"]]["messages"].keys())[-1]]["model"]
|
||||
last_model_used = self.convert_model_name(last_model_used, 0)
|
||||
for i in range(self.model_string_list.get_n_items()):
|
||||
if self.model_string_list.get_string(i) == last_model_used:
|
||||
self.model_drop_down.set_selected(i)
|
||||
for i, m in enumerate(self.local_models):
|
||||
if m == last_model_used:
|
||||
self.model_list_box.select_row(self.model_list_box.get_row_at_index(i))
|
||||
break
|
||||
else:
|
||||
self.load_history_into_chat()
|
||||
self.save_history()
|
||||
|
||||
@Gtk.Template.Callback()
|
||||
@@ -303,7 +323,7 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
||||
self.remote_url = entry.get_text()
|
||||
logger.debug(f"Changing remote url: {self.remote_url}")
|
||||
if self.run_remote:
|
||||
connection_handler.url = self.remote_url
|
||||
connection_handler.URL = self.remote_url
|
||||
if self.verify_connection() == False:
|
||||
entry.set_css_classes(["error"])
|
||||
self.show_toast(_("Failed to connect to server"), self.preferences_dialog)
|
||||
@@ -314,23 +334,11 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
||||
self.save_server_config()
|
||||
return
|
||||
if self.remote_url and self.run_remote:
|
||||
connection_handler.url = self.remote_url
|
||||
connection_handler.URL = self.remote_url
|
||||
if self.verify_connection() == False:
|
||||
entry.set_css_classes(["error"])
|
||||
self.show_toast(_("Failed to connect to server"), self.preferences_dialog)
|
||||
|
||||
@Gtk.Template.Callback()
|
||||
def pull_featured_model(self, button):
|
||||
action_row = button.get_parent().get_parent().get_parent()
|
||||
button.get_parent().remove(button)
|
||||
model = f"{action_row.get_title().lower()}:latest"
|
||||
action_row.set_subtitle(_("Pulling in the background..."))
|
||||
spinner = Gtk.Spinner()
|
||||
spinner.set_spinning(True)
|
||||
action_row.add_suffix(spinner)
|
||||
action_row.set_sensitive(False)
|
||||
self.pull_model(model)
|
||||
|
||||
@Gtk.Template.Callback()
|
||||
def closing_app(self, user_data):
|
||||
if self.get_hide_on_close():
|
||||
@@ -338,6 +346,7 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
||||
else:
|
||||
logger.info("Closing app...")
|
||||
local_instance.stop()
|
||||
self.get_application().quit()
|
||||
|
||||
@Gtk.Template.Callback()
|
||||
def model_spin_changed(self, spin):
|
||||
@@ -427,26 +436,78 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
||||
self.available_model_list_box.set_visible(True)
|
||||
self.no_results_page.set_visible(False)
|
||||
|
||||
def manage_models_button_activate(self, button=None):
|
||||
logger.debug(f"Managing models")
|
||||
self.update_list_local_models()
|
||||
if len(self.chats["chats"][self.chats["selected_chat"]]["messages"].keys()) > 0:
|
||||
last_model_used = self.chats["chats"][self.chats["selected_chat"]]["messages"][list(self.chats["chats"][self.chats["selected_chat"]]["messages"].keys())[-1]]["model"]
|
||||
last_model_used = self.convert_model_name(last_model_used, 0)
|
||||
for i in range(self.model_string_list.get_n_items()):
|
||||
if self.model_string_list.get_string(i) == last_model_used:
|
||||
self.model_drop_down.set_selected(i)
|
||||
break
|
||||
self.manage_models_dialog.present(self)
|
||||
@Gtk.Template.Callback()
|
||||
def close_model_popup(self, *_):
|
||||
self.model_popover.hide()
|
||||
|
||||
@Gtk.Template.Callback()
|
||||
def change_model(self, listbox=None, row=None):
|
||||
if not row:
|
||||
current_model = self.model_selector_button.get_name()
|
||||
if current_model != 'NO_MODEL':
|
||||
for i, m in enumerate(self.local_models):
|
||||
if m == current_model:
|
||||
self.model_list_box.select_row(self.model_list_box.get_row_at_index(i))
|
||||
return
|
||||
if len(self.local_models) > 0:
|
||||
self.model_list_box.select_row(self.model_list_box.get_row_at_index(0))
|
||||
return
|
||||
else:
|
||||
model_name = None
|
||||
else:
|
||||
model_name = row.get_child().get_label()
|
||||
button_content = Gtk.Box(
|
||||
spacing=10
|
||||
)
|
||||
button_content.append(
|
||||
Gtk.Label(
|
||||
label=model_name if model_name else _("Select a Model"),
|
||||
ellipsize=2
|
||||
)
|
||||
)
|
||||
button_content.append(
|
||||
Gtk.Image.new_from_icon_name("down-symbolic")
|
||||
)
|
||||
self.model_selector_button.set_name(row.get_name() if row else 'NO_MODEL')
|
||||
self.model_selector_button.set_child(button_content)
|
||||
self.close_model_popup()
|
||||
self.verify_if_image_can_be_used()
|
||||
|
||||
def verify_if_image_can_be_used(self):
|
||||
logger.debug("Verifying if image can be used")
|
||||
selected = self.get_current_model(1)
|
||||
if selected == None:
|
||||
return True
|
||||
selected = selected.split(":")[0]
|
||||
if selected in [key for key, value in self.available_models.items() if value["image"]]:
|
||||
for name, content in self.attachments.items():
|
||||
if content['type'] == 'image':
|
||||
content['button'].set_css_classes(["flat"])
|
||||
return True
|
||||
for name, content in self.attachments.items():
|
||||
if content['type'] == 'image':
|
||||
content['button'].set_css_classes(["flat", "error"])
|
||||
return False
|
||||
|
||||
def convert_model_name(self, name:str, mode:int) -> str: # mode=0 name:tag -> Name (tag) | mode=1 Name (tag) -> name:tag
|
||||
if mode == 0:
|
||||
return "{} ({})".format(name.split(":")[0].replace("-", " ").title(), name.split(":")[1])
|
||||
if mode == 1:
|
||||
return "{}:{}".format(name.split(" (")[0].replace(" ", "-").lower(), name.split(" (")[1][:-1])
|
||||
try:
|
||||
if mode == 0:
|
||||
return "{} ({})".format(name.split(":")[0].replace("-", " ").title(), name.split(":")[1])
|
||||
if mode == 1:
|
||||
return "{}:{}".format(name.split(" (")[0].replace(" ", "-").lower(), name.split(" (")[1][:-1])
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
def check_alphanumeric(self, editable, text, length, position):
|
||||
new_text = ''.join([char for char in text if char.isalnum() or char in ['-', '.', ':', '_']])
|
||||
def get_current_model(self, mode:int) -> str:
|
||||
if not self.model_list_box.get_selected_row():
|
||||
return None
|
||||
if mode == 0:
|
||||
return self.model_list_box.get_selected_row().get_child().get_label()
|
||||
if mode == 1:
|
||||
return self.model_list_box.get_selected_row().get_name()
|
||||
|
||||
def check_alphanumeric(self, editable, text, length, position, allowed_chars):
|
||||
new_text = ''.join([char for char in text if char.isalnum() or char in allowed_chars])
|
||||
if new_text != text:
|
||||
editable.stop_emission_by_name("insert-text")
|
||||
|
||||
@@ -455,7 +516,7 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
||||
modelfile_buffer.delete(modelfile_buffer.get_start_iter(), modelfile_buffer.get_end_iter())
|
||||
self.create_model_system.set_text('')
|
||||
if not file:
|
||||
response = connection_handler.simple_post(f"{connection_handler.url}/api/show", json.dumps({"name": self.convert_model_name(model, 1)}))
|
||||
response = connection_handler.simple_post(f"{connection_handler.URL}/api/show", json.dumps({"name": self.convert_model_name(model, 1)}))
|
||||
if response.status_code == 200:
|
||||
data = json.loads(response.text)
|
||||
modelfile = []
|
||||
@@ -500,6 +561,8 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
||||
if os.path.exists(os.path.join(self.data_dir, "chats", self.chats['selected_chat'], message_id)):
|
||||
shutil.rmtree(os.path.join(self.data_dir, "chats", self.chats['selected_chat'], message_id))
|
||||
self.save_history()
|
||||
if len(self.chats["chats"][self.chats["selected_chat"]]["messages"]) == 0:
|
||||
self.load_history_into_chat()
|
||||
|
||||
def copy_message(self, message_element):
|
||||
logger.debug("Copying message")
|
||||
@@ -529,6 +592,13 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
||||
text_view.set_cursor_visible(True)
|
||||
|
||||
self.editing_message = {"text_view": text_view, "id": message_id, "button_container": button_container, "footer": footer}
|
||||
if text_view.observe_controllers().get_n_items() == 8:
|
||||
print(text_view.observe_controllers().get_n_items())
|
||||
enter_key_controller = Gtk.EventControllerKey.new()
|
||||
enter_key_controller.connect("key-pressed", lambda controller, keyval, keycode, state: self.handle_enter_key() if keyval==Gdk.KEY_Return and not (state & Gdk.ModifierType.SHIFT_MASK) else None)
|
||||
text_view.add_controller(enter_key_controller)
|
||||
print(text_view.observe_controllers().get_n_items())
|
||||
self.set_focus(text_view)
|
||||
|
||||
def preview_file(self, file_path, file_type, presend_name):
|
||||
logger.debug(f"Previewing file: {file_path}")
|
||||
@@ -558,7 +628,7 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
||||
self.file_preview_text_view.set_visible(True)
|
||||
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))
|
||||
buffer.insert(buffer.get_start_iter(), content, len(content.encode('utf-8')))
|
||||
if file_type == 'youtube':
|
||||
self.file_preview_dialog.set_title(content.split('\n')[0])
|
||||
self.file_preview_open_button.set_name(content.split('\n')[2])
|
||||
@@ -593,6 +663,8 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
||||
return messages
|
||||
|
||||
def generate_chat_title(self, message, label_element):
|
||||
if not label_element.get_name().startswith(_("New Chat")):
|
||||
return
|
||||
logger.debug("Generating chat title")
|
||||
prompt = f"""
|
||||
Generate a title following these rules:
|
||||
@@ -605,11 +677,11 @@ Generate a title following these rules:
|
||||
```PROMPT
|
||||
{message['content']}
|
||||
```"""
|
||||
current_model = self.convert_model_name(self.model_drop_down.get_selected_item().get_string(), 1)
|
||||
current_model = self.get_current_model(1)
|
||||
data = {"model": current_model, "prompt": prompt, "stream": False}
|
||||
if 'images' in message:
|
||||
data["images"] = message['images']
|
||||
response = connection_handler.simple_post(f"{connection_handler.url}/api/generate", data=json.dumps(data))
|
||||
response = connection_handler.simple_post(f"{connection_handler.URL}/api/generate", data=json.dumps(data))
|
||||
|
||||
new_chat_name = json.loads(response.text)["response"].strip().removeprefix("Title: ").removeprefix("title: ").strip('\'"').replace('\n', ' ').title().replace('\'S', '\'s')
|
||||
new_chat_name = new_chat_name[:50] + (new_chat_name[50:] and '...')
|
||||
@@ -620,14 +692,11 @@ Generate a title following these rules:
|
||||
editable=False,
|
||||
focusable=True,
|
||||
wrap_mode= Gtk.WrapMode.WORD,
|
||||
margin_top=12,
|
||||
margin_bottom=12,
|
||||
margin_start=12,
|
||||
margin_end=12,
|
||||
hexpand=True,
|
||||
cursor_visible=False,
|
||||
css_classes=["flat"],
|
||||
css_classes=["flat", "response_message"] if bot else ["flat", "user_message"],
|
||||
)
|
||||
if not bot:
|
||||
message_text.update_property([4, 7, 1], [_("User message"), True, msg])
|
||||
message_buffer = message_text.get_buffer()
|
||||
message_buffer.insert(message_buffer.get_end_iter(), msg)
|
||||
if footer is not None:
|
||||
@@ -666,7 +735,8 @@ Generate a title following these rules:
|
||||
message_box = Gtk.Box(
|
||||
orientation=1,
|
||||
halign='fill',
|
||||
css_classes=[None if bot else "card"]
|
||||
css_classes=[None if bot else "card"],
|
||||
spacing=5
|
||||
)
|
||||
message_text.set_valign(Gtk.Align.CENTER)
|
||||
|
||||
@@ -696,6 +766,7 @@ Generate a title following these rules:
|
||||
name=os.path.join(self.data_dir, "chats", "{selected_chat}", message_id, image),
|
||||
tooltip_text=_("Image")
|
||||
)
|
||||
image_element.update_property([4], [_("Image")])
|
||||
button.connect("clicked", lambda button, file_path=path: self.preview_file(file_path, 'image', None))
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
@@ -722,6 +793,7 @@ Generate a title following these rules:
|
||||
css_classes=["flat", "chat_image_button"],
|
||||
tooltip_text=_("Missing Image")
|
||||
)
|
||||
image_texture.update_property([4], [_("Missing image")])
|
||||
button.connect("clicked", lambda button : self.show_toast(_("Missing image"), self.main_overlay))
|
||||
image_container.append(button)
|
||||
message_box.append(image_scroller)
|
||||
@@ -784,9 +856,8 @@ Generate a title following these rules:
|
||||
def update_list_local_models(self):
|
||||
logger.debug("Updating list of local models")
|
||||
self.local_models = []
|
||||
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)
|
||||
response = connection_handler.simple_get(f"{connection_handler.URL}/api/tags")
|
||||
self.model_list_box.remove_all()
|
||||
if response.status_code == 200:
|
||||
self.local_model_list_box.remove_all()
|
||||
if len(json.loads(response.text)['models']) == 0:
|
||||
@@ -810,9 +881,17 @@ Generate a title following these rules:
|
||||
model_row.add_suffix(button)
|
||||
self.local_model_list_box.append(model_row)
|
||||
|
||||
self.model_string_list.append(model_name)
|
||||
selector_row = Gtk.ListBoxRow(
|
||||
child = Gtk.Label(
|
||||
label=model_name, halign=1, hexpand=True
|
||||
),
|
||||
halign=0,
|
||||
hexpand=True,
|
||||
name=model["name"],
|
||||
tooltip_text=model_name
|
||||
)
|
||||
self.model_list_box.append(selector_row)
|
||||
self.local_models.append(model["name"])
|
||||
#self.verify_if_image_can_be_used()
|
||||
else:
|
||||
self.connection_error()
|
||||
|
||||
@@ -822,7 +901,7 @@ Generate a title following these rules:
|
||||
|
||||
def verify_connection(self):
|
||||
try:
|
||||
response = connection_handler.simple_get(f"{connection_handler.url}/api/tags")
|
||||
response = connection_handler.simple_get(f"{connection_handler.URL}/api/tags")
|
||||
if response.status_code == 200:
|
||||
self.save_server_config()
|
||||
self.update_list_local_models()
|
||||
@@ -845,7 +924,7 @@ Generate a title following these rules:
|
||||
parts.append({"type": "normal", "text": normal_text.strip()})
|
||||
language = match.group(1)
|
||||
code_text = match.group(2)
|
||||
parts.append({"type": "code", "text": code_text, "language": language})
|
||||
parts.append({"type": "code", "text": code_text, "language": 'python3' if language == 'python' else language})
|
||||
pos = end
|
||||
# Match code blocks without language
|
||||
no_lang_code_block_pattern = re.compile(r'`\n(.*?)\n`', re.DOTALL)
|
||||
@@ -883,10 +962,8 @@ Generate a title following these rules:
|
||||
editable=False,
|
||||
focusable=True,
|
||||
wrap_mode= Gtk.WrapMode.WORD,
|
||||
margin_top=12,
|
||||
margin_bottom=12,
|
||||
hexpand=True,
|
||||
css_classes=["flat"]
|
||||
css_classes=["flat", "response_message"]
|
||||
)
|
||||
message_buffer = message_text.get_buffer()
|
||||
|
||||
@@ -915,6 +992,7 @@ Generate a title following these rules:
|
||||
|
||||
if footer: message_buffer.insert_markup(message_buffer.get_end_iter(), footer, len(footer.encode('utf-8')))
|
||||
|
||||
message_text.update_property([4, 7, 1], [_("Response message"), False, message_buffer.get_text(message_buffer.get_start_iter(), message_buffer.get_end_iter(), False)])
|
||||
self.bot_message_box.append(message_text)
|
||||
elif part['type'] == 'code':
|
||||
language = None
|
||||
@@ -932,10 +1010,11 @@ Generate a title following these rules:
|
||||
buffer.set_style_scheme(source_style)
|
||||
source_view = GtkSource.View(
|
||||
auto_indent=True, indent_width=4, buffer=buffer, show_line_numbers=True,
|
||||
top_margin=6, bottom_margin=6, left_margin=12, right_margin=12
|
||||
top_margin=6, bottom_margin=6, left_margin=12, right_margin=12, css_classes=["code_block"]
|
||||
)
|
||||
source_view.update_property([4], [_("{}Code Block").format('{} '.format(language.get_name()) if language else "")])
|
||||
source_view.set_editable(False)
|
||||
code_block_box = Gtk.Box(css_classes=["card"], orientation=1, overflow=1)
|
||||
code_block_box = Gtk.Box(css_classes=["card", "code_block"], orientation=1, overflow=1)
|
||||
title_box = Gtk.Box(margin_start=12, margin_top=3, margin_bottom=3, margin_end=3)
|
||||
title_box.append(Gtk.Label(label=language.get_name() if language else _("Code Block"), hexpand=True, xalign=0))
|
||||
copy_button = Gtk.Button(icon_name="edit-copy-symbolic", css_classes=["flat", "circular"], tooltip_text=_("Copy Message"))
|
||||
@@ -982,10 +1061,9 @@ Generate a title following these rules:
|
||||
|
||||
def update_bot_message(self, data, message_id):
|
||||
if self.bot_message is None:
|
||||
self.save_history()
|
||||
sys.exit()
|
||||
vadjustment = self.chat_window.get_vadjustment()
|
||||
if message_id not in self.chats["chats"][self.chats["selected_chat"]]["messages"] or vadjustment.get_value() + 50 >= vadjustment.get_upper() - vadjustment.get_page_size():
|
||||
if not self.chats["chats"][self.chats["selected_chat"]]["messages"][message_id] or vadjustment.get_value() + 50 >= vadjustment.get_upper() - vadjustment.get_page_size():
|
||||
GLib.idle_add(vadjustment.set_value, vadjustment.get_upper())
|
||||
if 'done' in data and data['done']:
|
||||
formated_date = GLib.markup_escape_text(self.generate_datetime_format(datetime.strptime(self.chats["chats"][self.chats["selected_chat"]]["messages"][message_id]["date"], '%Y/%m/%d %H:%M:%S')))
|
||||
@@ -997,8 +1075,8 @@ Generate a title following these rules:
|
||||
first_paragraph = self.bot_message.get_text(self.bot_message.get_start_iter(), self.bot_message.get_end_iter(), False).split("\n")[0]
|
||||
GLib.idle_add(self.show_notification, self.chats["selected_chat"], first_paragraph[:100] + (first_paragraph[100:] and '...'), Gio.ThemedIcon.new("chat-message-new-symbolic"))
|
||||
else:
|
||||
if not self.chats["chats"][self.chats["selected_chat"]]["messages"][message_id]["content"] and self.loading_spinner:
|
||||
GLib.idle_add(self.chat_container.remove, self.loading_spinner)
|
||||
if self.loading_spinner:
|
||||
GLib.idle_add(self.loading_spinner.get_parent().remove, self.loading_spinner)
|
||||
self.loading_spinner = None
|
||||
GLib.idle_add(self.bot_message.insert, self.bot_message.get_end_iter(), data['message']['content'])
|
||||
self.chats["chats"][self.chats["selected_chat"]]["messages"][message_id]['content'] += data['message']['content']
|
||||
@@ -1007,23 +1085,24 @@ Generate a title following these rules:
|
||||
for element in [self.chat_list_box, self.add_chat_button, self.secondary_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 switch_send_stop_button(self, send:bool):
|
||||
self.stop_button.set_visible(not send)
|
||||
self.send_button.set_visible(send)
|
||||
|
||||
def run_message(self, messages, model, message_id):
|
||||
logger.debug("Running message")
|
||||
self.bot_message_button_container.set_visible(False)
|
||||
self.chats["chats"][self.chats["selected_chat"]]["messages"][message_id] = {
|
||||
"role": "assistant",
|
||||
"model": model,
|
||||
"date": datetime.now().strftime("%Y/%m/%d %H:%M:%S"),
|
||||
"content": ''
|
||||
}
|
||||
if message_id not in self.chats["chats"][self.chats["selected_chat"]]["messages"]:
|
||||
self.chats["chats"][self.chats["selected_chat"]]["messages"][message_id] = {
|
||||
"role": "assistant",
|
||||
"model": model,
|
||||
"date": datetime.now().strftime("%Y/%m/%d %H:%M:%S"),
|
||||
"content": ''
|
||||
}
|
||||
if self.regenerate_button:
|
||||
GLib.idle_add(self.chat_container.remove, self.regenerate_button)
|
||||
try:
|
||||
response = connection_handler.stream_post(f"{connection_handler.url}/api/chat", data=json.dumps({"model": model, "messages": messages}), callback=lambda data, message_id=message_id: self.update_bot_message(data, message_id))
|
||||
response = connection_handler.stream_post(f"{connection_handler.URL}/api/chat", data=json.dumps({"model": model, "messages": messages}), callback=lambda data, message_id=message_id: self.update_bot_message(data, message_id))
|
||||
if response.status_code != 200:
|
||||
raise Exception('Network Error')
|
||||
GLib.idle_add(self.add_code_blocks)
|
||||
@@ -1040,40 +1119,46 @@ Generate a title following these rules:
|
||||
GLib.idle_add(self.chat_container.append, self.regenerate_button)
|
||||
self.regenerate_button.connect('clicked', lambda button, message_id=message_id, bot_message_box=self.bot_message_box, bot_message_button_container=self.bot_message_button_container : self.regenerate_message(message_id, bot_message_box, bot_message_button_container))
|
||||
finally:
|
||||
GLib.idle_add(self.switch_send_stop_button)
|
||||
GLib.idle_add(self.switch_send_stop_button, True)
|
||||
GLib.idle_add(self.toggle_ui_sensitive, True)
|
||||
if self.loading_spinner:
|
||||
GLib.idle_add(self.chat_container.remove, self.loading_spinner)
|
||||
GLib.idle_add(self.loading_spinner.get_parent().remove, self.loading_spinner)
|
||||
self.loading_spinner = None
|
||||
|
||||
def regenerate_message(self, message_id, bot_message_box, bot_message_button_container):
|
||||
self.bot_message_button_container = bot_message_button_container
|
||||
self.bot_message_view = Gtk.TextView(
|
||||
editable=False,
|
||||
focusable=True,
|
||||
wrap_mode= Gtk.WrapMode.WORD,
|
||||
margin_top=12,
|
||||
margin_bottom=12,
|
||||
hexpand=True,
|
||||
css_classes=["flat"]
|
||||
)
|
||||
self.bot_message = self.bot_message_view.get_buffer()
|
||||
for widget in list(bot_message_box):
|
||||
bot_message_box.remove(widget)
|
||||
bot_message_box.append(self.bot_message_view)
|
||||
history = self.convert_history_to_ollama()[:list(self.chats["chats"][self.chats["selected_chat"]]["messages"].keys()).index(message_id)]
|
||||
if message_id in self.chats["chats"][self.chats["selected_chat"]]["messages"]:
|
||||
del self.chats["chats"][self.chats["selected_chat"]]["messages"][message_id]
|
||||
data = {
|
||||
"model": self.convert_model_name(self.model_drop_down.get_selected_item().get_string(), 1),
|
||||
"messages": history,
|
||||
"options": {"temperature": self.model_tweaks["temperature"], "seed": self.model_tweaks["seed"]},
|
||||
"keep_alive": f"{self.model_tweaks['keep_alive']}m"
|
||||
}
|
||||
self.switch_send_stop_button()
|
||||
self.toggle_ui_sensitive(False)
|
||||
thread = threading.Thread(target=self.run_message, args=(data['messages'], data['model'], message_id))
|
||||
thread.start()
|
||||
if not self.bot_message:
|
||||
self.bot_message_button_container = bot_message_button_container
|
||||
self.bot_message_view = Gtk.TextView(
|
||||
editable=False,
|
||||
focusable=True,
|
||||
wrap_mode= Gtk.WrapMode.WORD,
|
||||
margin_top=12,
|
||||
margin_bottom=12,
|
||||
hexpand=True,
|
||||
css_classes=["flat"]
|
||||
)
|
||||
self.bot_message = self.bot_message_view.get_buffer()
|
||||
self.bot_message_box = bot_message_box
|
||||
for widget in list(bot_message_box):
|
||||
bot_message_box.remove(widget)
|
||||
self.loading_spinner = Gtk.Spinner(spinning=True, margin_top=12, margin_bottom=12, hexpand=True)
|
||||
bot_message_box.append(self.loading_spinner)
|
||||
bot_message_box.append(self.bot_message_view)
|
||||
if message_id in self.chats["chats"][self.chats["selected_chat"]]["messages"]:
|
||||
self.chats["chats"][self.chats["selected_chat"]]["messages"][message_id]['content'] = ''
|
||||
history = self.convert_history_to_ollama()[:list(self.chats["chats"][self.chats["selected_chat"]]["messages"].keys()).index(message_id)]
|
||||
data = {
|
||||
"model": self.get_current_model(1),
|
||||
"messages": history,
|
||||
"options": {"temperature": self.model_tweaks["temperature"], "seed": self.model_tweaks["seed"]},
|
||||
"keep_alive": f"{self.model_tweaks['keep_alive']}m"
|
||||
}
|
||||
self.switch_send_stop_button(False)
|
||||
self.toggle_ui_sensitive(False)
|
||||
thread = threading.Thread(target=self.run_message, args=(data['messages'], data['model'], message_id))
|
||||
thread.start()
|
||||
else:
|
||||
self.show_toast(_("Message cannot be regenerated while receiving a response"), self.main_overlay)
|
||||
|
||||
def pull_model_update(self, data, model_name):
|
||||
if 'error' in data:
|
||||
@@ -1093,11 +1178,12 @@ Generate a title following these rules:
|
||||
def pull_model_process(self, model, modelfile):
|
||||
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))
|
||||
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))
|
||||
response = connection_handler.stream_post(f"{connection_handler.URL}/api/pull", data=json.dumps(data), callback=lambda data, model_name=model: self.pull_model_update(data, model_name))
|
||||
GLib.idle_add(self.update_list_local_models)
|
||||
GLib.idle_add(self.change_model)
|
||||
|
||||
if response.status_code == 200 and 'error' not in self.pulling_models[model]:
|
||||
GLib.idle_add(self.show_notification, _("Task Complete"), _("Model '{}' pulled successfully.").format(model), Gio.ThemedIcon.new("emblem-ok-symbolic"))
|
||||
@@ -1164,7 +1250,6 @@ Generate a title following these rules:
|
||||
self.model_link_button.set_name(self.available_models[model_name]['url'])
|
||||
self.model_link_button.set_tooltip_text(self.available_models[model_name]['url'])
|
||||
self.available_model_list_box.unselect_all()
|
||||
self.model_tag_list_box.connect('row_selected', lambda list_box, row: self.confirm_pull_model(row.get_name()) if row else None)
|
||||
self.model_tag_list_box.remove_all()
|
||||
tags = self.available_models[model_name]['tags']
|
||||
for tag_data in tags:
|
||||
@@ -1174,12 +1259,24 @@ Generate a title following these rules:
|
||||
subtitle = tag_data[1],
|
||||
name = f"{model_name}:{tag_data[0]}"
|
||||
)
|
||||
tag_row.add_suffix(Gtk.Image.new_from_icon_name("folder-download-symbolic"))
|
||||
download_icon = Gtk.Image.new_from_icon_name("folder-download-symbolic")
|
||||
tag_row.add_suffix(download_icon)
|
||||
download_icon.update_property([4], [_("Download {}:{}").format(model_name, tag_data[0])])
|
||||
|
||||
gesture_click = Gtk.GestureClick.new()
|
||||
gesture_click.connect("pressed", lambda *_, name=f"{model_name}:{tag_data[0]}" : self.confirm_pull_model(name))
|
||||
|
||||
event_controller_key = Gtk.EventControllerKey.new()
|
||||
event_controller_key.connect("key-pressed", lambda controller, key, *_, name=f"{model_name}:{tag_data[0]}" : self.confirm_pull_model(name) if key in (Gdk.KEY_space, Gdk.KEY_Return) else None)
|
||||
|
||||
tag_row.add_controller(gesture_click)
|
||||
tag_row.add_controller(event_controller_key)
|
||||
|
||||
self.model_tag_list_box.append(tag_row)
|
||||
return True
|
||||
|
||||
def update_list_available_models(self):
|
||||
logger.debug("Updating list of available models")
|
||||
self.available_model_list_box.connect('row_selected', lambda list_box, row: self.list_available_model_tags(row.get_name()) if row else None)
|
||||
self.available_model_list_box.remove_all()
|
||||
for name, model_info in self.available_models.items():
|
||||
model = Adw.ActionRow(
|
||||
@@ -1187,13 +1284,19 @@ Generate a title following these rules:
|
||||
subtitle = available_models_descriptions.descriptions[name] + ("\n\n<b>{}</b>".format(_("Image Recognition")) if model_info['image'] else ""),
|
||||
name = name
|
||||
)
|
||||
if model_info["image"]:
|
||||
image_icon = Gtk.Image.new_from_icon_name("image-x-generic-symbolic")
|
||||
image_icon.set_margin_start(5)
|
||||
#model.add_suffix(image_icon)
|
||||
next_icon = Gtk.Image.new_from_icon_name("go-next")
|
||||
next_icon.set_margin_start(5)
|
||||
next_icon.update_property([4], [_("Enter download menu for {}").format(name.replace("-", ""))])
|
||||
model.add_suffix(next_icon)
|
||||
|
||||
gesture_click = Gtk.GestureClick.new()
|
||||
gesture_click.connect("pressed", lambda *_, name=name : self.list_available_model_tags(name))
|
||||
|
||||
event_controller_key = Gtk.EventControllerKey.new()
|
||||
event_controller_key.connect("key-pressed", lambda controller, key, *_, name=name : self.list_available_model_tags(name) if key in (Gdk.KEY_space, Gdk.KEY_Return) else None)
|
||||
|
||||
model.add_controller(gesture_click)
|
||||
model.add_controller(event_controller_key)
|
||||
self.available_model_list_box.append(model)
|
||||
|
||||
def save_history(self):
|
||||
@@ -1201,17 +1304,56 @@ Generate a title following these rules:
|
||||
with open(os.path.join(self.data_dir, "chats", "chats.json"), "w+", encoding="utf-8") as f:
|
||||
json.dump(self.chats, f, indent=4)
|
||||
|
||||
def send_sample_prompt(self, prompt):
|
||||
buffer = self.message_text_view.get_buffer()
|
||||
buffer.delete(buffer.get_start_iter(), buffer.get_end_iter())
|
||||
buffer.insert(buffer.get_start_iter(), prompt, len(prompt.encode('utf-8')))
|
||||
self.send_message()
|
||||
|
||||
def load_history_into_chat(self):
|
||||
for widget in list(self.chat_container): self.chat_container.remove(widget)
|
||||
for key, message in self.chats['chats'][self.chats["selected_chat"]]['messages'].items():
|
||||
if message:
|
||||
formated_date = GLib.markup_escape_text(self.generate_datetime_format(datetime.strptime(message['date'] + (":00" if message['date'].count(":") == 1 else ""), '%Y/%m/%d %H:%M:%S')))
|
||||
if message['role'] == 'user':
|
||||
self.show_message(message['content'], False, f"\n\n<small>{formated_date}</small>", message['images'] if 'images' in message else None, message['files'] if 'files' in message else None, message_id=key)
|
||||
else:
|
||||
self.show_message(message['content'], True, f"\n\n{self.convert_model_name(message['model'], 0)}\n<small>{formated_date}</small>", message_id=key)
|
||||
self.add_code_blocks()
|
||||
self.bot_message = None
|
||||
self.chat_welcome_screen = None
|
||||
if len(self.chats['chats'][self.chats["selected_chat"]]['messages']) > 0:
|
||||
for key, message in self.chats['chats'][self.chats["selected_chat"]]['messages'].items():
|
||||
if message:
|
||||
formated_date = GLib.markup_escape_text(self.generate_datetime_format(datetime.strptime(message['date'] + (":00" if message['date'].count(":") == 1 else ""), '%Y/%m/%d %H:%M:%S')))
|
||||
if message['role'] == 'user':
|
||||
self.show_message(message['content'], False, f"\n\n<small>{formated_date}</small>", message['images'] if 'images' in message else None, message['files'] if 'files' in message else None, message_id=key)
|
||||
else:
|
||||
self.show_message(message['content'], True, f"\n\n{self.convert_model_name(message['model'], 0)}\n<small>{formated_date}</small>", message_id=key)
|
||||
self.add_code_blocks()
|
||||
self.bot_message = None
|
||||
else:
|
||||
button_container = Gtk.Box(
|
||||
orientation = 1,
|
||||
spacing = 10,
|
||||
halign = 3
|
||||
)
|
||||
if len(self.local_models) > 0:
|
||||
for prompt in random.sample(self.possible_prompts, 3):
|
||||
prompt_button = Gtk.Button(
|
||||
label=prompt,
|
||||
tooltip_text=_("Send prompt: '{}'").format(prompt)
|
||||
)
|
||||
prompt_button.connect('clicked', lambda *_, prompt=prompt : self.send_sample_prompt(prompt))
|
||||
button_container.append(prompt_button)
|
||||
else:
|
||||
button = Gtk.Button(
|
||||
label=_("Open Model Manager"),
|
||||
tooltip_text=_("Open Model Manager"),
|
||||
css_classes=["accent", "pill"]
|
||||
)
|
||||
button.connect('clicked', lambda *_ : self.manage_models_dialog.present(self))
|
||||
button_container.append(button)
|
||||
self.chat_welcome_screen = Adw.StatusPage(
|
||||
icon_name="com.jeffser.Alpaca",
|
||||
title="Alpaca",
|
||||
description=_("Try one of these prompts") if len(self.local_models) > 0 else _("It looks like you don't have any models downloaded yet. Download models to get started!"),
|
||||
child=button_container,
|
||||
vexpand=True
|
||||
)
|
||||
self.chat_container.append(self.chat_welcome_screen)
|
||||
|
||||
|
||||
def load_history(self):
|
||||
logger.debug("Loading history")
|
||||
@@ -1227,13 +1369,17 @@ Generate a title following these rules:
|
||||
self.chats["order"] = []
|
||||
for chat_name in self.chats["chats"].keys():
|
||||
self.chats["order"].append(chat_name)
|
||||
self.model_list_box.select_row(self.model_list_box.get_row_at_index(0))
|
||||
self.chats["chats"] = {key: value for key, value in self.chats["chats"].items() if key in self.chats["order"]}
|
||||
if self.chats["selected_chat"] not in self.chats["order"]:
|
||||
self.chats["selected_chat"] = self.chats["order"][0]
|
||||
if len(self.chats["chats"][self.chats["selected_chat"]]["messages"].keys()) > 0:
|
||||
last_model_used = self.chats["chats"][self.chats["selected_chat"]]["messages"][list(self.chats["chats"][self.chats["selected_chat"]]["messages"].keys())[-1]]["model"]
|
||||
last_model_used = self.convert_model_name(last_model_used, 0)
|
||||
for i in range(self.model_string_list.get_n_items()):
|
||||
if self.model_string_list.get_string(i) == last_model_used:
|
||||
self.model_drop_down.set_selected(i)
|
||||
for i, m in enumerate(self.local_models):
|
||||
if m == last_model_used:
|
||||
self.model_list_box.select_row(self.model_list_box.get_row_at_index(i))
|
||||
break
|
||||
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
self.chats = {"chats": {}, "selected_chat": None, "order": []}
|
||||
@@ -1263,8 +1409,9 @@ Generate a title following these rules:
|
||||
def clear_chat(self):
|
||||
logger.info("Clearing chat")
|
||||
for widget in list(self.chat_container): self.chat_container.remove(widget)
|
||||
self.chats["chats"][self.chats["selected_chat"]]["messages"] = []
|
||||
self.chats["chats"][self.chats["selected_chat"]]["messages"] = {}
|
||||
self.save_history()
|
||||
self.load_history_into_chat()
|
||||
|
||||
def delete_chat(self, chat_name):
|
||||
logger.info("Deleting chat")
|
||||
@@ -1279,6 +1426,14 @@ Generate a title following these rules:
|
||||
if self.chats['selected_chat'] == chat_name:
|
||||
self.chat_list_box.select_row(self.chat_list_box.get_row_at_index(0))
|
||||
|
||||
def duplicate_chat(self, chat_name):
|
||||
new_chat_name = self.generate_numbered_name(_("Copy of {}").format(chat_name), self.chats["chats"].keys())
|
||||
self.chats["chats"][new_chat_name] = self.chats["chats"][chat_name]
|
||||
self.chats["order"].insert(0, new_chat_name)
|
||||
self.save_history()
|
||||
self.new_chat_element(new_chat_name, True, False)
|
||||
shutil.copytree(os.path.join(self.data_dir, "chats", chat_name), os.path.join(self.data_dir, "chats", new_chat_name))
|
||||
|
||||
def rename_chat(self, old_chat_name, new_chat_name, label_element):
|
||||
logger.info(f"Renaming chat \"{old_chat_name}\" -> \"{new_chat_name}\"")
|
||||
new_chat_name = self.generate_numbered_name(new_chat_name, self.chats["chats"].keys())
|
||||
@@ -1300,6 +1455,7 @@ Generate a title following these rules:
|
||||
self.chats["order"].insert(0, chat_name)
|
||||
self.save_history()
|
||||
self.new_chat_element(chat_name, True, False)
|
||||
self.set_focus(self.message_text_view)
|
||||
|
||||
def stop_pull_model(self, model_name):
|
||||
logger.debug("Stopping model pull")
|
||||
@@ -1308,10 +1464,11 @@ Generate a title following these rules:
|
||||
|
||||
def delete_model(self, model_name):
|
||||
logger.debug("Deleting model")
|
||||
response = connection_handler.simple_delete(f"{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_code == 200:
|
||||
self.show_toast(_("Model deleted successfully"), self.manage_models_overlay)
|
||||
self.change_model()
|
||||
else:
|
||||
self.manage_models_dialog.close()
|
||||
self.connection_error()
|
||||
@@ -1322,6 +1479,7 @@ Generate a title following these rules:
|
||||
menu_model=self.chat_right_click_menu,
|
||||
has_arrow=False,
|
||||
halign=1,
|
||||
height_request=155
|
||||
)
|
||||
self.selected_chat_row = chat_row
|
||||
position = Gdk.Rectangle()
|
||||
@@ -1372,17 +1530,17 @@ Generate a title following these rules:
|
||||
|
||||
def connect_remote(self, url, bearer_token):
|
||||
logger.debug(f"Connecting to remote: {url}")
|
||||
connection_handler.url = url
|
||||
connection_handler.bearer_token = bearer_token
|
||||
self.remote_url = connection_handler.url
|
||||
connection_handler.URL = url
|
||||
connection_handler.BEARER_TOKEN = bearer_token
|
||||
self.remote_url = connection_handler.URL
|
||||
self.remote_connection_entry.set_text(self.remote_url)
|
||||
if self.verify_connection() == False: self.connection_error()
|
||||
|
||||
def connect_local(self):
|
||||
logger.debug("Connecting to Alpaca's Ollama instance")
|
||||
self.run_remote = False
|
||||
connection_handler.bearer_token = None
|
||||
connection_handler.url = f"http://127.0.0.1:{local_instance.port}"
|
||||
connection_handler.BEARER_TOKEN = None
|
||||
connection_handler.URL = f"http://127.0.0.1:{local_instance.port}"
|
||||
local_instance.start()
|
||||
if self.verify_connection() == False:
|
||||
self.connection_error()
|
||||
@@ -1392,7 +1550,7 @@ Generate a title following these rules:
|
||||
def connection_error(self):
|
||||
logger.error("Connection error")
|
||||
if self.run_remote:
|
||||
dialogs.reconnect_remote(self, connection_handler.url, connection_handler.bearer_token)
|
||||
dialogs.reconnect_remote(self, connection_handler.URL, connection_handler.BEARER_TOKEN)
|
||||
else:
|
||||
local_instance.reset()
|
||||
self.show_toast(_("There was an error with the local Ollama instance, so it has been reset"), self.main_overlay)
|
||||
@@ -1403,15 +1561,15 @@ Generate a title following these rules:
|
||||
if new_value != self.run_remote:
|
||||
self.run_remote = new_value
|
||||
if self.run_remote:
|
||||
connection_handler.bearer_token = self.remote_bearer_token
|
||||
connection_handler.url = self.remote_url
|
||||
connection_handler.BEARER_TOKEN = self.remote_bearer_token
|
||||
connection_handler.URL = self.remote_url
|
||||
if self.verify_connection() == False:
|
||||
self.connection_error()
|
||||
else:
|
||||
local_instance.stop()
|
||||
else:
|
||||
connection_handler.bearer_token = None
|
||||
connection_handler.url = f"http://127.0.0.1:{local_instance.port}"
|
||||
connection_handler.BEARER_TOKEN = None
|
||||
connection_handler.URL = f"http://127.0.0.1:{local_instance.port}"
|
||||
local_instance.start()
|
||||
if self.verify_connection() == False:
|
||||
self.connection_error()
|
||||
@@ -1428,7 +1586,7 @@ Generate a title following these rules:
|
||||
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
json_path = os.path.join(temp_dir, "data.json")
|
||||
with open(json_path, "wb", encoding="utf-8") as json_file:
|
||||
with open(json_path, "wb") as json_file:
|
||||
json_file.write(json_data)
|
||||
|
||||
tar_path = os.path.join(temp_dir, chat_name)
|
||||
@@ -1438,7 +1596,7 @@ Generate a title following these rules:
|
||||
if os.path.exists(directory) and os.path.isdir(directory):
|
||||
tar.add(directory, arcname=os.path.basename(directory))
|
||||
|
||||
with open(tar_path, "rb", encoding="utf-8") as tar:
|
||||
with open(tar_path, "rb") as tar:
|
||||
tar_content = tar.read()
|
||||
|
||||
file.replace_contents_async(
|
||||
@@ -1466,7 +1624,7 @@ Generate a title following these rules:
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
tar_filename = os.path.join(temp_dir, "imported_chat.tar")
|
||||
|
||||
with open(tar_filename, "wb", encoding="utf-8") as tar_file:
|
||||
with open(tar_filename, "wb") as tar_file:
|
||||
tar_file.write(tar_content.get_data())
|
||||
|
||||
with tarfile.open(tar_filename, "r") as tar:
|
||||
@@ -1481,6 +1639,7 @@ Generate a title following these rules:
|
||||
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
|
||||
self.chats["order"].insert(0, new_chat_name)
|
||||
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)
|
||||
@@ -1576,8 +1735,10 @@ Generate a title following these rules:
|
||||
chat_row = self.selected_chat_row
|
||||
chat_name = chat_row.get_child().get_name()
|
||||
action_name = action.get_name()
|
||||
if action_name == 'delete_chat':
|
||||
if action_name in ('delete_chat', 'delete_current_chat'):
|
||||
dialogs.delete_chat(self, chat_name)
|
||||
elif action_name in ('duplicate_chat', 'duplicate_current_chat'):
|
||||
self.duplicate_chat(chat_name)
|
||||
elif action_name in ('rename_chat', 'rename_current_chat'):
|
||||
dialogs.rename_chat(self, chat_name, chat_row.get_child())
|
||||
elif action_name in ('export_chat', 'export_current_chat'):
|
||||
@@ -1625,7 +1786,8 @@ Generate a title following these rules:
|
||||
self.attach_file(os.path.join(self.cache_dir, 'tmp/images/{}'.format(image_name)), 'image')
|
||||
else:
|
||||
self.show_toast(_("Image recognition is only available on specific models"), self.main_overlay)
|
||||
except Exception as e: 'huh'
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
def on_clipboard_paste(self, textview):
|
||||
logger.debug("Pasting from clipboard")
|
||||
@@ -1633,27 +1795,9 @@ Generate a title following these rules:
|
||||
clipboard.read_text_async(None, self.cb_text_received)
|
||||
clipboard.read_texture_async(None, self.cb_image_received)
|
||||
|
||||
|
||||
def on_model_dropdown_setup(self, factory, list_item):
|
||||
label = Gtk.Label()
|
||||
label.set_ellipsize(2)
|
||||
label.set_xalign(0)
|
||||
list_item.set_child(label)
|
||||
|
||||
def on_model_dropdown_bind(self, factory, list_item):
|
||||
label = list_item.get_child()
|
||||
item = list_item.get_item()
|
||||
label.set_text(item.get_string())
|
||||
label.set_tooltip_text(item.get_string())
|
||||
|
||||
def setup_model_dropdown(self):
|
||||
factory = Gtk.SignalListItemFactory()
|
||||
factory.connect("setup", self.on_model_dropdown_setup)
|
||||
factory.connect("bind", self.on_model_dropdown_bind)
|
||||
self.model_drop_down.set_factory(factory)
|
||||
|
||||
def handle_enter_key(self):
|
||||
self.send_message()
|
||||
if not self.bot_message:
|
||||
self.send_message()
|
||||
return True
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
@@ -1663,9 +1807,9 @@ Generate a title following these rules:
|
||||
self.available_models = json.load(f)
|
||||
if not os.path.exists(os.path.join(self.data_dir, "chats")):
|
||||
os.makedirs(os.path.join(self.data_dir, "chats"))
|
||||
key_controller = Gtk.EventControllerKey.new()
|
||||
key_controller.connect("key-pressed", lambda controller, keyval, keycode, state: self.handle_enter_key() if keyval==Gdk.KEY_Return else None)
|
||||
self.message_text_view.add_controller(key_controller)
|
||||
enter_key_controller = Gtk.EventControllerKey.new()
|
||||
enter_key_controller.connect("key-pressed", lambda controller, keyval, keycode, state: self.handle_enter_key() if keyval==Gdk.KEY_Return and not (state & Gdk.ModifierType.SHIFT_MASK) else None)
|
||||
self.message_text_view.add_controller(enter_key_controller)
|
||||
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'])
|
||||
@@ -1674,22 +1818,25 @@ Generate a title following these rules:
|
||||
self.get_application().create_action('create_model_from_existing', lambda *_: dialogs.create_model_from_existing(self))
|
||||
self.get_application().create_action('create_model_from_file', lambda *_: dialogs.create_model_from_file(self))
|
||||
self.get_application().create_action('create_model_from_name', lambda *_: dialogs.create_model_from_name(self))
|
||||
self.get_application().create_action('duplicate_chat', self.chat_actions)
|
||||
self.get_application().create_action('duplicate_current_chat', self.current_chat_actions)
|
||||
self.get_application().create_action('delete_chat', self.chat_actions)
|
||||
self.get_application().create_action('delete_current_chat', self.current_chat_actions)
|
||||
self.get_application().create_action('rename_chat', self.chat_actions)
|
||||
self.get_application().create_action('rename_current_chat', self.current_chat_actions)
|
||||
self.get_application().create_action('export_chat', self.chat_actions)
|
||||
self.get_application().create_action('export_current_chat', self.current_chat_actions)
|
||||
self.get_application().create_action('toggle_sidebar', lambda *_: self.split_view_overlay.set_show_sidebar(not self.split_view_overlay.get_show_sidebar()), ['F9'])
|
||||
self.get_application().create_action('manage_models', lambda *_: self.manage_models_button_activate(), ['<primary>m'])
|
||||
self.get_application().create_action('manage_models', lambda *_: self.manage_models_dialog.present(self), ['<primary>m'])
|
||||
self.message_text_view.connect("paste-clipboard", self.on_clipboard_paste)
|
||||
self.file_preview_remove_button.connect('clicked', lambda button : dialogs.remove_attached_file(self, button.get_name()))
|
||||
self.add_chat_button.connect("clicked", lambda button : self.new_chat())
|
||||
self.attachment_button.connect("clicked", lambda button, file_filter=self.file_filter_attachments: dialogs.attach_file(self, file_filter))
|
||||
self.create_model_name.get_delegate().connect("insert-text", self.check_alphanumeric)
|
||||
self.create_model_name.get_delegate().connect("insert-text", lambda *_ : self.check_alphanumeric(*_, ['-', '.', '_']))
|
||||
self.remote_connection_entry.connect("entry-activated", lambda entry : entry.set_css_classes([]))
|
||||
self.remote_connection_switch.connect("notify", lambda pspec, user_data : self.connection_switched())
|
||||
self.background_switch.connect("notify", lambda pspec, user_data : self.switch_run_on_background())
|
||||
self.setup_model_dropdown()
|
||||
self.set_focus(self.message_text_view)
|
||||
if os.path.exists(os.path.join(self.config_dir, "server.json")):
|
||||
with open(os.path.join(self.config_dir, "server.json"), "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
@@ -1719,17 +1866,17 @@ Generate a title following these rules:
|
||||
self.remote_connection_entry.set_text(self.remote_url)
|
||||
self.remote_bearer_token_entry.set_text(self.remote_bearer_token)
|
||||
if self.run_remote:
|
||||
connection_handler.bearer_token = self.remote_bearer_token
|
||||
connection_handler.url = self.remote_url
|
||||
connection_handler.BEARER_TOKEN = self.remote_bearer_token
|
||||
connection_handler.URL = self.remote_url
|
||||
self.remote_connection_switch.set_active(True)
|
||||
else:
|
||||
connection_handler.bearer_token = None
|
||||
connection_handler.BEARER_TOKEN = None
|
||||
self.remote_connection_switch.set_active(False)
|
||||
connection_handler.url = f"http://127.0.0.1:{local_instance.port}"
|
||||
connection_handler.URL = f"http://127.0.0.1:{local_instance.port}"
|
||||
local_instance.start()
|
||||
else:
|
||||
local_instance.start()
|
||||
connection_handler.url = f"http://127.0.0.1:{local_instance.port}"
|
||||
connection_handler.URL = f"http://127.0.0.1:{local_instance.port}"
|
||||
self.welcome_dialog.present(self)
|
||||
if self.verify_connection() is False:
|
||||
self.connection_error()
|
||||
|
||||
308
src/window.ui
308
src/window.ui
@@ -12,13 +12,14 @@
|
||||
<property name="title">Alpaca</property>
|
||||
<child>
|
||||
<object class="AdwBreakpoint">
|
||||
<condition>max-width: 800sp</condition>
|
||||
<condition>max-width: 690sp</condition>
|
||||
<setter object="split_view_overlay" property="collapsed">true</setter>
|
||||
</object>
|
||||
</child>
|
||||
<property name="content">
|
||||
<object class="AdwOverlaySplitView" id="split_view_overlay">
|
||||
<property name="show-sidebar" bind-source="show_sidebar_button" bind-property="active" bind-flags="sync-create"/>
|
||||
<property name="sidebar-width-fraction">0.4</property>
|
||||
<property name="sidebar">
|
||||
<object class="AdwToolbarView">
|
||||
<child type="top">
|
||||
@@ -75,30 +76,84 @@
|
||||
<property name="orientation">0</property>
|
||||
<property name="spacing">12</property>
|
||||
<child>
|
||||
<object class="GtkDropDown" id="model_drop_down">
|
||||
<signal name="notify" handler="verify_if_image_can_be_used"/>
|
||||
<property name="width-request">260</property>
|
||||
<property name="enable-search">true</property>
|
||||
<property name="tooltip-text">Select Model</property>
|
||||
<property name="model">
|
||||
<object class="GtkStringList" id="model_string_list">
|
||||
<items>
|
||||
</items>
|
||||
<object class="GtkMenuButton" id="model_selector_button">
|
||||
<property name="tooltip-text" translatable="yes">Select Model</property>
|
||||
<property name="child">
|
||||
<object class="GtkBox">
|
||||
<property name="spacing">10</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="label" translatable="yes">Select a Model</property>
|
||||
<property name="ellipsize">2</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="icon-name">down-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
<property name="halign">1</property>
|
||||
<style>
|
||||
<class name="raised"/>
|
||||
</style>
|
||||
<property name="popover">
|
||||
<object class="GtkPopover" id="model_popover">
|
||||
<style>
|
||||
<class name="model_popover"/>
|
||||
</style>
|
||||
<property name="has-arrow">false</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">1</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkButton">
|
||||
<property name="child">
|
||||
<object class="GtkLabel">
|
||||
<property name="label" translatable="yes">Manage Models</property>
|
||||
<property name="justify">left</property>
|
||||
<property name="halign">1</property>
|
||||
</object>
|
||||
</property>
|
||||
<property name="hexpand">true</property>
|
||||
<property name="tooltip-text" translatable="yes">Manage Models</property>
|
||||
<property name="action-name">app.manage_models</property>
|
||||
<signal name="clicked" handler="close_model_popup"/>
|
||||
<style>
|
||||
<class name="flat"/>
|
||||
<class name="manage_models_button"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparator"/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="max-content-height">300</property>
|
||||
<property name="propagate-natural-width">true</property>
|
||||
<property name="propagate-natural-height">true</property>
|
||||
<child>
|
||||
<object class="GtkListBox" id="model_list_box">
|
||||
<property name="hexpand">true</property>
|
||||
<style>
|
||||
<class name="navigation-sidebar"/>
|
||||
<class name="model_list_box"/>
|
||||
</style>
|
||||
<signal name="row-selected" handler="change_model"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</child>
|
||||
<!--<child>
|
||||
<object class="GtkButton" id="manage_models_button">
|
||||
<signal name="clicked" handler="manage_models_button_activate"/>
|
||||
<property name="tooltip-text" translatable="yes">Manage Models</property>
|
||||
<child>
|
||||
<object class="AdwButtonContent">
|
||||
<property name="icon-name">brain-augemnted-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>-->
|
||||
</object>
|
||||
</property>
|
||||
<child type="end">
|
||||
@@ -215,6 +270,10 @@
|
||||
<property name="top-margin">10</property>
|
||||
<property name="bottom-margin">10</property>
|
||||
<property name="hexpand">true</property>
|
||||
<property name="input-hints">spellcheck</property>
|
||||
<accessibility>
|
||||
<property name="label" translatable="yes">Message text box</property>
|
||||
</accessibility>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
@@ -442,6 +501,9 @@
|
||||
</object>
|
||||
|
||||
<object class="AdwDialog" id="manage_models_dialog">
|
||||
<accessibility>
|
||||
<property name="label" translatable="yes">Manage models dialog</property>
|
||||
</accessibility>
|
||||
<property name="can-close">true</property>
|
||||
<property name="width-request">400</property>
|
||||
<property name="height-request">600</property>
|
||||
@@ -476,12 +538,18 @@
|
||||
</child>
|
||||
<child type="top">
|
||||
<object class="GtkSearchBar" id="model_searchbar">
|
||||
<accessibility>
|
||||
<property name="label" translatable="yes">Model search bar</property>
|
||||
</accessibility>
|
||||
<property name="key-capture-widget">AlpacaWindow</property>
|
||||
<child>
|
||||
<object class="GtkSearchEntry" id="searchentry">
|
||||
<signal name="search-changed" handler="model_search_changed"/>
|
||||
<property name="search-delay">100</property>
|
||||
<property name="placeholder-text" translatable="yes">Search models</property>
|
||||
<accessibility>
|
||||
<property name="label" translatable="yes">Search models</property>
|
||||
</accessibility>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
@@ -517,7 +585,7 @@
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkListBox" id="available_model_list_box">
|
||||
<property name="selection-mode">single</property>
|
||||
<property name="selection-mode">none</property>
|
||||
<style>
|
||||
<class name="boxed-list"/>
|
||||
</style>
|
||||
@@ -580,7 +648,7 @@
|
||||
<child>
|
||||
<object class="GtkListBox" id="model_tag_list_box">
|
||||
<property name="valign">1</property>
|
||||
<property name="selection-mode">single</property>
|
||||
<property name="selection-mode">none</property>
|
||||
<style>
|
||||
<class name="boxed-list"/>
|
||||
</style>
|
||||
@@ -738,9 +806,12 @@
|
||||
</object>
|
||||
|
||||
<object class="AdwDialog" id="file_preview_dialog">
|
||||
<accessibility>
|
||||
<property name="label" translatable="yes">File preview dialog</property>
|
||||
</accessibility>
|
||||
<property name="can-close">true</property>
|
||||
<property name="width-request">450</property>
|
||||
<property name="height-request">450</property>
|
||||
<property name="width-request">400</property>
|
||||
<property name="height-request">600</property>
|
||||
<child>
|
||||
<object class="AdwToolbarView">
|
||||
<child type="top">
|
||||
@@ -805,8 +876,8 @@
|
||||
|
||||
<object class="AdwDialog" id="welcome_dialog">
|
||||
<property name="can-close">false</property>
|
||||
<property name="width-request">450</property>
|
||||
<property name="height-request">450</property>
|
||||
<property name="width-request">400</property>
|
||||
<property name="height-request">600</property>
|
||||
<child>
|
||||
<object class="AdwToolbarView">
|
||||
<child type="bottom">
|
||||
@@ -885,141 +956,6 @@
|
||||
<property name="description" translatable="yes">Alpaca and its developers are not liable for any damages to devices or software resulting from the execution of code generated by an AI model. Please exercise caution and review the code carefully before running it.</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwStatusPage">
|
||||
<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>
|
||||
<property name="title" translatable="yes">Featured Models</property>
|
||||
<property name="description" translatable="yes">Alpaca works locally on your device, to start chatting you'll need an AI model, you can either pull models from this list or the 'Manage Models' menu later.
|
||||
|
||||
By downloading any model you accept their license agreement available on the model's website.
|
||||
</property>
|
||||
<child>
|
||||
<object class="GtkListBox">
|
||||
<property name="selection-mode">none</property>
|
||||
<style>
|
||||
<class name="boxed-list"/>
|
||||
</style>
|
||||
<child>
|
||||
<object class="AdwActionRow">
|
||||
<property name="title" translatable="no">Llama3</property>
|
||||
<property name="subtitle" translatable="yes">Built by Meta</property>
|
||||
<child type="suffix">
|
||||
<object class="GtkButton">
|
||||
<signal name="clicked" handler="link_button_handler"/>
|
||||
<property name="vexpand">false</property>
|
||||
<property name="icon-name">globe-symbolic</property>
|
||||
<property name="valign">3</property>
|
||||
<property name="name">https://ollama.com/library/llama3</property>
|
||||
<property name="tooltip-text">https://ollama.com/library/llama3</property>
|
||||
</object>
|
||||
</child>
|
||||
<child type="suffix">
|
||||
<object class="GtkButton">
|
||||
<signal name="clicked" handler="pull_featured_model"/>
|
||||
<property name="vexpand">false</property>
|
||||
<property name="icon-name">folder-download-symbolic</property>
|
||||
<property name="valign">3</property>
|
||||
<property name="tooltip-text">Pull 'Llama3 (latest)'</property>
|
||||
<style>
|
||||
<class name="accent"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwActionRow">
|
||||
<property name="title" translatable="no">Gemma2</property>
|
||||
<property name="subtitle" translatable="yes">Built by Google DeepMind</property>
|
||||
<child type="suffix">
|
||||
<object class="GtkButton">
|
||||
<signal name="clicked" handler="link_button_handler"/>
|
||||
<property name="vexpand">false</property>
|
||||
<property name="icon-name">globe-symbolic</property>
|
||||
<property name="valign">3</property>
|
||||
<property name="name">https://ollama.com/library/gemma2</property>
|
||||
<property name="tooltip-text">https://ollama.com/library/gemma2</property>
|
||||
</object>
|
||||
</child>
|
||||
<child type="suffix">
|
||||
<object class="GtkButton">
|
||||
<signal name="clicked" handler="pull_featured_model"/>
|
||||
<property name="vexpand">false</property>
|
||||
<property name="icon-name">folder-download-symbolic</property>
|
||||
<property name="valign">3</property>
|
||||
<property name="tooltip-text">Pull 'Gemma2 (latest)'</property>
|
||||
<style>
|
||||
<class name="accent"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwActionRow">
|
||||
<property name="title" translatable="no">Phi3</property>
|
||||
<property name="subtitle" translatable="yes">Built by Microsoft</property>
|
||||
<child type="suffix">
|
||||
<object class="GtkButton">
|
||||
<signal name="clicked" handler="link_button_handler"/>
|
||||
<property name="vexpand">false</property>
|
||||
<property name="icon-name">globe-symbolic</property>
|
||||
<property name="valign">3</property>
|
||||
<property name="name">https://ollama.com/library/phi3</property>
|
||||
<property name="tooltip-text">https://ollama.com/library/phi3</property>
|
||||
</object>
|
||||
</child>
|
||||
<child type="suffix">
|
||||
<object class="GtkButton">
|
||||
<signal name="clicked" handler="pull_featured_model"/>
|
||||
<property name="vexpand">false</property>
|
||||
<property name="icon-name">folder-download-symbolic</property>
|
||||
<property name="valign">3</property>
|
||||
<property name="tooltip-text">Pull 'Phi3 (latest)'</property>
|
||||
<style>
|
||||
<class name="accent"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwActionRow">
|
||||
<property name="title" translatable="no">Llava</property>
|
||||
<property name="subtitle" translatable="yes">Multimodal AI with image recognition</property>
|
||||
<child type="suffix">
|
||||
<object class="GtkButton">
|
||||
<signal name="clicked" handler="link_button_handler"/>
|
||||
<property name="vexpand">false</property>
|
||||
<property name="icon-name">globe-symbolic</property>
|
||||
<property name="valign">3</property>
|
||||
<property name="name">https://ollama.com/library/llava</property>
|
||||
<property name="tooltip-text">https://ollama.com/library/llava</property>
|
||||
</object>
|
||||
</child>
|
||||
<child type="suffix">
|
||||
<object class="GtkButton">
|
||||
<signal name="clicked" handler="pull_featured_model"/>
|
||||
<property name="vexpand">false</property>
|
||||
<property name="icon-name">folder-download-symbolic</property>
|
||||
<property name="valign">3</property>
|
||||
<property name="tooltip-text">Pull 'Llava (latest)'</property>
|
||||
<style>
|
||||
<class name="accent"/>
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
@@ -1054,34 +990,52 @@ By downloading any model you accept their license agreement available on the mod
|
||||
</section>
|
||||
</menu>
|
||||
<menu id="secondary_menu">
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Rename Chat</attribute>
|
||||
<attribute name="action">app.rename_current_chat</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Export Chat</attribute>
|
||||
<attribute name="action">app.export_current_chat</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Clear Chat</attribute>
|
||||
<attribute name="action">app.clear</attribute>
|
||||
</item>
|
||||
</menu>
|
||||
<menu id="chat_right_click_menu">
|
||||
<section>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Rename Chat</attribute>
|
||||
<attribute name="action">app.rename_current_chat</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Duplicate Chat</attribute>
|
||||
<attribute name="action">app.duplicate_current_chat</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Export Chat</attribute>
|
||||
<attribute name="action">app.export_current_chat</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Clear Chat</attribute>
|
||||
<attribute name="action">app.clear</attribute>
|
||||
</item>
|
||||
</section>
|
||||
<section>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Delete Chat</attribute>
|
||||
<attribute name="action">app.delete_chat</attribute>
|
||||
<attribute name="action">app.delete_current_chat</attribute>
|
||||
</item>
|
||||
</section>
|
||||
</menu>
|
||||
<menu id="chat_right_click_menu">
|
||||
<section>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Rename Chat</attribute>
|
||||
<attribute name="action">app.rename_chat</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Duplicate Chat</attribute>
|
||||
<attribute name="action">app.duplicate_chat</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Export Chat</attribute>
|
||||
<attribute name="action">app.export_chat</attribute>
|
||||
</item>
|
||||
</section>
|
||||
<section>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">Delete Chat</attribute>
|
||||
<attribute name="action">app.delete_chat</attribute>
|
||||
</item>
|
||||
</section>
|
||||
</menu>
|
||||
<menu id="create_model_menu">
|
||||
<section>
|
||||
|
||||
@@ -15,4 +15,6 @@ msgmerge --no-fuzzy-matching -U po/nb_NO.po po/alpaca.pot
|
||||
echo "Updating Bengali"
|
||||
msgmerge --no-fuzzy-matching -U po/bn.po po/alpaca.pot
|
||||
echo "Updating Simplified Chinese"
|
||||
msgmerge --no-fuzzy-matching -U po/zh_CN.po po/alpaca.pot
|
||||
msgmerge --no-fuzzy-matching -U po/zh_CN.po po/alpaca.pot
|
||||
echo "Updating Hindi"
|
||||
msgmerge --no-fuzzy-matching -U po/hi.po po/alpaca.pot
|
||||
|
||||
Reference in New Issue
Block a user