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: actions/checkout@v4
|
||||||
- uses: flatpak/flatpak-github-actions/flatpak-builder@v6
|
- uses: flatpak/flatpak-github-actions/flatpak-builder@v6
|
||||||
with:
|
with:
|
||||||
bundle: Alpaca.flatpak
|
bundle: com.jeffser.Alpaca.flatpak
|
||||||
manifest-path: com.jeffser.Alpaca.json
|
manifest-path: com.jeffser.Alpaca.json
|
||||||
cache-key: flatpak-builder-${{ github.sha }}
|
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)
|
🇳🇴 Norwegian | [CounterFlow64](https://github.com/CounterFlow64)
|
||||||
🇮🇳 Bengali | [Aritra Saha](https://github.com/olumolu)
|
🇮🇳 Bengali | [Aritra Saha](https://github.com/olumolu)
|
||||||
🇨🇳 Simplified Chinese | [Yuehao Sui](https://github.com/8ar10der) , [Aleksana](https://github.com/Aleksanaa)
|
🇨🇳 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>
|
<caption>Multiple models being downloaded</caption>
|
||||||
</screenshot>
|
</screenshot>
|
||||||
</screenshots>
|
</screenshots>
|
||||||
<content_rating type="oars-1.1">
|
<content_rating type="oars-1.1" />
|
||||||
<content_attribute id="money-purchasing">mild</content_attribute>
|
|
||||||
</content_rating>
|
|
||||||
<url type="bugtracker">https://github.com/Jeffser/Alpaca/issues</url>
|
<url type="bugtracker">https://github.com/Jeffser/Alpaca/issues</url>
|
||||||
<url type="homepage">https://jeffser.com/alpaca/</url>
|
<url type="homepage">https://jeffser.com/alpaca/</url>
|
||||||
<url type="donation">https://github.com/sponsors/Jeffser</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="contribute">https://github.com/Jeffser/Alpaca/discussions/154</url>
|
||||||
<url type="vcs-browser">https://github.com/Jeffser/Alpaca</url>
|
<url type="vcs-browser">https://github.com/Jeffser/Alpaca</url>
|
||||||
<releases>
|
<releases>
|
||||||
|
<release version="1.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">
|
<release version="1.0.6" date="2024-08-04">
|
||||||
<url type="details">https://github.com/Jeffser/Alpaca/releases/tag/1.0.6</url>
|
<url type="details">https://github.com/Jeffser/Alpaca/releases/tag/1.0.6</url>
|
||||||
<description>
|
<description>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
project('Alpaca', 'c',
|
project('Alpaca', 'c',
|
||||||
version: '1.0.6',
|
version: '1.1.1',
|
||||||
meson_version: '>= 0.62.0',
|
meson_version: '>= 0.62.0',
|
||||||
default_options: [ 'warning_level=2', 'werror=false', ],
|
default_options: [ 'warning_level=2', 'werror=false', ],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,4 +4,5 @@ pt_BR
|
|||||||
fr
|
fr
|
||||||
nb_NO
|
nb_NO
|
||||||
bn
|
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/edit-symbolic.svg">icons/edit-symbolic.svg</file>
|
||||||
<file alias="icons/scalable/status/image-missing-symbolic.svg">icons/image-missing-symbolic.svg</file>
|
<file alias="icons/scalable/status/image-missing-symbolic.svg">icons/image-missing-symbolic.svg</file>
|
||||||
<file alias="icons/scalable/status/update-symbolic.svg">icons/update-symbolic.svg</file>
|
<file 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">window.ui</file>
|
||||||
<file preprocess="xml-stripblanks">gtk/help-overlay.ui</file>
|
<file preprocess="xml-stripblanks">gtk/help-overlay.ui</file>
|
||||||
</gresource>
|
</gresource>
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ def clear_chat(self):
|
|||||||
dialog.add_response("cancel", _("Cancel"))
|
dialog.add_response("cancel", _("Cancel"))
|
||||||
dialog.add_response("clear", _("Clear"))
|
dialog.add_response("clear", _("Clear"))
|
||||||
dialog.set_response_appearance("clear", Adw.ResponseAppearance.DESTRUCTIVE)
|
dialog.set_response_appearance("clear", Adw.ResponseAppearance.DESTRUCTIVE)
|
||||||
|
dialog.set_default_response("clear")
|
||||||
dialog.choose(
|
dialog.choose(
|
||||||
parent = self,
|
parent = self,
|
||||||
cancellable = None,
|
cancellable = None,
|
||||||
@@ -49,6 +50,7 @@ def delete_chat(self, chat_name):
|
|||||||
dialog.add_response("cancel", _("Cancel"))
|
dialog.add_response("cancel", _("Cancel"))
|
||||||
dialog.add_response("delete", _("Delete"))
|
dialog.add_response("delete", _("Delete"))
|
||||||
dialog.set_response_appearance("delete", Adw.ResponseAppearance.DESTRUCTIVE)
|
dialog.set_response_appearance("delete", Adw.ResponseAppearance.DESTRUCTIVE)
|
||||||
|
dialog.set_default_response("delete")
|
||||||
dialog.choose(
|
dialog.choose(
|
||||||
parent = self,
|
parent = self,
|
||||||
cancellable = None,
|
cancellable = None,
|
||||||
@@ -74,10 +76,10 @@ def rename_chat(self, chat_name, label_element):
|
|||||||
extra_child=entry,
|
extra_child=entry,
|
||||||
close_response="cancel"
|
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("cancel", _("Cancel"))
|
||||||
dialog.add_response("rename", _("Rename"))
|
dialog.add_response("rename", _("Rename"))
|
||||||
dialog.set_response_appearance("rename", Adw.ResponseAppearance.SUGGESTED)
|
dialog.set_response_appearance("rename", Adw.ResponseAppearance.SUGGESTED)
|
||||||
|
dialog.set_default_response("rename")
|
||||||
dialog.choose(
|
dialog.choose(
|
||||||
parent = self,
|
parent = self,
|
||||||
cancellable = None,
|
cancellable = None,
|
||||||
@@ -102,10 +104,10 @@ def new_chat(self):
|
|||||||
extra_child=entry,
|
extra_child=entry,
|
||||||
close_response="cancel"
|
close_response="cancel"
|
||||||
)
|
)
|
||||||
entry.connect("activate", lambda dialog, entry: new_chat_response(self, dialog, None, entry))
|
|
||||||
dialog.add_response("cancel", _("Cancel"))
|
dialog.add_response("cancel", _("Cancel"))
|
||||||
dialog.add_response("create", _("Create"))
|
dialog.add_response("create", _("Create"))
|
||||||
dialog.set_response_appearance("create", Adw.ResponseAppearance.SUGGESTED)
|
dialog.set_response_appearance("create", Adw.ResponseAppearance.SUGGESTED)
|
||||||
|
dialog.set_default_response("create")
|
||||||
dialog.choose(
|
dialog.choose(
|
||||||
parent = self,
|
parent = self,
|
||||||
cancellable = None,
|
cancellable = None,
|
||||||
@@ -128,6 +130,7 @@ def stop_pull_model(self, model_name):
|
|||||||
dialog.add_response("cancel", _("Cancel"))
|
dialog.add_response("cancel", _("Cancel"))
|
||||||
dialog.add_response("stop", _("Stop"))
|
dialog.add_response("stop", _("Stop"))
|
||||||
dialog.set_response_appearance("stop", Adw.ResponseAppearance.DESTRUCTIVE)
|
dialog.set_response_appearance("stop", Adw.ResponseAppearance.DESTRUCTIVE)
|
||||||
|
dialog.set_default_response("stop")
|
||||||
dialog.choose(
|
dialog.choose(
|
||||||
parent = self.manage_models_dialog,
|
parent = self.manage_models_dialog,
|
||||||
cancellable = None,
|
cancellable = None,
|
||||||
@@ -149,6 +152,7 @@ def delete_model(self, model_name):
|
|||||||
dialog.add_response("cancel", _("Cancel"))
|
dialog.add_response("cancel", _("Cancel"))
|
||||||
dialog.add_response("delete", _("Delete"))
|
dialog.add_response("delete", _("Delete"))
|
||||||
dialog.set_response_appearance("delete", Adw.ResponseAppearance.DESTRUCTIVE)
|
dialog.set_response_appearance("delete", Adw.ResponseAppearance.DESTRUCTIVE)
|
||||||
|
dialog.set_default_response("delete")
|
||||||
dialog.choose(
|
dialog.choose(
|
||||||
parent = self.manage_models_dialog,
|
parent = self.manage_models_dialog,
|
||||||
cancellable = None,
|
cancellable = None,
|
||||||
@@ -171,6 +175,7 @@ def remove_attached_file(self, name):
|
|||||||
dialog.add_response("cancel", _("Cancel"))
|
dialog.add_response("cancel", _("Cancel"))
|
||||||
dialog.add_response("remove", _("Remove"))
|
dialog.add_response("remove", _("Remove"))
|
||||||
dialog.set_response_appearance("remove", Adw.ResponseAppearance.DESTRUCTIVE)
|
dialog.set_response_appearance("remove", Adw.ResponseAppearance.DESTRUCTIVE)
|
||||||
|
dialog.set_default_response("remove")
|
||||||
dialog.choose(
|
dialog.choose(
|
||||||
parent = self,
|
parent = self,
|
||||||
cancellable = None,
|
cancellable = None,
|
||||||
@@ -210,11 +215,11 @@ def reconnect_remote(self, current_url, current_bearer_token):
|
|||||||
body=_("The remote instance has disconnected"),
|
body=_("The remote instance has disconnected"),
|
||||||
extra_child=container
|
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("close", _("Close Alpaca"))
|
||||||
dialog.add_response("local", _("Use local instance"))
|
dialog.add_response("local", _("Use local instance"))
|
||||||
dialog.add_response("remote", _("Connect"))
|
dialog.add_response("remote", _("Connect"))
|
||||||
dialog.set_response_appearance("remote", Adw.ResponseAppearance.SUGGESTED)
|
dialog.set_response_appearance("remote", Adw.ResponseAppearance.SUGGESTED)
|
||||||
|
dialog.set_default_response("remote")
|
||||||
dialog.choose(
|
dialog.choose(
|
||||||
parent = self,
|
parent = self,
|
||||||
cancellable = None,
|
cancellable = None,
|
||||||
@@ -243,6 +248,7 @@ def create_model_from_existing(self):
|
|||||||
dialog.add_response("cancel", _("Cancel"))
|
dialog.add_response("cancel", _("Cancel"))
|
||||||
dialog.add_response("accept", _("Accept"))
|
dialog.add_response("accept", _("Accept"))
|
||||||
dialog.set_response_appearance("accept", Adw.ResponseAppearance.SUGGESTED)
|
dialog.set_response_appearance("accept", Adw.ResponseAppearance.SUGGESTED)
|
||||||
|
dialog.set_default_response("accept")
|
||||||
dialog.choose(
|
dialog.choose(
|
||||||
parent = self,
|
parent = self,
|
||||||
cancellable = None,
|
cancellable = None,
|
||||||
@@ -271,7 +277,7 @@ def create_model_from_name_response(self, dialog, task, entry):
|
|||||||
|
|
||||||
def create_model_from_name(self):
|
def create_model_from_name(self):
|
||||||
entry = Gtk.Entry()
|
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(
|
dialog = Adw.AlertDialog(
|
||||||
heading=_("Pull Model"),
|
heading=_("Pull Model"),
|
||||||
body=_("Input the name of the model in this format\nname:tag"),
|
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("cancel", _("Cancel"))
|
||||||
dialog.add_response("accept", _("Accept"))
|
dialog.add_response("accept", _("Accept"))
|
||||||
dialog.set_response_appearance("accept", Adw.ResponseAppearance.SUGGESTED)
|
dialog.set_response_appearance("accept", Adw.ResponseAppearance.SUGGESTED)
|
||||||
|
dialog.set_default_response("accept")
|
||||||
dialog.choose(
|
dialog.choose(
|
||||||
parent = self,
|
parent = self,
|
||||||
cancellable = None,
|
cancellable = None,
|
||||||
@@ -323,7 +330,7 @@ def youtube_caption_response(self, dialog, task, video_url, caption_drop_down):
|
|||||||
yt = YouTube(video_url)
|
yt = YouTube(video_url)
|
||||||
text = "{}\n{}\n{}\n\n".format(yt.title, yt.author, yt.watch_url)
|
text = "{}\n{}\n{}\n\n".format(yt.title, yt.author, yt.watch_url)
|
||||||
selected_caption = caption_drop_down.get_selected_item().get_string()
|
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'))
|
text += "{}\n".format(event['segs'][0]['utf8'].replace('\n', '\\n'))
|
||||||
if not os.path.exists(os.path.join(self.cache_dir, 'tmp/youtube')):
|
if not os.path.exists(os.path.join(self.cache_dir, 'tmp/youtube')):
|
||||||
os.makedirs(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
|
return
|
||||||
caption_list = Gtk.StringList()
|
caption_list = Gtk.StringList()
|
||||||
for caption in captions:
|
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(
|
caption_drop_down = Gtk.DropDown(
|
||||||
enable_search=True,
|
enable_search=len(captions) > 10,
|
||||||
model=caption_list
|
model=caption_list
|
||||||
)
|
)
|
||||||
dialog = Adw.AlertDialog(
|
dialog = Adw.AlertDialog(
|
||||||
@@ -355,6 +362,7 @@ def youtube_caption(self, video_url):
|
|||||||
dialog.add_response("cancel", _("Cancel"))
|
dialog.add_response("cancel", _("Cancel"))
|
||||||
dialog.add_response("accept", _("Accept"))
|
dialog.add_response("accept", _("Accept"))
|
||||||
dialog.set_response_appearance("accept", Adw.ResponseAppearance.SUGGESTED)
|
dialog.set_response_appearance("accept", Adw.ResponseAppearance.SUGGESTED)
|
||||||
|
dialog.set_default_response("accept")
|
||||||
dialog.choose(
|
dialog.choose(
|
||||||
parent = self,
|
parent = self,
|
||||||
cancellable = None,
|
cancellable = None,
|
||||||
@@ -393,6 +401,7 @@ def attach_website(self, url):
|
|||||||
dialog.add_response("cancel", _("Cancel"))
|
dialog.add_response("cancel", _("Cancel"))
|
||||||
dialog.add_response("accept", _("Accept"))
|
dialog.add_response("accept", _("Accept"))
|
||||||
dialog.set_response_appearance("accept", Adw.ResponseAppearance.SUGGESTED)
|
dialog.set_response_appearance("accept", Adw.ResponseAppearance.SUGGESTED)
|
||||||
|
dialog.set_default_response("accept")
|
||||||
dialog.choose(
|
dialog.choose(
|
||||||
parent = self,
|
parent = self,
|
||||||
cancellable = None,
|
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
|
Handles running, stopping and resetting the integrated Ollama instance
|
||||||
"""
|
"""
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import threading
|
||||||
import os
|
import os
|
||||||
from time import sleep
|
from time import sleep
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
@@ -15,6 +16,14 @@ instance = None
|
|||||||
port = 11435
|
port = 11435
|
||||||
overrides = {}
|
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():
|
def start():
|
||||||
if not os.path.isdir(os.path.join(cache_dir, 'tmp/ollama')):
|
if not os.path.isdir(os.path.join(cache_dir, 'tmp/ollama')):
|
||||||
os.mkdir(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["OLLAMA_HOST"] = f"127.0.0.1:{port}" # You can't change this directly sorry :3
|
||||||
params["HOME"] = data_dir
|
params["HOME"] = data_dir
|
||||||
params["TMPDIR"] = os.path.join(cache_dir, 'tmp/ollama')
|
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.info("Starting Alpaca's Ollama instance...")
|
||||||
logger.debug(params)
|
logger.debug(params)
|
||||||
sleep(1)
|
sleep(1)
|
||||||
|
|||||||
@@ -43,7 +43,8 @@ translators = [
|
|||||||
'CounterFlow64 (Norwegian) https://github.com/CounterFlow64',
|
'CounterFlow64 (Norwegian) https://github.com/CounterFlow64',
|
||||||
'Aritra Saha (Bengali) https://github.com/olumolu',
|
'Aritra Saha (Bengali) https://github.com/olumolu',
|
||||||
'Yuehao Sui (Simplified Chinese) https://github.com/8ar10der',
|
'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):
|
class AlpacaApplication(Adw.Application):
|
||||||
@@ -52,7 +53,7 @@ class AlpacaApplication(Adw.Application):
|
|||||||
def __init__(self, version):
|
def __init__(self, version):
|
||||||
super().__init__(application_id='com.jeffser.Alpaca',
|
super().__init__(application_id='com.jeffser.Alpaca',
|
||||||
flags=Gio.ApplicationFlags.DEFAULT_FLAGS)
|
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('preferences', lambda *_: AlpacaWindow.show_preferences_dialog(self.props.active_window), ['<primary>comma'])
|
||||||
self.create_action('about', self.on_about_action)
|
self.create_action('about', self.on_about_action)
|
||||||
self.version = version
|
self.version = version
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
.message_input_scroll_window > * {
|
|
||||||
box-shadow: none;
|
|
||||||
border-width: 0;
|
|
||||||
}
|
|
||||||
.message_text_view, .modelfile_textview {
|
.message_text_view, .modelfile_textview {
|
||||||
background-color: rgba(0,0,0,0);
|
background-color: rgba(0,0,0,0);
|
||||||
}
|
}
|
||||||
@@ -12,3 +8,23 @@
|
|||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
padding: 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
|
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 io import BytesIO
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from pypdf import PdfReader
|
from pypdf import PdfReader
|
||||||
@@ -64,6 +64,30 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
pulling_models = {}
|
pulling_models = {}
|
||||||
chats = {"chats": {_("New Chat"): {"messages": {}}}, "selected_chat": "New Chat", "order": []}
|
chats = {"chats": {_("New Chat"): {"messages": {}}}, "selected_chat": "New Chat", "order": []}
|
||||||
attachments = {}
|
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 elements
|
||||||
override_HSA_OVERRIDE_GFX_VERSION = Gtk.Template.Child()
|
override_HSA_OVERRIDE_GFX_VERSION = Gtk.Template.Child()
|
||||||
@@ -107,8 +131,6 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
file_filter_gguf = Gtk.Template.Child()
|
file_filter_gguf = Gtk.Template.Child()
|
||||||
file_filter_attachments = Gtk.Template.Child()
|
file_filter_attachments = Gtk.Template.Child()
|
||||||
attachment_button = 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()
|
chat_right_click_menu = Gtk.Template.Child()
|
||||||
model_tag_list_box = Gtk.Template.Child()
|
model_tag_list_box = Gtk.Template.Child()
|
||||||
navigation_view_manage_models = Gtk.Template.Child()
|
navigation_view_manage_models = Gtk.Template.Child()
|
||||||
@@ -118,6 +140,10 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
model_searchbar = Gtk.Template.Child()
|
model_searchbar = Gtk.Template.Child()
|
||||||
no_results_page = Gtk.Template.Child()
|
no_results_page = Gtk.Template.Child()
|
||||||
model_link_button = 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()
|
manage_models_dialog = Gtk.Template.Child()
|
||||||
pulling_model_list_box = Gtk.Template.Child()
|
pulling_model_list_box = Gtk.Template.Child()
|
||||||
@@ -136,38 +162,29 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
|
|
||||||
style_manager = Adw.StyleManager()
|
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()
|
@Gtk.Template.Callback()
|
||||||
def stop_message(self, button=None):
|
def stop_message(self, button=None):
|
||||||
if self.loading_spinner:
|
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.toggle_ui_sensitive(True)
|
||||||
self.switch_send_stop_button()
|
self.switch_send_stop_button(True)
|
||||||
self.bot_message = None
|
self.bot_message = None
|
||||||
self.bot_message_box = None
|
self.bot_message_box = None
|
||||||
self.bot_message_view = None
|
self.bot_message_view = None
|
||||||
self.bot_message_button_container = None
|
self.bot_message_button_container = None
|
||||||
|
self.save_history()
|
||||||
|
|
||||||
@Gtk.Template.Callback()
|
@Gtk.Template.Callback()
|
||||||
def send_message(self, button=None):
|
def send_message(self, button=None):
|
||||||
if self.editing_message:
|
if self.editing_message:
|
||||||
self.editing_message["button_container"].set_visible(True)
|
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_cursor_visible(False)
|
||||||
self.editing_message["text_view"].set_editable(False)
|
self.editing_message["text_view"].set_editable(False)
|
||||||
buffer = self.editing_message["text_view"].get_buffer()
|
buffer = self.editing_message["text_view"].get_buffer()
|
||||||
@@ -178,8 +195,7 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
self.editing_message = None
|
self.editing_message = None
|
||||||
self.save_history()
|
self.save_history()
|
||||||
self.show_toast(_("Message edited successfully"), self.main_overlay)
|
self.show_toast(_("Message edited successfully"), self.main_overlay)
|
||||||
|
if button and not button.get_visible():
|
||||||
if self.bot_message or self.get_focus() not in (self.message_text_view, self.send_button):
|
|
||||||
return
|
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):
|
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
|
return
|
||||||
@@ -191,7 +207,7 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
self.chats['order'].remove(self.chats['selected_chat'])
|
self.chats['order'].remove(self.chats['selected_chat'])
|
||||||
self.chats['order'].insert(0, self.chats['selected_chat'])
|
self.chats['order'].insert(0, self.chats['selected_chat'])
|
||||||
self.save_history()
|
self.save_history()
|
||||||
current_model = self.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:
|
if current_model is None:
|
||||||
self.show_toast(_("Please select a model before chatting"), self.main_overlay)
|
self.show_toast(_("Please select a model before chatting"), self.main_overlay)
|
||||||
return
|
return
|
||||||
@@ -232,7 +248,7 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
"options": {"temperature": self.model_tweaks["temperature"], "seed": self.model_tweaks["seed"]},
|
"options": {"temperature": self.model_tweaks["temperature"], "seed": self.model_tweaks["seed"]},
|
||||||
"keep_alive": f"{self.model_tweaks['keep_alive']}m"
|
"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.toggle_ui_sensitive(False)
|
||||||
|
|
||||||
#self.attachments[name] = {"path": file_path, "type": file_type, "content": content}
|
#self.attachments[name] = {"path": file_path, "type": file_type, "content": content}
|
||||||
@@ -245,6 +261,9 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
bot_id=self.generate_uuid()
|
bot_id=self.generate_uuid()
|
||||||
self.show_message("", True, message_id=bot_id)
|
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 = threading.Thread(target=self.run_message, args=(data['messages'], data['model'], bot_id))
|
||||||
thread.start()
|
thread.start()
|
||||||
if len(data['messages']) == 1:
|
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"]:
|
if row and row.get_child().get_name() != self.chats["selected_chat"]:
|
||||||
self.chats["selected_chat"] = row.get_child().get_name()
|
self.chats["selected_chat"] = row.get_child().get_name()
|
||||||
self.load_history_into_chat()
|
self.load_history_into_chat()
|
||||||
if len(self.chats["chats"][self.chats["selected_chat"]]["messages"].keys()) > 0:
|
if len(self.chats["chats"][self.chats["selected_chat"]]["messages"]) > 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.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, m in enumerate(self.local_models):
|
||||||
for i in range(self.model_string_list.get_n_items()):
|
if m == last_model_used:
|
||||||
if self.model_string_list.get_string(i) == last_model_used:
|
self.model_list_box.select_row(self.model_list_box.get_row_at_index(i))
|
||||||
self.model_drop_down.set_selected(i)
|
|
||||||
break
|
break
|
||||||
|
else:
|
||||||
|
self.load_history_into_chat()
|
||||||
self.save_history()
|
self.save_history()
|
||||||
|
|
||||||
@Gtk.Template.Callback()
|
@Gtk.Template.Callback()
|
||||||
@@ -303,7 +323,7 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
self.remote_url = entry.get_text()
|
self.remote_url = entry.get_text()
|
||||||
logger.debug(f"Changing remote url: {self.remote_url}")
|
logger.debug(f"Changing remote url: {self.remote_url}")
|
||||||
if self.run_remote:
|
if self.run_remote:
|
||||||
connection_handler.url = self.remote_url
|
connection_handler.URL = self.remote_url
|
||||||
if self.verify_connection() == False:
|
if self.verify_connection() == False:
|
||||||
entry.set_css_classes(["error"])
|
entry.set_css_classes(["error"])
|
||||||
self.show_toast(_("Failed to connect to server"), self.preferences_dialog)
|
self.show_toast(_("Failed to connect to server"), self.preferences_dialog)
|
||||||
@@ -314,23 +334,11 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
self.save_server_config()
|
self.save_server_config()
|
||||||
return
|
return
|
||||||
if self.remote_url and self.run_remote:
|
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:
|
if self.verify_connection() == False:
|
||||||
entry.set_css_classes(["error"])
|
entry.set_css_classes(["error"])
|
||||||
self.show_toast(_("Failed to connect to server"), self.preferences_dialog)
|
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()
|
@Gtk.Template.Callback()
|
||||||
def closing_app(self, user_data):
|
def closing_app(self, user_data):
|
||||||
if self.get_hide_on_close():
|
if self.get_hide_on_close():
|
||||||
@@ -338,6 +346,7 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
else:
|
else:
|
||||||
logger.info("Closing app...")
|
logger.info("Closing app...")
|
||||||
local_instance.stop()
|
local_instance.stop()
|
||||||
|
self.get_application().quit()
|
||||||
|
|
||||||
@Gtk.Template.Callback()
|
@Gtk.Template.Callback()
|
||||||
def model_spin_changed(self, spin):
|
def model_spin_changed(self, spin):
|
||||||
@@ -427,26 +436,78 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
self.available_model_list_box.set_visible(True)
|
self.available_model_list_box.set_visible(True)
|
||||||
self.no_results_page.set_visible(False)
|
self.no_results_page.set_visible(False)
|
||||||
|
|
||||||
def manage_models_button_activate(self, button=None):
|
@Gtk.Template.Callback()
|
||||||
logger.debug(f"Managing models")
|
def close_model_popup(self, *_):
|
||||||
self.update_list_local_models()
|
self.model_popover.hide()
|
||||||
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"]
|
@Gtk.Template.Callback()
|
||||||
last_model_used = self.convert_model_name(last_model_used, 0)
|
def change_model(self, listbox=None, row=None):
|
||||||
for i in range(self.model_string_list.get_n_items()):
|
if not row:
|
||||||
if self.model_string_list.get_string(i) == last_model_used:
|
current_model = self.model_selector_button.get_name()
|
||||||
self.model_drop_down.set_selected(i)
|
if current_model != 'NO_MODEL':
|
||||||
break
|
for i, m in enumerate(self.local_models):
|
||||||
self.manage_models_dialog.present(self)
|
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
|
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:
|
try:
|
||||||
return "{} ({})".format(name.split(":")[0].replace("-", " ").title(), name.split(":")[1])
|
if mode == 0:
|
||||||
if mode == 1:
|
return "{} ({})".format(name.split(":")[0].replace("-", " ").title(), name.split(":")[1])
|
||||||
return "{}:{}".format(name.split(" (")[0].replace(" ", "-").lower(), name.split(" (")[1][:-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):
|
def get_current_model(self, mode:int) -> str:
|
||||||
new_text = ''.join([char for char in text if char.isalnum() or char in ['-', '.', ':', '_']])
|
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:
|
if new_text != text:
|
||||||
editable.stop_emission_by_name("insert-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())
|
modelfile_buffer.delete(modelfile_buffer.get_start_iter(), modelfile_buffer.get_end_iter())
|
||||||
self.create_model_system.set_text('')
|
self.create_model_system.set_text('')
|
||||||
if not file:
|
if not file:
|
||||||
response = connection_handler.simple_post(f"{connection_handler.url}/api/show", json.dumps({"name": 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:
|
if response.status_code == 200:
|
||||||
data = json.loads(response.text)
|
data = json.loads(response.text)
|
||||||
modelfile = []
|
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)):
|
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))
|
shutil.rmtree(os.path.join(self.data_dir, "chats", self.chats['selected_chat'], message_id))
|
||||||
self.save_history()
|
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):
|
def copy_message(self, message_element):
|
||||||
logger.debug("Copying message")
|
logger.debug("Copying message")
|
||||||
@@ -529,6 +592,13 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
text_view.set_cursor_visible(True)
|
text_view.set_cursor_visible(True)
|
||||||
|
|
||||||
self.editing_message = {"text_view": text_view, "id": message_id, "button_container": button_container, "footer": footer}
|
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):
|
def preview_file(self, file_path, file_type, presend_name):
|
||||||
logger.debug(f"Previewing file: {file_path}")
|
logger.debug(f"Previewing file: {file_path}")
|
||||||
@@ -558,7 +628,7 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
self.file_preview_text_view.set_visible(True)
|
self.file_preview_text_view.set_visible(True)
|
||||||
buffer = self.file_preview_text_view.get_buffer()
|
buffer = self.file_preview_text_view.get_buffer()
|
||||||
buffer.delete(buffer.get_start_iter(), buffer.get_end_iter())
|
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':
|
if file_type == 'youtube':
|
||||||
self.file_preview_dialog.set_title(content.split('\n')[0])
|
self.file_preview_dialog.set_title(content.split('\n')[0])
|
||||||
self.file_preview_open_button.set_name(content.split('\n')[2])
|
self.file_preview_open_button.set_name(content.split('\n')[2])
|
||||||
@@ -593,6 +663,8 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
return messages
|
return messages
|
||||||
|
|
||||||
def generate_chat_title(self, message, label_element):
|
def generate_chat_title(self, message, label_element):
|
||||||
|
if not label_element.get_name().startswith(_("New Chat")):
|
||||||
|
return
|
||||||
logger.debug("Generating chat title")
|
logger.debug("Generating chat title")
|
||||||
prompt = f"""
|
prompt = f"""
|
||||||
Generate a title following these rules:
|
Generate a title following these rules:
|
||||||
@@ -605,11 +677,11 @@ Generate a title following these rules:
|
|||||||
```PROMPT
|
```PROMPT
|
||||||
{message['content']}
|
{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}
|
data = {"model": current_model, "prompt": prompt, "stream": False}
|
||||||
if 'images' in message:
|
if 'images' in message:
|
||||||
data["images"] = message['images']
|
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 = json.loads(response.text)["response"].strip().removeprefix("Title: ").removeprefix("title: ").strip('\'"').replace('\n', ' ').title().replace('\'S', '\'s')
|
||||||
new_chat_name = new_chat_name[:50] + (new_chat_name[50:] and '...')
|
new_chat_name = new_chat_name[:50] + (new_chat_name[50:] and '...')
|
||||||
@@ -620,14 +692,11 @@ Generate a title following these rules:
|
|||||||
editable=False,
|
editable=False,
|
||||||
focusable=True,
|
focusable=True,
|
||||||
wrap_mode= Gtk.WrapMode.WORD,
|
wrap_mode= Gtk.WrapMode.WORD,
|
||||||
margin_top=12,
|
|
||||||
margin_bottom=12,
|
|
||||||
margin_start=12,
|
|
||||||
margin_end=12,
|
|
||||||
hexpand=True,
|
hexpand=True,
|
||||||
cursor_visible=False,
|
css_classes=["flat", "response_message"] if bot else ["flat", "user_message"],
|
||||||
css_classes=["flat"],
|
|
||||||
)
|
)
|
||||||
|
if not bot:
|
||||||
|
message_text.update_property([4, 7, 1], [_("User message"), True, msg])
|
||||||
message_buffer = message_text.get_buffer()
|
message_buffer = message_text.get_buffer()
|
||||||
message_buffer.insert(message_buffer.get_end_iter(), msg)
|
message_buffer.insert(message_buffer.get_end_iter(), msg)
|
||||||
if footer is not None:
|
if footer is not None:
|
||||||
@@ -666,7 +735,8 @@ Generate a title following these rules:
|
|||||||
message_box = Gtk.Box(
|
message_box = Gtk.Box(
|
||||||
orientation=1,
|
orientation=1,
|
||||||
halign='fill',
|
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)
|
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),
|
name=os.path.join(self.data_dir, "chats", "{selected_chat}", message_id, image),
|
||||||
tooltip_text=_("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))
|
button.connect("clicked", lambda button, file_path=path: self.preview_file(file_path, 'image', None))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(e)
|
logger.error(e)
|
||||||
@@ -722,6 +793,7 @@ Generate a title following these rules:
|
|||||||
css_classes=["flat", "chat_image_button"],
|
css_classes=["flat", "chat_image_button"],
|
||||||
tooltip_text=_("Missing Image")
|
tooltip_text=_("Missing Image")
|
||||||
)
|
)
|
||||||
|
image_texture.update_property([4], [_("Missing image")])
|
||||||
button.connect("clicked", lambda button : self.show_toast(_("Missing image"), self.main_overlay))
|
button.connect("clicked", lambda button : self.show_toast(_("Missing image"), self.main_overlay))
|
||||||
image_container.append(button)
|
image_container.append(button)
|
||||||
message_box.append(image_scroller)
|
message_box.append(image_scroller)
|
||||||
@@ -784,9 +856,8 @@ Generate a title following these rules:
|
|||||||
def update_list_local_models(self):
|
def update_list_local_models(self):
|
||||||
logger.debug("Updating list of local models")
|
logger.debug("Updating list of local models")
|
||||||
self.local_models = []
|
self.local_models = []
|
||||||
response = connection_handler.simple_get(f"{connection_handler.url}/api/tags")
|
response = connection_handler.simple_get(f"{connection_handler.URL}/api/tags")
|
||||||
for i in range(self.model_string_list.get_n_items() -1, -1, -1):
|
self.model_list_box.remove_all()
|
||||||
self.model_string_list.remove(i)
|
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
self.local_model_list_box.remove_all()
|
self.local_model_list_box.remove_all()
|
||||||
if len(json.loads(response.text)['models']) == 0:
|
if len(json.loads(response.text)['models']) == 0:
|
||||||
@@ -810,9 +881,17 @@ Generate a title following these rules:
|
|||||||
model_row.add_suffix(button)
|
model_row.add_suffix(button)
|
||||||
self.local_model_list_box.append(model_row)
|
self.local_model_list_box.append(model_row)
|
||||||
|
|
||||||
self.model_string_list.append(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.local_models.append(model["name"])
|
||||||
#self.verify_if_image_can_be_used()
|
|
||||||
else:
|
else:
|
||||||
self.connection_error()
|
self.connection_error()
|
||||||
|
|
||||||
@@ -822,7 +901,7 @@ Generate a title following these rules:
|
|||||||
|
|
||||||
def verify_connection(self):
|
def verify_connection(self):
|
||||||
try:
|
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:
|
if response.status_code == 200:
|
||||||
self.save_server_config()
|
self.save_server_config()
|
||||||
self.update_list_local_models()
|
self.update_list_local_models()
|
||||||
@@ -845,7 +924,7 @@ Generate a title following these rules:
|
|||||||
parts.append({"type": "normal", "text": normal_text.strip()})
|
parts.append({"type": "normal", "text": normal_text.strip()})
|
||||||
language = match.group(1)
|
language = match.group(1)
|
||||||
code_text = match.group(2)
|
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
|
pos = end
|
||||||
# Match code blocks without language
|
# Match code blocks without language
|
||||||
no_lang_code_block_pattern = re.compile(r'`\n(.*?)\n`', re.DOTALL)
|
no_lang_code_block_pattern = re.compile(r'`\n(.*?)\n`', re.DOTALL)
|
||||||
@@ -883,10 +962,8 @@ Generate a title following these rules:
|
|||||||
editable=False,
|
editable=False,
|
||||||
focusable=True,
|
focusable=True,
|
||||||
wrap_mode= Gtk.WrapMode.WORD,
|
wrap_mode= Gtk.WrapMode.WORD,
|
||||||
margin_top=12,
|
|
||||||
margin_bottom=12,
|
|
||||||
hexpand=True,
|
hexpand=True,
|
||||||
css_classes=["flat"]
|
css_classes=["flat", "response_message"]
|
||||||
)
|
)
|
||||||
message_buffer = message_text.get_buffer()
|
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')))
|
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)
|
self.bot_message_box.append(message_text)
|
||||||
elif part['type'] == 'code':
|
elif part['type'] == 'code':
|
||||||
language = None
|
language = None
|
||||||
@@ -932,10 +1010,11 @@ Generate a title following these rules:
|
|||||||
buffer.set_style_scheme(source_style)
|
buffer.set_style_scheme(source_style)
|
||||||
source_view = GtkSource.View(
|
source_view = GtkSource.View(
|
||||||
auto_indent=True, indent_width=4, buffer=buffer, show_line_numbers=True,
|
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)
|
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 = 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))
|
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"))
|
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):
|
def update_bot_message(self, data, message_id):
|
||||||
if self.bot_message is None:
|
if self.bot_message is None:
|
||||||
self.save_history()
|
|
||||||
sys.exit()
|
sys.exit()
|
||||||
vadjustment = self.chat_window.get_vadjustment()
|
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())
|
GLib.idle_add(vadjustment.set_value, vadjustment.get_upper())
|
||||||
if 'done' in data and data['done']:
|
if 'done' in data and data['done']:
|
||||||
formated_date = GLib.markup_escape_text(self.generate_datetime_format(datetime.strptime(self.chats["chats"][self.chats["selected_chat"]]["messages"][message_id]["date"], '%Y/%m/%d %H:%M:%S')))
|
formated_date = GLib.markup_escape_text(self.generate_datetime_format(datetime.strptime(self.chats["chats"][self.chats["selected_chat"]]["messages"][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]
|
first_paragraph = self.bot_message.get_text(self.bot_message.get_start_iter(), self.bot_message.get_end_iter(), False).split("\n")[0]
|
||||||
GLib.idle_add(self.show_notification, self.chats["selected_chat"], first_paragraph[:100] + (first_paragraph[100:] and '...'), Gio.ThemedIcon.new("chat-message-new-symbolic"))
|
GLib.idle_add(self.show_notification, self.chats["selected_chat"], first_paragraph[:100] + (first_paragraph[100:] and '...'), Gio.ThemedIcon.new("chat-message-new-symbolic"))
|
||||||
else:
|
else:
|
||||||
if not self.chats["chats"][self.chats["selected_chat"]]["messages"][message_id]["content"] and self.loading_spinner:
|
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
|
self.loading_spinner = None
|
||||||
GLib.idle_add(self.bot_message.insert, self.bot_message.get_end_iter(), data['message']['content'])
|
GLib.idle_add(self.bot_message.insert, self.bot_message.get_end_iter(), data['message']['content'])
|
||||||
self.chats["chats"][self.chats["selected_chat"]]["messages"][message_id]['content'] += 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]:
|
for element in [self.chat_list_box, self.add_chat_button, self.secondary_menu_button]:
|
||||||
element.set_sensitive(status)
|
element.set_sensitive(status)
|
||||||
|
|
||||||
def switch_send_stop_button(self):
|
def switch_send_stop_button(self, send:bool):
|
||||||
self.stop_button.set_visible(self.send_button.get_visible())
|
self.stop_button.set_visible(not send)
|
||||||
self.send_button.set_visible(not self.send_button.get_visible())
|
self.send_button.set_visible(send)
|
||||||
|
|
||||||
def run_message(self, messages, model, message_id):
|
def run_message(self, messages, model, message_id):
|
||||||
logger.debug("Running message")
|
logger.debug("Running message")
|
||||||
self.bot_message_button_container.set_visible(False)
|
self.bot_message_button_container.set_visible(False)
|
||||||
self.chats["chats"][self.chats["selected_chat"]]["messages"][message_id] = {
|
if message_id not in self.chats["chats"][self.chats["selected_chat"]]["messages"]:
|
||||||
"role": "assistant",
|
self.chats["chats"][self.chats["selected_chat"]]["messages"][message_id] = {
|
||||||
"model": model,
|
"role": "assistant",
|
||||||
"date": datetime.now().strftime("%Y/%m/%d %H:%M:%S"),
|
"model": model,
|
||||||
"content": ''
|
"date": datetime.now().strftime("%Y/%m/%d %H:%M:%S"),
|
||||||
}
|
"content": ''
|
||||||
|
}
|
||||||
if self.regenerate_button:
|
if self.regenerate_button:
|
||||||
GLib.idle_add(self.chat_container.remove, self.regenerate_button)
|
GLib.idle_add(self.chat_container.remove, self.regenerate_button)
|
||||||
try:
|
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:
|
if response.status_code != 200:
|
||||||
raise Exception('Network Error')
|
raise Exception('Network Error')
|
||||||
GLib.idle_add(self.add_code_blocks)
|
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)
|
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))
|
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:
|
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)
|
GLib.idle_add(self.toggle_ui_sensitive, True)
|
||||||
if self.loading_spinner:
|
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
|
self.loading_spinner = None
|
||||||
|
|
||||||
def regenerate_message(self, message_id, bot_message_box, bot_message_button_container):
|
def regenerate_message(self, message_id, bot_message_box, bot_message_button_container):
|
||||||
self.bot_message_button_container = bot_message_button_container
|
if not self.bot_message:
|
||||||
self.bot_message_view = Gtk.TextView(
|
self.bot_message_button_container = bot_message_button_container
|
||||||
editable=False,
|
self.bot_message_view = Gtk.TextView(
|
||||||
focusable=True,
|
editable=False,
|
||||||
wrap_mode= Gtk.WrapMode.WORD,
|
focusable=True,
|
||||||
margin_top=12,
|
wrap_mode= Gtk.WrapMode.WORD,
|
||||||
margin_bottom=12,
|
margin_top=12,
|
||||||
hexpand=True,
|
margin_bottom=12,
|
||||||
css_classes=["flat"]
|
hexpand=True,
|
||||||
)
|
css_classes=["flat"]
|
||||||
self.bot_message = self.bot_message_view.get_buffer()
|
)
|
||||||
for widget in list(bot_message_box):
|
self.bot_message = self.bot_message_view.get_buffer()
|
||||||
bot_message_box.remove(widget)
|
self.bot_message_box = bot_message_box
|
||||||
bot_message_box.append(self.bot_message_view)
|
for widget in list(bot_message_box):
|
||||||
history = self.convert_history_to_ollama()[:list(self.chats["chats"][self.chats["selected_chat"]]["messages"].keys()).index(message_id)]
|
bot_message_box.remove(widget)
|
||||||
if message_id in self.chats["chats"][self.chats["selected_chat"]]["messages"]:
|
self.loading_spinner = Gtk.Spinner(spinning=True, margin_top=12, margin_bottom=12, hexpand=True)
|
||||||
del self.chats["chats"][self.chats["selected_chat"]]["messages"][message_id]
|
bot_message_box.append(self.loading_spinner)
|
||||||
data = {
|
bot_message_box.append(self.bot_message_view)
|
||||||
"model": self.convert_model_name(self.model_drop_down.get_selected_item().get_string(), 1),
|
if message_id in self.chats["chats"][self.chats["selected_chat"]]["messages"]:
|
||||||
"messages": history,
|
self.chats["chats"][self.chats["selected_chat"]]["messages"][message_id]['content'] = ''
|
||||||
"options": {"temperature": self.model_tweaks["temperature"], "seed": self.model_tweaks["seed"]},
|
history = self.convert_history_to_ollama()[:list(self.chats["chats"][self.chats["selected_chat"]]["messages"].keys()).index(message_id)]
|
||||||
"keep_alive": f"{self.model_tweaks['keep_alive']}m"
|
data = {
|
||||||
}
|
"model": self.get_current_model(1),
|
||||||
self.switch_send_stop_button()
|
"messages": history,
|
||||||
self.toggle_ui_sensitive(False)
|
"options": {"temperature": self.model_tweaks["temperature"], "seed": self.model_tweaks["seed"]},
|
||||||
thread = threading.Thread(target=self.run_message, args=(data['messages'], data['model'], message_id))
|
"keep_alive": f"{self.model_tweaks['keep_alive']}m"
|
||||||
thread.start()
|
}
|
||||||
|
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):
|
def pull_model_update(self, data, model_name):
|
||||||
if 'error' in data:
|
if 'error' in data:
|
||||||
@@ -1093,11 +1178,12 @@ Generate a title following these rules:
|
|||||||
def pull_model_process(self, model, modelfile):
|
def pull_model_process(self, model, modelfile):
|
||||||
if modelfile:
|
if modelfile:
|
||||||
data = {"name": model, "modelfile": 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:
|
else:
|
||||||
data = {"name": model}
|
data = {"name": model}
|
||||||
response = connection_handler.stream_post(f"{connection_handler.url}/api/pull", data=json.dumps(data), callback=lambda data, model_name=model: self.pull_model_update(data, model_name))
|
response = connection_handler.stream_post(f"{connection_handler.URL}/api/pull", data=json.dumps(data), callback=lambda data, model_name=model: self.pull_model_update(data, model_name))
|
||||||
GLib.idle_add(self.update_list_local_models)
|
GLib.idle_add(self.update_list_local_models)
|
||||||
|
GLib.idle_add(self.change_model)
|
||||||
|
|
||||||
if response.status_code == 200 and 'error' not in self.pulling_models[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"))
|
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_name(self.available_models[model_name]['url'])
|
||||||
self.model_link_button.set_tooltip_text(self.available_models[model_name]['url'])
|
self.model_link_button.set_tooltip_text(self.available_models[model_name]['url'])
|
||||||
self.available_model_list_box.unselect_all()
|
self.available_model_list_box.unselect_all()
|
||||||
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()
|
self.model_tag_list_box.remove_all()
|
||||||
tags = self.available_models[model_name]['tags']
|
tags = self.available_models[model_name]['tags']
|
||||||
for tag_data in tags:
|
for tag_data in tags:
|
||||||
@@ -1174,12 +1259,24 @@ Generate a title following these rules:
|
|||||||
subtitle = tag_data[1],
|
subtitle = tag_data[1],
|
||||||
name = f"{model_name}:{tag_data[0]}"
|
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)
|
self.model_tag_list_box.append(tag_row)
|
||||||
|
return True
|
||||||
|
|
||||||
def update_list_available_models(self):
|
def update_list_available_models(self):
|
||||||
logger.debug("Updating list of available models")
|
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()
|
self.available_model_list_box.remove_all()
|
||||||
for name, model_info in self.available_models.items():
|
for name, model_info in self.available_models.items():
|
||||||
model = Adw.ActionRow(
|
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 ""),
|
subtitle = available_models_descriptions.descriptions[name] + ("\n\n<b>{}</b>".format(_("Image Recognition")) if model_info['image'] else ""),
|
||||||
name = name
|
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 = Gtk.Image.new_from_icon_name("go-next")
|
||||||
next_icon.set_margin_start(5)
|
next_icon.set_margin_start(5)
|
||||||
|
next_icon.update_property([4], [_("Enter download menu for {}").format(name.replace("-", ""))])
|
||||||
model.add_suffix(next_icon)
|
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)
|
self.available_model_list_box.append(model)
|
||||||
|
|
||||||
def save_history(self):
|
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:
|
with open(os.path.join(self.data_dir, "chats", "chats.json"), "w+", encoding="utf-8") as f:
|
||||||
json.dump(self.chats, f, indent=4)
|
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):
|
def load_history_into_chat(self):
|
||||||
for widget in list(self.chat_container): self.chat_container.remove(widget)
|
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():
|
self.chat_welcome_screen = None
|
||||||
if message:
|
if len(self.chats['chats'][self.chats["selected_chat"]]['messages']) > 0:
|
||||||
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')))
|
for key, message in self.chats['chats'][self.chats["selected_chat"]]['messages'].items():
|
||||||
if message['role'] == 'user':
|
if message:
|
||||||
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)
|
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')))
|
||||||
else:
|
if message['role'] == 'user':
|
||||||
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.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)
|
||||||
self.add_code_blocks()
|
else:
|
||||||
self.bot_message = None
|
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):
|
def load_history(self):
|
||||||
logger.debug("Loading history")
|
logger.debug("Loading history")
|
||||||
@@ -1227,13 +1369,17 @@ Generate a title following these rules:
|
|||||||
self.chats["order"] = []
|
self.chats["order"] = []
|
||||||
for chat_name in self.chats["chats"].keys():
|
for chat_name in self.chats["chats"].keys():
|
||||||
self.chats["order"].append(chat_name)
|
self.chats["order"].append(chat_name)
|
||||||
|
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:
|
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.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, m in enumerate(self.local_models):
|
||||||
for i in range(self.model_string_list.get_n_items()):
|
if m == last_model_used:
|
||||||
if self.model_string_list.get_string(i) == last_model_used:
|
self.model_list_box.select_row(self.model_list_box.get_row_at_index(i))
|
||||||
self.model_drop_down.set_selected(i)
|
|
||||||
break
|
break
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(e)
|
logger.error(e)
|
||||||
self.chats = {"chats": {}, "selected_chat": None, "order": []}
|
self.chats = {"chats": {}, "selected_chat": None, "order": []}
|
||||||
@@ -1263,8 +1409,9 @@ Generate a title following these rules:
|
|||||||
def clear_chat(self):
|
def clear_chat(self):
|
||||||
logger.info("Clearing chat")
|
logger.info("Clearing chat")
|
||||||
for widget in list(self.chat_container): self.chat_container.remove(widget)
|
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.save_history()
|
||||||
|
self.load_history_into_chat()
|
||||||
|
|
||||||
def delete_chat(self, chat_name):
|
def delete_chat(self, chat_name):
|
||||||
logger.info("Deleting chat")
|
logger.info("Deleting chat")
|
||||||
@@ -1279,6 +1426,14 @@ Generate a title following these rules:
|
|||||||
if self.chats['selected_chat'] == chat_name:
|
if self.chats['selected_chat'] == chat_name:
|
||||||
self.chat_list_box.select_row(self.chat_list_box.get_row_at_index(0))
|
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):
|
def rename_chat(self, old_chat_name, new_chat_name, label_element):
|
||||||
logger.info(f"Renaming chat \"{old_chat_name}\" -> \"{new_chat_name}\"")
|
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())
|
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.chats["order"].insert(0, chat_name)
|
||||||
self.save_history()
|
self.save_history()
|
||||||
self.new_chat_element(chat_name, True, False)
|
self.new_chat_element(chat_name, True, False)
|
||||||
|
self.set_focus(self.message_text_view)
|
||||||
|
|
||||||
def stop_pull_model(self, model_name):
|
def stop_pull_model(self, model_name):
|
||||||
logger.debug("Stopping model pull")
|
logger.debug("Stopping model pull")
|
||||||
@@ -1308,10 +1464,11 @@ Generate a title following these rules:
|
|||||||
|
|
||||||
def delete_model(self, model_name):
|
def delete_model(self, model_name):
|
||||||
logger.debug("Deleting model")
|
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()
|
self.update_list_local_models()
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
self.show_toast(_("Model deleted successfully"), self.manage_models_overlay)
|
self.show_toast(_("Model deleted successfully"), self.manage_models_overlay)
|
||||||
|
self.change_model()
|
||||||
else:
|
else:
|
||||||
self.manage_models_dialog.close()
|
self.manage_models_dialog.close()
|
||||||
self.connection_error()
|
self.connection_error()
|
||||||
@@ -1322,6 +1479,7 @@ Generate a title following these rules:
|
|||||||
menu_model=self.chat_right_click_menu,
|
menu_model=self.chat_right_click_menu,
|
||||||
has_arrow=False,
|
has_arrow=False,
|
||||||
halign=1,
|
halign=1,
|
||||||
|
height_request=155
|
||||||
)
|
)
|
||||||
self.selected_chat_row = chat_row
|
self.selected_chat_row = chat_row
|
||||||
position = Gdk.Rectangle()
|
position = Gdk.Rectangle()
|
||||||
@@ -1372,17 +1530,17 @@ Generate a title following these rules:
|
|||||||
|
|
||||||
def connect_remote(self, url, bearer_token):
|
def connect_remote(self, url, bearer_token):
|
||||||
logger.debug(f"Connecting to remote: {url}")
|
logger.debug(f"Connecting to remote: {url}")
|
||||||
connection_handler.url = url
|
connection_handler.URL = url
|
||||||
connection_handler.bearer_token = bearer_token
|
connection_handler.BEARER_TOKEN = bearer_token
|
||||||
self.remote_url = connection_handler.url
|
self.remote_url = connection_handler.URL
|
||||||
self.remote_connection_entry.set_text(self.remote_url)
|
self.remote_connection_entry.set_text(self.remote_url)
|
||||||
if self.verify_connection() == False: self.connection_error()
|
if self.verify_connection() == False: self.connection_error()
|
||||||
|
|
||||||
def connect_local(self):
|
def connect_local(self):
|
||||||
logger.debug("Connecting to Alpaca's Ollama instance")
|
logger.debug("Connecting to Alpaca's Ollama instance")
|
||||||
self.run_remote = False
|
self.run_remote = False
|
||||||
connection_handler.bearer_token = None
|
connection_handler.BEARER_TOKEN = None
|
||||||
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()
|
local_instance.start()
|
||||||
if self.verify_connection() == False:
|
if self.verify_connection() == False:
|
||||||
self.connection_error()
|
self.connection_error()
|
||||||
@@ -1392,7 +1550,7 @@ Generate a title following these rules:
|
|||||||
def connection_error(self):
|
def connection_error(self):
|
||||||
logger.error("Connection error")
|
logger.error("Connection error")
|
||||||
if self.run_remote:
|
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:
|
else:
|
||||||
local_instance.reset()
|
local_instance.reset()
|
||||||
self.show_toast(_("There was an error with the local Ollama instance, so it has been reset"), self.main_overlay)
|
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:
|
if new_value != self.run_remote:
|
||||||
self.run_remote = new_value
|
self.run_remote = new_value
|
||||||
if self.run_remote:
|
if self.run_remote:
|
||||||
connection_handler.bearer_token = self.remote_bearer_token
|
connection_handler.BEARER_TOKEN = self.remote_bearer_token
|
||||||
connection_handler.url = self.remote_url
|
connection_handler.URL = self.remote_url
|
||||||
if self.verify_connection() == False:
|
if self.verify_connection() == False:
|
||||||
self.connection_error()
|
self.connection_error()
|
||||||
else:
|
else:
|
||||||
local_instance.stop()
|
local_instance.stop()
|
||||||
else:
|
else:
|
||||||
connection_handler.bearer_token = None
|
connection_handler.BEARER_TOKEN = None
|
||||||
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()
|
local_instance.start()
|
||||||
if self.verify_connection() == False:
|
if self.verify_connection() == False:
|
||||||
self.connection_error()
|
self.connection_error()
|
||||||
@@ -1428,7 +1586,7 @@ Generate a title following these rules:
|
|||||||
|
|
||||||
with tempfile.TemporaryDirectory() as temp_dir:
|
with tempfile.TemporaryDirectory() as temp_dir:
|
||||||
json_path = os.path.join(temp_dir, "data.json")
|
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)
|
json_file.write(json_data)
|
||||||
|
|
||||||
tar_path = os.path.join(temp_dir, chat_name)
|
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):
|
if os.path.exists(directory) and os.path.isdir(directory):
|
||||||
tar.add(directory, arcname=os.path.basename(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()
|
tar_content = tar.read()
|
||||||
|
|
||||||
file.replace_contents_async(
|
file.replace_contents_async(
|
||||||
@@ -1466,7 +1624,7 @@ Generate a title following these rules:
|
|||||||
with tempfile.TemporaryDirectory() as temp_dir:
|
with tempfile.TemporaryDirectory() as temp_dir:
|
||||||
tar_filename = os.path.join(temp_dir, "imported_chat.tar")
|
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())
|
tar_file.write(tar_content.get_data())
|
||||||
|
|
||||||
with tarfile.open(tar_filename, "r") as tar:
|
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():
|
for chat_name, chat_content in data.items():
|
||||||
new_chat_name = self.generate_numbered_name(chat_name, list(self.chats['chats'].keys()))
|
new_chat_name = self.generate_numbered_name(chat_name, list(self.chats['chats'].keys()))
|
||||||
self.chats['chats'][new_chat_name] = chat_content
|
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)
|
src_path = os.path.join(temp_dir, chat_name)
|
||||||
if os.path.exists(src_path) and os.path.isdir(src_path):
|
if os.path.exists(src_path) and os.path.isdir(src_path):
|
||||||
dest_path = os.path.join(self.data_dir, "chats", new_chat_name)
|
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_row = self.selected_chat_row
|
||||||
chat_name = chat_row.get_child().get_name()
|
chat_name = chat_row.get_child().get_name()
|
||||||
action_name = action.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)
|
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'):
|
elif action_name in ('rename_chat', 'rename_current_chat'):
|
||||||
dialogs.rename_chat(self, chat_name, chat_row.get_child())
|
dialogs.rename_chat(self, chat_name, chat_row.get_child())
|
||||||
elif action_name in ('export_chat', 'export_current_chat'):
|
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')
|
self.attach_file(os.path.join(self.cache_dir, 'tmp/images/{}'.format(image_name)), 'image')
|
||||||
else:
|
else:
|
||||||
self.show_toast(_("Image recognition is only available on specific models"), self.main_overlay)
|
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):
|
def on_clipboard_paste(self, textview):
|
||||||
logger.debug("Pasting from clipboard")
|
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_text_async(None, self.cb_text_received)
|
||||||
clipboard.read_texture_async(None, self.cb_image_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):
|
def handle_enter_key(self):
|
||||||
self.send_message()
|
if not self.bot_message:
|
||||||
|
self.send_message()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
@@ -1663,9 +1807,9 @@ Generate a title following these rules:
|
|||||||
self.available_models = json.load(f)
|
self.available_models = json.load(f)
|
||||||
if not os.path.exists(os.path.join(self.data_dir, "chats")):
|
if not os.path.exists(os.path.join(self.data_dir, "chats")):
|
||||||
os.makedirs(os.path.join(self.data_dir, "chats"))
|
os.makedirs(os.path.join(self.data_dir, "chats"))
|
||||||
key_controller = Gtk.EventControllerKey.new()
|
enter_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)
|
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(key_controller)
|
self.message_text_view.add_controller(enter_key_controller)
|
||||||
self.set_help_overlay(self.shortcut_window)
|
self.set_help_overlay(self.shortcut_window)
|
||||||
self.get_application().set_accels_for_action("win.show-help-overlay", ['<primary>slash'])
|
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'])
|
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_existing', lambda *_: dialogs.create_model_from_existing(self))
|
||||||
self.get_application().create_action('create_model_from_file', lambda *_: dialogs.create_model_from_file(self))
|
self.get_application().create_action('create_model_from_file', lambda *_: dialogs.create_model_from_file(self))
|
||||||
self.get_application().create_action('create_model_from_name', lambda *_: dialogs.create_model_from_name(self))
|
self.get_application().create_action('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_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_chat', self.chat_actions)
|
||||||
self.get_application().create_action('rename_current_chat', self.current_chat_actions)
|
self.get_application().create_action('rename_current_chat', self.current_chat_actions)
|
||||||
self.get_application().create_action('export_chat', self.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('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('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.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.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.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.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_entry.connect("entry-activated", lambda entry : entry.set_css_classes([]))
|
||||||
self.remote_connection_switch.connect("notify", lambda pspec, user_data : self.connection_switched())
|
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.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")):
|
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:
|
with open(os.path.join(self.config_dir, "server.json"), "r", encoding="utf-8") as f:
|
||||||
data = json.load(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_connection_entry.set_text(self.remote_url)
|
||||||
self.remote_bearer_token_entry.set_text(self.remote_bearer_token)
|
self.remote_bearer_token_entry.set_text(self.remote_bearer_token)
|
||||||
if self.run_remote:
|
if self.run_remote:
|
||||||
connection_handler.bearer_token = self.remote_bearer_token
|
connection_handler.BEARER_TOKEN = self.remote_bearer_token
|
||||||
connection_handler.url = self.remote_url
|
connection_handler.URL = self.remote_url
|
||||||
self.remote_connection_switch.set_active(True)
|
self.remote_connection_switch.set_active(True)
|
||||||
else:
|
else:
|
||||||
connection_handler.bearer_token = None
|
connection_handler.BEARER_TOKEN = None
|
||||||
self.remote_connection_switch.set_active(False)
|
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()
|
local_instance.start()
|
||||||
else:
|
else:
|
||||||
local_instance.start()
|
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)
|
self.welcome_dialog.present(self)
|
||||||
if self.verify_connection() is False:
|
if self.verify_connection() is False:
|
||||||
self.connection_error()
|
self.connection_error()
|
||||||
|
|||||||
308
src/window.ui
308
src/window.ui
@@ -12,13 +12,14 @@
|
|||||||
<property name="title">Alpaca</property>
|
<property name="title">Alpaca</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="AdwBreakpoint">
|
<object class="AdwBreakpoint">
|
||||||
<condition>max-width: 800sp</condition>
|
<condition>max-width: 690sp</condition>
|
||||||
<setter object="split_view_overlay" property="collapsed">true</setter>
|
<setter object="split_view_overlay" property="collapsed">true</setter>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<property name="content">
|
<property name="content">
|
||||||
<object class="AdwOverlaySplitView" id="split_view_overlay">
|
<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="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">
|
<property name="sidebar">
|
||||||
<object class="AdwToolbarView">
|
<object class="AdwToolbarView">
|
||||||
<child type="top">
|
<child type="top">
|
||||||
@@ -75,30 +76,84 @@
|
|||||||
<property name="orientation">0</property>
|
<property name="orientation">0</property>
|
||||||
<property name="spacing">12</property>
|
<property name="spacing">12</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkDropDown" id="model_drop_down">
|
<object class="GtkMenuButton" id="model_selector_button">
|
||||||
<signal name="notify" handler="verify_if_image_can_be_used"/>
|
<property name="tooltip-text" translatable="yes">Select Model</property>
|
||||||
<property name="width-request">260</property>
|
<property name="child">
|
||||||
<property name="enable-search">true</property>
|
<object class="GtkBox">
|
||||||
<property name="tooltip-text">Select Model</property>
|
<property name="spacing">10</property>
|
||||||
<property name="model">
|
<child>
|
||||||
<object class="GtkStringList" id="model_string_list">
|
<object class="GtkLabel">
|
||||||
<items>
|
<property name="label" translatable="yes">Select a Model</property>
|
||||||
</items>
|
<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>
|
</object>
|
||||||
</property>
|
</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</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>
|
</object>
|
||||||
</property>
|
</property>
|
||||||
<child type="end">
|
<child type="end">
|
||||||
@@ -215,6 +270,10 @@
|
|||||||
<property name="top-margin">10</property>
|
<property name="top-margin">10</property>
|
||||||
<property name="bottom-margin">10</property>
|
<property name="bottom-margin">10</property>
|
||||||
<property name="hexpand">true</property>
|
<property name="hexpand">true</property>
|
||||||
|
<property name="input-hints">spellcheck</property>
|
||||||
|
<accessibility>
|
||||||
|
<property name="label" translatable="yes">Message text box</property>
|
||||||
|
</accessibility>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
@@ -442,6 +501,9 @@
|
|||||||
</object>
|
</object>
|
||||||
|
|
||||||
<object class="AdwDialog" id="manage_models_dialog">
|
<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="can-close">true</property>
|
||||||
<property name="width-request">400</property>
|
<property name="width-request">400</property>
|
||||||
<property name="height-request">600</property>
|
<property name="height-request">600</property>
|
||||||
@@ -476,12 +538,18 @@
|
|||||||
</child>
|
</child>
|
||||||
<child type="top">
|
<child type="top">
|
||||||
<object class="GtkSearchBar" id="model_searchbar">
|
<object class="GtkSearchBar" id="model_searchbar">
|
||||||
|
<accessibility>
|
||||||
|
<property name="label" translatable="yes">Model search bar</property>
|
||||||
|
</accessibility>
|
||||||
<property name="key-capture-widget">AlpacaWindow</property>
|
<property name="key-capture-widget">AlpacaWindow</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkSearchEntry" id="searchentry">
|
<object class="GtkSearchEntry" id="searchentry">
|
||||||
<signal name="search-changed" handler="model_search_changed"/>
|
<signal name="search-changed" handler="model_search_changed"/>
|
||||||
<property name="search-delay">100</property>
|
<property name="search-delay">100</property>
|
||||||
<property name="placeholder-text" translatable="yes">Search models</property>
|
<property name="placeholder-text" translatable="yes">Search models</property>
|
||||||
|
<accessibility>
|
||||||
|
<property name="label" translatable="yes">Search models</property>
|
||||||
|
</accessibility>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
@@ -517,7 +585,7 @@
|
|||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkListBox" id="available_model_list_box">
|
<object class="GtkListBox" id="available_model_list_box">
|
||||||
<property name="selection-mode">single</property>
|
<property name="selection-mode">none</property>
|
||||||
<style>
|
<style>
|
||||||
<class name="boxed-list"/>
|
<class name="boxed-list"/>
|
||||||
</style>
|
</style>
|
||||||
@@ -580,7 +648,7 @@
|
|||||||
<child>
|
<child>
|
||||||
<object class="GtkListBox" id="model_tag_list_box">
|
<object class="GtkListBox" id="model_tag_list_box">
|
||||||
<property name="valign">1</property>
|
<property name="valign">1</property>
|
||||||
<property name="selection-mode">single</property>
|
<property name="selection-mode">none</property>
|
||||||
<style>
|
<style>
|
||||||
<class name="boxed-list"/>
|
<class name="boxed-list"/>
|
||||||
</style>
|
</style>
|
||||||
@@ -738,9 +806,12 @@
|
|||||||
</object>
|
</object>
|
||||||
|
|
||||||
<object class="AdwDialog" id="file_preview_dialog">
|
<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="can-close">true</property>
|
||||||
<property name="width-request">450</property>
|
<property name="width-request">400</property>
|
||||||
<property name="height-request">450</property>
|
<property name="height-request">600</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="AdwToolbarView">
|
<object class="AdwToolbarView">
|
||||||
<child type="top">
|
<child type="top">
|
||||||
@@ -805,8 +876,8 @@
|
|||||||
|
|
||||||
<object class="AdwDialog" id="welcome_dialog">
|
<object class="AdwDialog" id="welcome_dialog">
|
||||||
<property name="can-close">false</property>
|
<property name="can-close">false</property>
|
||||||
<property name="width-request">450</property>
|
<property name="width-request">400</property>
|
||||||
<property name="height-request">450</property>
|
<property name="height-request">600</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="AdwToolbarView">
|
<object class="AdwToolbarView">
|
||||||
<child type="bottom">
|
<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>
|
<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>
|
</object>
|
||||||
</child>
|
</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>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
@@ -1054,34 +990,52 @@ By downloading any model you accept their license agreement available on the mod
|
|||||||
</section>
|
</section>
|
||||||
</menu>
|
</menu>
|
||||||
<menu id="secondary_menu">
|
<menu id="secondary_menu">
|
||||||
<item>
|
<section>
|
||||||
<attribute name="label" translatable="yes">Rename Chat</attribute>
|
<item>
|
||||||
<attribute name="action">app.rename_current_chat</attribute>
|
<attribute name="label" translatable="yes">Rename Chat</attribute>
|
||||||
</item>
|
<attribute name="action">app.rename_current_chat</attribute>
|
||||||
<item>
|
</item>
|
||||||
<attribute name="label" translatable="yes">Export Chat</attribute>
|
<item>
|
||||||
<attribute name="action">app.export_current_chat</attribute>
|
<attribute name="label" translatable="yes">Duplicate Chat</attribute>
|
||||||
</item>
|
<attribute name="action">app.duplicate_current_chat</attribute>
|
||||||
<item>
|
</item>
|
||||||
<attribute name="label" translatable="yes">Clear Chat</attribute>
|
<item>
|
||||||
<attribute name="action">app.clear</attribute>
|
<attribute name="label" translatable="yes">Export Chat</attribute>
|
||||||
</item>
|
<attribute name="action">app.export_current_chat</attribute>
|
||||||
</menu>
|
</item>
|
||||||
<menu id="chat_right_click_menu">
|
<item>
|
||||||
|
<attribute name="label" translatable="yes">Clear Chat</attribute>
|
||||||
|
<attribute name="action">app.clear</attribute>
|
||||||
|
</item>
|
||||||
|
</section>
|
||||||
<section>
|
<section>
|
||||||
<item>
|
<item>
|
||||||
<attribute name="label" translatable="yes">Delete Chat</attribute>
|
<attribute name="label" translatable="yes">Delete Chat</attribute>
|
||||||
<attribute name="action">app.delete_chat</attribute>
|
<attribute name="action">app.delete_current_chat</attribute>
|
||||||
</item>
|
</item>
|
||||||
|
</section>
|
||||||
|
</menu>
|
||||||
|
<menu id="chat_right_click_menu">
|
||||||
|
<section>
|
||||||
<item>
|
<item>
|
||||||
<attribute name="label" translatable="yes">Rename Chat</attribute>
|
<attribute name="label" translatable="yes">Rename Chat</attribute>
|
||||||
<attribute name="action">app.rename_chat</attribute>
|
<attribute name="action">app.rename_chat</attribute>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<attribute name="label" translatable="yes">Duplicate Chat</attribute>
|
||||||
|
<attribute name="action">app.duplicate_chat</attribute>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<attribute name="label" translatable="yes">Export Chat</attribute>
|
<attribute name="label" translatable="yes">Export Chat</attribute>
|
||||||
<attribute name="action">app.export_chat</attribute>
|
<attribute name="action">app.export_chat</attribute>
|
||||||
</item>
|
</item>
|
||||||
</section>
|
</section>
|
||||||
|
<section>
|
||||||
|
<item>
|
||||||
|
<attribute name="label" translatable="yes">Delete Chat</attribute>
|
||||||
|
<attribute name="action">app.delete_chat</attribute>
|
||||||
|
</item>
|
||||||
|
</section>
|
||||||
</menu>
|
</menu>
|
||||||
<menu id="create_model_menu">
|
<menu id="create_model_menu">
|
||||||
<section>
|
<section>
|
||||||
|
|||||||
@@ -15,4 +15,6 @@ msgmerge --no-fuzzy-matching -U po/nb_NO.po po/alpaca.pot
|
|||||||
echo "Updating Bengali"
|
echo "Updating Bengali"
|
||||||
msgmerge --no-fuzzy-matching -U po/bn.po po/alpaca.pot
|
msgmerge --no-fuzzy-matching -U po/bn.po po/alpaca.pot
|
||||||
echo "Updating Simplified Chinese"
|
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