90 Commits
1.0.6 ... 1.1.1

Author SHA1 Message Date
Jeffry Samuel
a7b6e6bbce Update flatpak-builder.yml 2024-08-12 23:15:53 -06:00
aritra saha
801c10fb77 Update hi.po (#240) 2024-08-12 22:58:04 -06:00
aritra saha
50520b8474 Update bn.po (#239) 2024-08-12 22:53:43 -06:00
jeffser
b66e2102d3 Spanish update 2024-08-12 22:21:32 -06:00
jeffser
8c0f1fd4d5 Updated languages 2024-08-12 22:17:35 -06:00
jeffser
8b851d1b56 Preparing for 1.1.1 2024-08-12 22:16:47 -06:00
jeffser
f36d6e1b29 Removed bugged imported chats 2024-08-12 22:12:09 -06:00
jeffser
eecac162ef Update translations 2024-08-12 22:03:47 -06:00
jeffser
82e7a3a9e1 Better caption names on dropdown 2024-08-12 18:57:17 -06:00
jeffser
f0505a0242 Only enable youtube caption search when there's more than 10 captions 2024-08-12 18:55:06 -06:00
jeffser
11dd13b430 Added ollama debbugging messages to about dialog 2024-08-11 22:30:56 -06:00
jeffser
b8fe222052 Added duplicate chat option 2024-08-11 22:11:17 -06:00
jeffser
47d19a58aa Give message focus when editing it 2024-08-11 21:50:50 -06:00
jeffser
fd67afbf33 Fixed message editor 2024-08-11 21:48:31 -06:00
jeffser
d06e08a64e Fix message regeneration 2024-08-11 21:36:31 -06:00
jeffser
77b08d9e52 Added loading spinner to regenerating message 2024-08-11 21:15:26 -06:00
jeffser
9451bf88d0 Removed french language set 2024-08-11 16:02:30 -06:00
jeffser
82bb50d663 Merge branch 'main' of github.com-jeffser:Jeffser/Alpaca 2024-08-11 16:01:14 -06:00
jeffser
edc3053774 Focus message entry when creating a new chat 2024-08-11 15:59:39 -06:00
Louis Chauvet-Villaret
1320ddb7d4 Hola Jeffry, traduje la aplicacion, buen dia (#236) 2024-08-11 15:37:51 -06:00
jeffser
d95f06a230 Make buttons visible when stopping message 2024-08-11 15:08:26 -06:00
jeffser
938ace91c1 Fixed import chat 2024-08-11 14:55:18 -06:00
jeffser
175cfad81c Welcome screen appears when clearing chat 2024-08-11 14:49:12 -06:00
jeffser
2f399dbb64 Removed bold from manage button label 2024-08-11 14:44:19 -06:00
jeffser
27558b85af Added top margin to model selector 2024-08-11 14:23:30 -06:00
jeffser
bcc1f3fa65 Changed 'Open Model Manager' to pill button on welcome screen 2024-08-11 14:19:00 -06:00
jeffser
fd92a86c5e CTRL+W and CTRL+Q stops local instance before closing the app 2024-08-11 13:23:34 -06:00
jeffser
3b95d369b8 Added Azerbaijani template 2024-08-11 01:45:57 -06:00
Jeffry Samuel
a12920d801 Update SECURITY.md 2024-08-11 00:47:58 -06:00
Jeffry Samuel
2dd63df533 Create SECURITY.md 2024-08-11 00:44:49 -06:00
Jeffry Samuel
cea1aa5028 Update CODE_OF_CONDUCT.md 2024-08-11 00:40:27 -06:00
Jeffry Samuel
54b96d4e3a Update CODE_OF_CONDUCT.md 2024-08-11 00:40:04 -06:00
Jeffry Samuel
a470136476 Create CODE_OF_CONDUCT.md 2024-08-11 00:37:44 -06:00
Jeffry Samuel
7d35cb08dd Create CONTRIBUTING.md 2024-08-11 00:32:55 -06:00
aritra saha
1f03f1032e Update bn.po and hipo (#223)
* Update bn.po

* Update hi.po
2024-08-11 00:14:48 -06:00
jeffser
9e2b55a249 Spanish update 2024-08-10 21:32:19 -06:00
jeffser
0fbb94cd72 Updated languages again 2024-08-10 21:32:10 -06:00
jeffser
004b3f8574 Preparing for 1.1.0 2024-08-10 21:26:33 -06:00
jeffser
7d1931dd17 Updated languages 2024-08-10 21:18:22 -06:00
jeffser
8b7f41afa7 Fixed codeblocks spacing 2024-08-10 21:10:23 -06:00
jeffser
4bc0832865 Better padding for focus border on messages 2024-08-10 21:03:19 -06:00
jeffser
a66c6d5f40 Fixed nasty clear chat glitch 2024-08-10 20:37:51 -06:00
jeffser
33b7cae24d Show date on stopped messages 2024-08-10 20:31:03 -06:00
jeffser
47f5c88ef2 Don't regenerate title when there's a custom chat title 2024-08-10 20:22:39 -06:00
jeffser
ffe382aee2 Added default buttons to dialogs 2024-08-10 20:14:51 -06:00
jeffser
919f71ee78 Fixed regenerate button 2024-08-10 20:05:41 -06:00
jeffser
404d4476ae Fixed import export 2024-08-10 19:27:03 -06:00
jeffser
f2b243cd5f Changed python 2 to python from codeblocks 2024-08-10 19:23:11 -06:00
Louis Chauvet-Villaret
c2fae41355 Complete french translation (#210)
Model description translated with ChatGPT, and it's work really well
2024-08-08 14:13:54 -06:00
jeffser
8fda2cde9e Fixed return + shift should make a new line 2024-08-08 10:56:45 -06:00
jeffser
930380cdce Fixed Hindi 2024-08-08 10:56:22 -06:00
jeffser
5b788ffe15 Missed a comma 2024-08-07 23:58:58 -06:00
jeffser
521c2bdde5 Fixed syntax 2024-08-07 23:57:23 -06:00
aritra saha
eee73b1218 some update and fix for welcome screen. (#206)
* Update bn.po

* Update hi.po

* Update window.py

* Update window.py

* Update window.py

* Update window.py

* Update window.py

I removed some and added commas

---------

Co-authored-by: Jeffry Samuel <69224322+Jeffser@users.noreply.github.com>
2024-08-07 23:54:34 -06:00
jeffser
87d6da26c9 Merge branch 'main' of github.com-jeffser:Jeffser/Alpaca 2024-08-07 21:37:39 -06:00
jeffser
2029cd5cd2 Added Hindi credits 2024-08-07 21:37:32 -06:00
Jeffry Samuel
36be752ee6 Update README.md 2024-08-07 21:34:48 -06:00
jeffser
5b3586789f Spanish Update 2024-08-07 21:32:57 -06:00
jeffser
6ce670e643 Updated translations 2024-08-07 21:27:08 -06:00
jeffser
dd70e8139c Give textview focus at launch 2024-08-07 21:21:26 -06:00
jeffser
3ac0936d1a Prevent enter key send when a response is being received 2024-08-07 21:20:03 -06:00
jeffser
1477bacf6a Removed 'Featured models' from welcome dialog, added 'Open Model Manager' button in welcome screen if the user doesn't have models 2024-08-07 21:02:29 -06:00
jeffser
d339a18901 Fixed toast message 2024-08-07 20:43:23 -06:00
jeffser
f9460416d9 Prevent regenerating message whilst receiving a response 2024-08-07 20:41:49 -06:00
jeffser
a9112cf3da Added welcome screen 2024-08-07 20:39:46 -06:00
jeffser
c873b49700 Merge branch 'main' of github.com-jeffser:Jeffser/Alpaca 2024-08-07 19:41:58 -06:00
jeffser
3c553e37d8 Fixed pull by name feature 2024-08-07 19:41:34 -06:00
aritra saha
0c47fbb1f7 update hi (#200)
* Update hi.po

* Update hi.po

* Update LINGUAS

* Update update_translation_files.sh

* Update hi.po

* Update hi.po

* Update hi.po
2024-08-07 19:35:18 -06:00
jeffser
476138ef53 Changed oars rating 2024-08-06 23:54:15 -06:00
jeffser
385ca4f0fa Added focus indicator for message being edited 2024-08-06 17:12:46 -06:00
jeffser
46fd642789 Merge branch 'main' of github.com-jeffser:Jeffser/Alpaca 2024-08-06 17:08:56 -06:00
jeffser
e48249c7c9 Focus indicators on messages 2024-08-06 17:08:50 -06:00
aritra saha
9e8535e97e Update bn.po (#198) 2024-08-06 14:42:40 -06:00
jeffser
a794c63a5a Fixed url 2024-08-06 13:49:22 -06:00
ProjectMoon
f3610a46a2 Fix bearer token use for remote ollama APIs. (#197) 2024-08-06 13:48:05 -06:00
aritra saha
20fd2cf6e3 hi updTe (#195)
* Update bn.po

* Update hi.po

* Update hi.po
2024-08-06 13:40:24 -06:00
jeffser
7bf345d09d Updated language files 2024-08-06 13:39:54 -06:00
jeffser
17e9560449 Fixed (none) in model selector problem 2024-08-06 13:15:15 -06:00
jeffser
c02e6a565e New model selector design + moved around the delete chat option on menus 2024-08-06 12:59:41 -06:00
jeffser
7fbc9b9bde Merge branch 'main' of github.com-jeffser:Jeffser/Alpaca 2024-08-05 23:12:29 -06:00
jeffser
416e97d488 Remade model selector 2024-08-05 23:12:22 -06:00
aritra saha
753060d9f3 another small fix (#194)
* Update bn.po

* Update hi.po
2024-08-05 17:52:38 -06:00
jeffser
972c53000c Changed splitview collapsing behaviour 2024-08-05 17:46:01 -06:00
jeffser
2b948a49a0 Made model manager navigatable by keyboard 2024-08-05 16:45:26 -06:00
jeffser
7999548738 Added fixes for accessibility / screen reader fixes (tested on Orca) 2024-08-05 16:07:49 -06:00
jeffser
d4d13b793f Fixed width of dialogs 2024-08-05 13:59:11 -06:00
jeffser
210b6f0d89 Removed search from model dropdown 2024-08-05 13:51:31 -06:00
jeffser
7f5894b274 Added delete chat option in secondary menu 2024-08-05 13:50:42 -06:00
jeffser
2dc24ab945 Made manage models dialog appear faster 2024-08-05 13:45:18 -06:00
jeffser
2f153c9974 Added ctrl+q shortcut to close app 2024-08-05 13:45:02 -06:00
27 changed files with 9674 additions and 4930 deletions

View File

@@ -13,6 +13,6 @@ jobs:
- uses: actions/checkout@v4
- uses: flatpak/flatpak-github-actions/flatpak-builder@v6
with:
bundle: Alpaca.flatpak
bundle: com.jeffser.Alpaca.flatpak
manifest-path: com.jeffser.Alpaca.json
cache-key: flatpak-builder-${{ github.sha }}

4
CODE_OF_CONDUCT.md Normal file
View 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
View 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.

View File

@@ -46,6 +46,7 @@ Language | Contributors
🇳🇴 Norwegian | [CounterFlow64](https://github.com/CounterFlow64)
🇮🇳 Bengali | [Aritra Saha](https://github.com/olumolu)
🇨🇳 Simplified Chinese | [Yuehao Sui](https://github.com/8ar10der) , [Aleksana](https://github.com/Aleksanaa)
🇮🇳 Hindi | [Aritra Saha](https://github.com/olumolu)
---

12
SECURITY.md Normal file
View 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)

View File

@@ -70,9 +70,7 @@
<caption>Multiple models being downloaded</caption>
</screenshot>
</screenshots>
<content_rating type="oars-1.1">
<content_attribute id="money-purchasing">mild</content_attribute>
</content_rating>
<content_rating type="oars-1.1" />
<url type="bugtracker">https://github.com/Jeffser/Alpaca/issues</url>
<url type="homepage">https://jeffser.com/alpaca/</url>
<url type="donation">https://github.com/sponsors/Jeffser</url>
@@ -80,6 +78,59 @@
<url type="contribute">https://github.com/Jeffser/Alpaca/discussions/154</url>
<url type="vcs-browser">https://github.com/Jeffser/Alpaca</url>
<releases>
<release version="1.1.1" date="2024-08-12">
<url type="details">https://github.com/Jeffser/Alpaca/releases/tag/1.1.1</url>
<description>
<p>New</p>
<ul>
<li>New duplicate chat option</li>
<li>Changed model selector appearance</li>
<li>Message entry is focused on launch and chat change</li>
<li>Message is focused when it's being edited</li>
<li>Added loading spinner when regenerating a message</li>
<li>Added Ollama debugging to 'About Alpaca' dialog</li>
<li>Changed YouTube transcription dialog appearance and behavior</li>
</ul>
<p>Fixes</p>
<ul>
<li>CTRL+W and CTRL+Q stops local instance before closing the app</li>
<li>Changed appearance of 'Open Model Manager' button on welcome screen</li>
<li>Fixed message generation not working consistently</li>
<li>Fixed message edition not working consistently</li>
</ul>
</description>
</release>
<release version="1.1.0" date="2024-08-10">
<url type="details">https://github.com/Jeffser/Alpaca/releases/tag/1.1.0</url>
<description>
<p>New</p>
<ul>
<li>Model manager opens faster</li>
<li>Delete chat option in secondary menu</li>
<li>New model selector popup</li>
<li>Standard shortcuts</li>
<li>Model manager is navigable with keyboard</li>
<li>Changed sidebar collapsing behavior</li>
<li>Focus indicators on messages</li>
<li>Welcome screen</li>
<li>Give message entry focus at launch</li>
<li>Generally better code</li>
</ul>
<p>Fixes</p>
<ul>
<li>Better width for dialogs</li>
<li>Better compatibility with screen readers</li>
<li>Fixed message regenerator</li>
<li>Removed 'Featured models' from welcome dialog</li>
<li>Added default buttons to dialogs</li>
<li>Fixed import / export of chats</li>
<li>Changed Python2 title to Python on code blocks</li>
<li>Prevent regeneration of title when the user changed it to a custom title</li>
<li>Show date on stopped messages</li>
<li>Fix clear chat error</li>
</ul>
</description>
</release>
<release version="1.0.6" date="2024-08-04">
<url type="details">https://github.com/Jeffser/Alpaca/releases/tag/1.0.6</url>
<description>

View File

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

View File

@@ -4,4 +4,5 @@ pt_BR
fr
nb_NO
bn
zh_CN
zh_CN
hi

File diff suppressed because it is too large Load Diff

2306
po/az.po Normal file

File diff suppressed because it is too large Load Diff

1086
po/bn.po

File diff suppressed because it is too large Load Diff

1137
po/es.po

File diff suppressed because it is too large Load Diff

1857
po/fr.po

File diff suppressed because it is too large Load Diff

2009
po/hi.po

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

1045
po/ru.po

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -28,6 +28,7 @@ def clear_chat(self):
dialog.add_response("cancel", _("Cancel"))
dialog.add_response("clear", _("Clear"))
dialog.set_response_appearance("clear", Adw.ResponseAppearance.DESTRUCTIVE)
dialog.set_default_response("clear")
dialog.choose(
parent = self,
cancellable = None,
@@ -49,6 +50,7 @@ def delete_chat(self, chat_name):
dialog.add_response("cancel", _("Cancel"))
dialog.add_response("delete", _("Delete"))
dialog.set_response_appearance("delete", Adw.ResponseAppearance.DESTRUCTIVE)
dialog.set_default_response("delete")
dialog.choose(
parent = self,
cancellable = None,
@@ -74,10 +76,10 @@ def rename_chat(self, chat_name, label_element):
extra_child=entry,
close_response="cancel"
)
entry.connect("activate", lambda dialog, old_chat_name=chat_name, entry=entry, label_element=label_element: rename_chat_response(self, dialog, None, old_chat_name, entry, label_element))
dialog.add_response("cancel", _("Cancel"))
dialog.add_response("rename", _("Rename"))
dialog.set_response_appearance("rename", Adw.ResponseAppearance.SUGGESTED)
dialog.set_default_response("rename")
dialog.choose(
parent = self,
cancellable = None,
@@ -102,10 +104,10 @@ def new_chat(self):
extra_child=entry,
close_response="cancel"
)
entry.connect("activate", lambda dialog, entry: new_chat_response(self, dialog, None, entry))
dialog.add_response("cancel", _("Cancel"))
dialog.add_response("create", _("Create"))
dialog.set_response_appearance("create", Adw.ResponseAppearance.SUGGESTED)
dialog.set_default_response("create")
dialog.choose(
parent = self,
cancellable = None,
@@ -128,6 +130,7 @@ def stop_pull_model(self, model_name):
dialog.add_response("cancel", _("Cancel"))
dialog.add_response("stop", _("Stop"))
dialog.set_response_appearance("stop", Adw.ResponseAppearance.DESTRUCTIVE)
dialog.set_default_response("stop")
dialog.choose(
parent = self.manage_models_dialog,
cancellable = None,
@@ -149,6 +152,7 @@ def delete_model(self, model_name):
dialog.add_response("cancel", _("Cancel"))
dialog.add_response("delete", _("Delete"))
dialog.set_response_appearance("delete", Adw.ResponseAppearance.DESTRUCTIVE)
dialog.set_default_response("delete")
dialog.choose(
parent = self.manage_models_dialog,
cancellable = None,
@@ -171,6 +175,7 @@ def remove_attached_file(self, name):
dialog.add_response("cancel", _("Cancel"))
dialog.add_response("remove", _("Remove"))
dialog.set_response_appearance("remove", Adw.ResponseAppearance.DESTRUCTIVE)
dialog.set_default_response("remove")
dialog.choose(
parent = self,
cancellable = None,
@@ -210,11 +215,11 @@ def reconnect_remote(self, current_url, current_bearer_token):
body=_("The remote instance has disconnected"),
extra_child=container
)
#entry.connect("activate", lambda entry, dialog: reconnect_remote_response(self, dialog, None, entry))
dialog.add_response("close", _("Close Alpaca"))
dialog.add_response("local", _("Use local instance"))
dialog.add_response("remote", _("Connect"))
dialog.set_response_appearance("remote", Adw.ResponseAppearance.SUGGESTED)
dialog.set_default_response("remote")
dialog.choose(
parent = self,
cancellable = None,
@@ -243,6 +248,7 @@ def create_model_from_existing(self):
dialog.add_response("cancel", _("Cancel"))
dialog.add_response("accept", _("Accept"))
dialog.set_response_appearance("accept", Adw.ResponseAppearance.SUGGESTED)
dialog.set_default_response("accept")
dialog.choose(
parent = self,
cancellable = None,
@@ -271,7 +277,7 @@ def create_model_from_name_response(self, dialog, task, entry):
def create_model_from_name(self):
entry = Gtk.Entry()
entry.get_delegate().connect("insert-text", self.check_alphanumeric)
entry.get_delegate().connect("insert-text", lambda *_ : self.check_alphanumeric(*_, ['-', '.', ':', '_', '/']))
dialog = Adw.AlertDialog(
heading=_("Pull Model"),
body=_("Input the name of the model in this format\nname:tag"),
@@ -280,6 +286,7 @@ def create_model_from_name(self):
dialog.add_response("cancel", _("Cancel"))
dialog.add_response("accept", _("Accept"))
dialog.set_response_appearance("accept", Adw.ResponseAppearance.SUGGESTED)
dialog.set_default_response("accept")
dialog.choose(
parent = self,
cancellable = None,
@@ -323,7 +330,7 @@ def youtube_caption_response(self, dialog, task, video_url, caption_drop_down):
yt = YouTube(video_url)
text = "{}\n{}\n{}\n\n".format(yt.title, yt.author, yt.watch_url)
selected_caption = caption_drop_down.get_selected_item().get_string()
for event in yt.captions[selected_caption.split(' | ')[1]].json_captions['events']:
for event in yt.captions[selected_caption.split('(')[1][:-1]].json_captions['events']:
text += "{}\n".format(event['segs'][0]['utf8'].replace('\n', '\\n'))
if not os.path.exists(os.path.join(self.cache_dir, 'tmp/youtube')):
os.makedirs(os.path.join(self.cache_dir, 'tmp/youtube'))
@@ -341,9 +348,9 @@ def youtube_caption(self, video_url):
return
caption_list = Gtk.StringList()
for caption in captions:
caption_list.append("{} | {}".format(caption.name, caption.code))
caption_list.append("{} ({})".format(caption.name.title(), caption.code))
caption_drop_down = Gtk.DropDown(
enable_search=True,
enable_search=len(captions) > 10,
model=caption_list
)
dialog = Adw.AlertDialog(
@@ -355,6 +362,7 @@ def youtube_caption(self, video_url):
dialog.add_response("cancel", _("Cancel"))
dialog.add_response("accept", _("Accept"))
dialog.set_response_appearance("accept", Adw.ResponseAppearance.SUGGESTED)
dialog.set_default_response("accept")
dialog.choose(
parent = self,
cancellable = None,
@@ -393,6 +401,7 @@ def attach_website(self, url):
dialog.add_response("cancel", _("Cancel"))
dialog.add_response("accept", _("Accept"))
dialog.set_response_appearance("accept", Adw.ResponseAppearance.SUGGESTED)
dialog.set_default_response("accept")
dialog.choose(
parent = self,
cancellable = None,

View File

@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 0 16 16" width="16px"><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

View File

@@ -3,6 +3,7 @@
Handles running, stopping and resetting the integrated Ollama instance
"""
import subprocess
import threading
import os
from time import sleep
from logging import getLogger
@@ -15,6 +16,14 @@ instance = None
port = 11435
overrides = {}
def log_output(pipe):
with open(os.path.join(data_dir, 'tmp.log'), 'a') as f:
with pipe:
for line in iter(pipe.readline, ''):
print(line, end='')
f.write(line)
f.flush()
def start():
if not os.path.isdir(os.path.join(cache_dir, 'tmp/ollama')):
os.mkdir(os.path.join(cache_dir, 'tmp/ollama'))
@@ -23,7 +32,9 @@ def start():
params["OLLAMA_HOST"] = f"127.0.0.1:{port}" # You can't change this directly sorry :3
params["HOME"] = data_dir
params["TMPDIR"] = os.path.join(cache_dir, 'tmp/ollama')
instance = subprocess.Popen(["ollama", "serve"], env={**os.environ, **params}, stderr=subprocess.PIPE, text=True)
instance = subprocess.Popen(["ollama", "serve"], env={**os.environ, **params}, stderr=subprocess.PIPE, stdout=subprocess.PIPE, text=True)
threading.Thread(target=log_output, args=(instance.stdout,)).start()
threading.Thread(target=log_output, args=(instance.stderr,)).start()
logger.info("Starting Alpaca's Ollama instance...")
logger.debug(params)
sleep(1)

View File

@@ -43,7 +43,8 @@ translators = [
'CounterFlow64 (Norwegian) https://github.com/CounterFlow64',
'Aritra Saha (Bengali) https://github.com/olumolu',
'Yuehao Sui (Simplified Chinese) https://github.com/8ar10der',
'Aleksana (Simplified Chinese) https://github.com/Aleksanaa'
'Aleksana (Simplified Chinese) https://github.com/Aleksanaa',
'Aritra Saha (Hindi) https://github.com/olumolu'
]
class AlpacaApplication(Adw.Application):
@@ -52,7 +53,7 @@ class AlpacaApplication(Adw.Application):
def __init__(self, version):
super().__init__(application_id='com.jeffser.Alpaca',
flags=Gio.ApplicationFlags.DEFAULT_FLAGS)
self.create_action('quit', lambda *_: self.quit(), ['<primary>w'])
self.create_action('quit', lambda *_: self.props.active_window.closing_app(None), ['<primary>w', '<primary>q'])
self.create_action('preferences', lambda *_: AlpacaWindow.show_preferences_dialog(self.props.active_window), ['<primary>comma'])
self.create_action('about', self.on_about_action)
self.version = version

View File

@@ -1,7 +1,3 @@
.message_input_scroll_window > * {
box-shadow: none;
border-width: 0;
}
.message_text_view, .modelfile_textview {
background-color: rgba(0,0,0,0);
}
@@ -12,3 +8,23 @@
border-radius: 5px;
padding: 5px;
}
.model_list_box {
padding: 0;
}
.manage_models_button {
padding: 6px 8px 6px 8px;
font-weight: 400;
}
.model_list_box > * {
margin: 0;
}
.user_message, .response_message {
padding: 12px;
border-radius: 10px;
}
.user_message:focus, .response_message:focus, .editing_message_textview:focus, .code_block:focus {
box-shadow: 0 0 1px 2px mix(@accent_color, @window_bg_color, 0.5);
}
.model_popover {
margin-top: 6px;
}

View File

@@ -19,7 +19,7 @@
"""
Handles the main window
"""
import json, threading, os, re, base64, sys, gettext, uuid, shutil, tarfile, tempfile, logging
import json, threading, os, re, base64, sys, gettext, uuid, shutil, tarfile, tempfile, logging, random
from io import BytesIO
from PIL import Image
from pypdf import PdfReader
@@ -64,6 +64,30 @@ class AlpacaWindow(Adw.ApplicationWindow):
pulling_models = {}
chats = {"chats": {_("New Chat"): {"messages": {}}}, "selected_chat": "New Chat", "order": []}
attachments = {}
possible_prompts = [
"What can you do?",
"Give me a pancake recipe",
"Why is the sky blue?",
"Can you tell me a joke?",
"Give me a healthy breakfast recipe",
"How to make a pizza",
"Can you write a poem?",
"Can you write a story?",
"What is GNU-Linux?",
"Which is the best Linux distro?",
"Why is Pluto not a planet?",
"What is a black-hole?",
"Tell me how to stay fit",
"Write a conversation between sun and Earth",
"Why is the grass green?",
"Write an Haïku about AI",
"What is the meaning of life?",
"Explain quantum physics in simple terms",
"Explain the theory of relativity",
"Explain how photosynthesis works",
"Recommend a film about nature",
"What is nostalgia?"
]
#Override elements
override_HSA_OVERRIDE_GFX_VERSION = Gtk.Template.Child()
@@ -107,8 +131,6 @@ class AlpacaWindow(Adw.ApplicationWindow):
file_filter_gguf = Gtk.Template.Child()
file_filter_attachments = Gtk.Template.Child()
attachment_button = Gtk.Template.Child()
model_drop_down = Gtk.Template.Child()
model_string_list = Gtk.Template.Child()
chat_right_click_menu = Gtk.Template.Child()
model_tag_list_box = Gtk.Template.Child()
navigation_view_manage_models = Gtk.Template.Child()
@@ -118,6 +140,10 @@ class AlpacaWindow(Adw.ApplicationWindow):
model_searchbar = Gtk.Template.Child()
no_results_page = Gtk.Template.Child()
model_link_button = Gtk.Template.Child()
model_list_box = Gtk.Template.Child()
model_popover = Gtk.Template.Child()
model_selector_button = Gtk.Template.Child()
chat_welcome_screen : Adw.StatusPage = None
manage_models_dialog = Gtk.Template.Child()
pulling_model_list_box = Gtk.Template.Child()
@@ -136,38 +162,29 @@ class AlpacaWindow(Adw.ApplicationWindow):
style_manager = Adw.StyleManager()
@Gtk.Template.Callback()
def verify_if_image_can_be_used(self, pspec=None, user_data=None):
logger.debug("Verifying if image can be used")
if self.model_drop_down.get_selected_item() == None:
return True
selected = self.convert_model_name(self.model_drop_down.get_selected_item().get_string(), 1).split(":")[0]
if selected in [key for key, value in self.available_models.items() if value["image"]]:
for name, content in self.attachments.items():
if content['type'] == 'image':
content['button'].set_css_classes(["flat"])
return True
for name, content in self.attachments.items():
if content['type'] == 'image':
content['button'].set_css_classes(["flat", "error"])
return False
@Gtk.Template.Callback()
def stop_message(self, button=None):
if self.loading_spinner:
self.chat_container.remove(self.loading_spinner)
self.loading_spinner.get_parent().remove(self.loading_spinner)
message_id = list(self.chats["chats"][self.chats["selected_chat"]]["messages"])[-1]
formated_date = GLib.markup_escape_text(self.generate_datetime_format(datetime.strptime(self.chats["chats"][self.chats["selected_chat"]]["messages"][message_id]["date"], '%Y/%m/%d %H:%M:%S')))
text = f"\n\n{self.convert_model_name(self.chats['chats'][self.chats['selected_chat']]['messages'][message_id]['model'], 0)}\n<small>{formated_date}</small>"
self.bot_message.insert_markup(self.bot_message.get_end_iter(), text, len(text.encode('utf-8')))
self.add_code_blocks()
self.bot_message_button_container.set_visible(True)
self.toggle_ui_sensitive(True)
self.switch_send_stop_button()
self.switch_send_stop_button(True)
self.bot_message = None
self.bot_message_box = None
self.bot_message_view = None
self.bot_message_button_container = None
self.save_history()
@Gtk.Template.Callback()
def send_message(self, button=None):
if self.editing_message:
self.editing_message["button_container"].set_visible(True)
self.editing_message["text_view"].set_css_classes(["flat"])
self.editing_message["text_view"].set_css_classes(["flat", "user_message"])
self.editing_message["text_view"].set_cursor_visible(False)
self.editing_message["text_view"].set_editable(False)
buffer = self.editing_message["text_view"].get_buffer()
@@ -178,8 +195,7 @@ class AlpacaWindow(Adw.ApplicationWindow):
self.editing_message = None
self.save_history()
self.show_toast(_("Message edited successfully"), self.main_overlay)
if self.bot_message or self.get_focus() not in (self.message_text_view, self.send_button):
if button and not button.get_visible():
return
if not self.message_text_view.get_buffer().get_text(self.message_text_view.get_buffer().get_start_iter(), self.message_text_view.get_buffer().get_end_iter(), False):
return
@@ -191,7 +207,7 @@ class AlpacaWindow(Adw.ApplicationWindow):
self.chats['order'].remove(self.chats['selected_chat'])
self.chats['order'].insert(0, self.chats['selected_chat'])
self.save_history()
current_model = self.convert_model_name(self.model_drop_down.get_selected_item().get_string(), 1)
current_model = self.get_current_model(1)
if current_model is None:
self.show_toast(_("Please select a model before chatting"), self.main_overlay)
return
@@ -232,7 +248,7 @@ class AlpacaWindow(Adw.ApplicationWindow):
"options": {"temperature": self.model_tweaks["temperature"], "seed": self.model_tweaks["seed"]},
"keep_alive": f"{self.model_tweaks['keep_alive']}m"
}
self.switch_send_stop_button()
self.switch_send_stop_button(False)
self.toggle_ui_sensitive(False)
#self.attachments[name] = {"path": file_path, "type": file_type, "content": content}
@@ -245,6 +261,9 @@ class AlpacaWindow(Adw.ApplicationWindow):
bot_id=self.generate_uuid()
self.show_message("", True, message_id=bot_id)
if self.chat_welcome_screen:
self.chat_container.remove(self.chat_welcome_screen)
thread = threading.Thread(target=self.run_message, args=(data['messages'], data['model'], bot_id))
thread.start()
if len(data['messages']) == 1:
@@ -286,13 +305,14 @@ class AlpacaWindow(Adw.ApplicationWindow):
if row and row.get_child().get_name() != self.chats["selected_chat"]:
self.chats["selected_chat"] = row.get_child().get_name()
self.load_history_into_chat()
if len(self.chats["chats"][self.chats["selected_chat"]]["messages"].keys()) > 0:
if len(self.chats["chats"][self.chats["selected_chat"]]["messages"]) > 0:
last_model_used = self.chats["chats"][self.chats["selected_chat"]]["messages"][list(self.chats["chats"][self.chats["selected_chat"]]["messages"].keys())[-1]]["model"]
last_model_used = self.convert_model_name(last_model_used, 0)
for i in range(self.model_string_list.get_n_items()):
if self.model_string_list.get_string(i) == last_model_used:
self.model_drop_down.set_selected(i)
for i, m in enumerate(self.local_models):
if m == last_model_used:
self.model_list_box.select_row(self.model_list_box.get_row_at_index(i))
break
else:
self.load_history_into_chat()
self.save_history()
@Gtk.Template.Callback()
@@ -303,7 +323,7 @@ class AlpacaWindow(Adw.ApplicationWindow):
self.remote_url = entry.get_text()
logger.debug(f"Changing remote url: {self.remote_url}")
if self.run_remote:
connection_handler.url = self.remote_url
connection_handler.URL = self.remote_url
if self.verify_connection() == False:
entry.set_css_classes(["error"])
self.show_toast(_("Failed to connect to server"), self.preferences_dialog)
@@ -314,23 +334,11 @@ class AlpacaWindow(Adw.ApplicationWindow):
self.save_server_config()
return
if self.remote_url and self.run_remote:
connection_handler.url = self.remote_url
connection_handler.URL = self.remote_url
if self.verify_connection() == False:
entry.set_css_classes(["error"])
self.show_toast(_("Failed to connect to server"), self.preferences_dialog)
@Gtk.Template.Callback()
def pull_featured_model(self, button):
action_row = button.get_parent().get_parent().get_parent()
button.get_parent().remove(button)
model = f"{action_row.get_title().lower()}:latest"
action_row.set_subtitle(_("Pulling in the background..."))
spinner = Gtk.Spinner()
spinner.set_spinning(True)
action_row.add_suffix(spinner)
action_row.set_sensitive(False)
self.pull_model(model)
@Gtk.Template.Callback()
def closing_app(self, user_data):
if self.get_hide_on_close():
@@ -338,6 +346,7 @@ class AlpacaWindow(Adw.ApplicationWindow):
else:
logger.info("Closing app...")
local_instance.stop()
self.get_application().quit()
@Gtk.Template.Callback()
def model_spin_changed(self, spin):
@@ -427,26 +436,78 @@ class AlpacaWindow(Adw.ApplicationWindow):
self.available_model_list_box.set_visible(True)
self.no_results_page.set_visible(False)
def manage_models_button_activate(self, button=None):
logger.debug(f"Managing models")
self.update_list_local_models()
if len(self.chats["chats"][self.chats["selected_chat"]]["messages"].keys()) > 0:
last_model_used = self.chats["chats"][self.chats["selected_chat"]]["messages"][list(self.chats["chats"][self.chats["selected_chat"]]["messages"].keys())[-1]]["model"]
last_model_used = self.convert_model_name(last_model_used, 0)
for i in range(self.model_string_list.get_n_items()):
if self.model_string_list.get_string(i) == last_model_used:
self.model_drop_down.set_selected(i)
break
self.manage_models_dialog.present(self)
@Gtk.Template.Callback()
def close_model_popup(self, *_):
self.model_popover.hide()
@Gtk.Template.Callback()
def change_model(self, listbox=None, row=None):
if not row:
current_model = self.model_selector_button.get_name()
if current_model != 'NO_MODEL':
for i, m in enumerate(self.local_models):
if m == current_model:
self.model_list_box.select_row(self.model_list_box.get_row_at_index(i))
return
if len(self.local_models) > 0:
self.model_list_box.select_row(self.model_list_box.get_row_at_index(0))
return
else:
model_name = None
else:
model_name = row.get_child().get_label()
button_content = Gtk.Box(
spacing=10
)
button_content.append(
Gtk.Label(
label=model_name if model_name else _("Select a Model"),
ellipsize=2
)
)
button_content.append(
Gtk.Image.new_from_icon_name("down-symbolic")
)
self.model_selector_button.set_name(row.get_name() if row else 'NO_MODEL')
self.model_selector_button.set_child(button_content)
self.close_model_popup()
self.verify_if_image_can_be_used()
def verify_if_image_can_be_used(self):
logger.debug("Verifying if image can be used")
selected = self.get_current_model(1)
if selected == None:
return True
selected = selected.split(":")[0]
if selected in [key for key, value in self.available_models.items() if value["image"]]:
for name, content in self.attachments.items():
if content['type'] == 'image':
content['button'].set_css_classes(["flat"])
return True
for name, content in self.attachments.items():
if content['type'] == 'image':
content['button'].set_css_classes(["flat", "error"])
return False
def convert_model_name(self, name:str, mode:int) -> str: # mode=0 name:tag -> Name (tag) | mode=1 Name (tag) -> name:tag
if mode == 0:
return "{} ({})".format(name.split(":")[0].replace("-", " ").title(), name.split(":")[1])
if mode == 1:
return "{}:{}".format(name.split(" (")[0].replace(" ", "-").lower(), name.split(" (")[1][:-1])
try:
if mode == 0:
return "{} ({})".format(name.split(":")[0].replace("-", " ").title(), name.split(":")[1])
if mode == 1:
return "{}:{}".format(name.split(" (")[0].replace(" ", "-").lower(), name.split(" (")[1][:-1])
except Exception as e:
pass
def check_alphanumeric(self, editable, text, length, position):
new_text = ''.join([char for char in text if char.isalnum() or char in ['-', '.', ':', '_']])
def get_current_model(self, mode:int) -> str:
if not self.model_list_box.get_selected_row():
return None
if mode == 0:
return self.model_list_box.get_selected_row().get_child().get_label()
if mode == 1:
return self.model_list_box.get_selected_row().get_name()
def check_alphanumeric(self, editable, text, length, position, allowed_chars):
new_text = ''.join([char for char in text if char.isalnum() or char in allowed_chars])
if new_text != text:
editable.stop_emission_by_name("insert-text")
@@ -455,7 +516,7 @@ class AlpacaWindow(Adw.ApplicationWindow):
modelfile_buffer.delete(modelfile_buffer.get_start_iter(), modelfile_buffer.get_end_iter())
self.create_model_system.set_text('')
if not file:
response = connection_handler.simple_post(f"{connection_handler.url}/api/show", json.dumps({"name": self.convert_model_name(model, 1)}))
response = connection_handler.simple_post(f"{connection_handler.URL}/api/show", json.dumps({"name": self.convert_model_name(model, 1)}))
if response.status_code == 200:
data = json.loads(response.text)
modelfile = []
@@ -500,6 +561,8 @@ class AlpacaWindow(Adw.ApplicationWindow):
if os.path.exists(os.path.join(self.data_dir, "chats", self.chats['selected_chat'], message_id)):
shutil.rmtree(os.path.join(self.data_dir, "chats", self.chats['selected_chat'], message_id))
self.save_history()
if len(self.chats["chats"][self.chats["selected_chat"]]["messages"]) == 0:
self.load_history_into_chat()
def copy_message(self, message_element):
logger.debug("Copying message")
@@ -529,6 +592,13 @@ class AlpacaWindow(Adw.ApplicationWindow):
text_view.set_cursor_visible(True)
self.editing_message = {"text_view": text_view, "id": message_id, "button_container": button_container, "footer": footer}
if text_view.observe_controllers().get_n_items() == 8:
print(text_view.observe_controllers().get_n_items())
enter_key_controller = Gtk.EventControllerKey.new()
enter_key_controller.connect("key-pressed", lambda controller, keyval, keycode, state: self.handle_enter_key() if keyval==Gdk.KEY_Return and not (state & Gdk.ModifierType.SHIFT_MASK) else None)
text_view.add_controller(enter_key_controller)
print(text_view.observe_controllers().get_n_items())
self.set_focus(text_view)
def preview_file(self, file_path, file_type, presend_name):
logger.debug(f"Previewing file: {file_path}")
@@ -558,7 +628,7 @@ class AlpacaWindow(Adw.ApplicationWindow):
self.file_preview_text_view.set_visible(True)
buffer = self.file_preview_text_view.get_buffer()
buffer.delete(buffer.get_start_iter(), buffer.get_end_iter())
buffer.insert(buffer.get_start_iter(), content, len(content))
buffer.insert(buffer.get_start_iter(), content, len(content.encode('utf-8')))
if file_type == 'youtube':
self.file_preview_dialog.set_title(content.split('\n')[0])
self.file_preview_open_button.set_name(content.split('\n')[2])
@@ -593,6 +663,8 @@ class AlpacaWindow(Adw.ApplicationWindow):
return messages
def generate_chat_title(self, message, label_element):
if not label_element.get_name().startswith(_("New Chat")):
return
logger.debug("Generating chat title")
prompt = f"""
Generate a title following these rules:
@@ -605,11 +677,11 @@ Generate a title following these rules:
```PROMPT
{message['content']}
```"""
current_model = self.convert_model_name(self.model_drop_down.get_selected_item().get_string(), 1)
current_model = self.get_current_model(1)
data = {"model": current_model, "prompt": prompt, "stream": False}
if 'images' in message:
data["images"] = message['images']
response = connection_handler.simple_post(f"{connection_handler.url}/api/generate", data=json.dumps(data))
response = connection_handler.simple_post(f"{connection_handler.URL}/api/generate", data=json.dumps(data))
new_chat_name = json.loads(response.text)["response"].strip().removeprefix("Title: ").removeprefix("title: ").strip('\'"').replace('\n', ' ').title().replace('\'S', '\'s')
new_chat_name = new_chat_name[:50] + (new_chat_name[50:] and '...')
@@ -620,14 +692,11 @@ Generate a title following these rules:
editable=False,
focusable=True,
wrap_mode= Gtk.WrapMode.WORD,
margin_top=12,
margin_bottom=12,
margin_start=12,
margin_end=12,
hexpand=True,
cursor_visible=False,
css_classes=["flat"],
css_classes=["flat", "response_message"] if bot else ["flat", "user_message"],
)
if not bot:
message_text.update_property([4, 7, 1], [_("User message"), True, msg])
message_buffer = message_text.get_buffer()
message_buffer.insert(message_buffer.get_end_iter(), msg)
if footer is not None:
@@ -666,7 +735,8 @@ Generate a title following these rules:
message_box = Gtk.Box(
orientation=1,
halign='fill',
css_classes=[None if bot else "card"]
css_classes=[None if bot else "card"],
spacing=5
)
message_text.set_valign(Gtk.Align.CENTER)
@@ -696,6 +766,7 @@ Generate a title following these rules:
name=os.path.join(self.data_dir, "chats", "{selected_chat}", message_id, image),
tooltip_text=_("Image")
)
image_element.update_property([4], [_("Image")])
button.connect("clicked", lambda button, file_path=path: self.preview_file(file_path, 'image', None))
except Exception as e:
logger.error(e)
@@ -722,6 +793,7 @@ Generate a title following these rules:
css_classes=["flat", "chat_image_button"],
tooltip_text=_("Missing Image")
)
image_texture.update_property([4], [_("Missing image")])
button.connect("clicked", lambda button : self.show_toast(_("Missing image"), self.main_overlay))
image_container.append(button)
message_box.append(image_scroller)
@@ -784,9 +856,8 @@ Generate a title following these rules:
def update_list_local_models(self):
logger.debug("Updating list of local models")
self.local_models = []
response = connection_handler.simple_get(f"{connection_handler.url}/api/tags")
for i in range(self.model_string_list.get_n_items() -1, -1, -1):
self.model_string_list.remove(i)
response = connection_handler.simple_get(f"{connection_handler.URL}/api/tags")
self.model_list_box.remove_all()
if response.status_code == 200:
self.local_model_list_box.remove_all()
if len(json.loads(response.text)['models']) == 0:
@@ -810,9 +881,17 @@ Generate a title following these rules:
model_row.add_suffix(button)
self.local_model_list_box.append(model_row)
self.model_string_list.append(model_name)
selector_row = Gtk.ListBoxRow(
child = Gtk.Label(
label=model_name, halign=1, hexpand=True
),
halign=0,
hexpand=True,
name=model["name"],
tooltip_text=model_name
)
self.model_list_box.append(selector_row)
self.local_models.append(model["name"])
#self.verify_if_image_can_be_used()
else:
self.connection_error()
@@ -822,7 +901,7 @@ Generate a title following these rules:
def verify_connection(self):
try:
response = connection_handler.simple_get(f"{connection_handler.url}/api/tags")
response = connection_handler.simple_get(f"{connection_handler.URL}/api/tags")
if response.status_code == 200:
self.save_server_config()
self.update_list_local_models()
@@ -845,7 +924,7 @@ Generate a title following these rules:
parts.append({"type": "normal", "text": normal_text.strip()})
language = match.group(1)
code_text = match.group(2)
parts.append({"type": "code", "text": code_text, "language": language})
parts.append({"type": "code", "text": code_text, "language": 'python3' if language == 'python' else language})
pos = end
# Match code blocks without language
no_lang_code_block_pattern = re.compile(r'`\n(.*?)\n`', re.DOTALL)
@@ -883,10 +962,8 @@ Generate a title following these rules:
editable=False,
focusable=True,
wrap_mode= Gtk.WrapMode.WORD,
margin_top=12,
margin_bottom=12,
hexpand=True,
css_classes=["flat"]
css_classes=["flat", "response_message"]
)
message_buffer = message_text.get_buffer()
@@ -915,6 +992,7 @@ Generate a title following these rules:
if footer: message_buffer.insert_markup(message_buffer.get_end_iter(), footer, len(footer.encode('utf-8')))
message_text.update_property([4, 7, 1], [_("Response message"), False, message_buffer.get_text(message_buffer.get_start_iter(), message_buffer.get_end_iter(), False)])
self.bot_message_box.append(message_text)
elif part['type'] == 'code':
language = None
@@ -932,10 +1010,11 @@ Generate a title following these rules:
buffer.set_style_scheme(source_style)
source_view = GtkSource.View(
auto_indent=True, indent_width=4, buffer=buffer, show_line_numbers=True,
top_margin=6, bottom_margin=6, left_margin=12, right_margin=12
top_margin=6, bottom_margin=6, left_margin=12, right_margin=12, css_classes=["code_block"]
)
source_view.update_property([4], [_("{}Code Block").format('{} '.format(language.get_name()) if language else "")])
source_view.set_editable(False)
code_block_box = Gtk.Box(css_classes=["card"], orientation=1, overflow=1)
code_block_box = Gtk.Box(css_classes=["card", "code_block"], orientation=1, overflow=1)
title_box = Gtk.Box(margin_start=12, margin_top=3, margin_bottom=3, margin_end=3)
title_box.append(Gtk.Label(label=language.get_name() if language else _("Code Block"), hexpand=True, xalign=0))
copy_button = Gtk.Button(icon_name="edit-copy-symbolic", css_classes=["flat", "circular"], tooltip_text=_("Copy Message"))
@@ -982,10 +1061,9 @@ Generate a title following these rules:
def update_bot_message(self, data, message_id):
if self.bot_message is None:
self.save_history()
sys.exit()
vadjustment = self.chat_window.get_vadjustment()
if message_id not in self.chats["chats"][self.chats["selected_chat"]]["messages"] or vadjustment.get_value() + 50 >= vadjustment.get_upper() - vadjustment.get_page_size():
if not self.chats["chats"][self.chats["selected_chat"]]["messages"][message_id] or vadjustment.get_value() + 50 >= vadjustment.get_upper() - vadjustment.get_page_size():
GLib.idle_add(vadjustment.set_value, vadjustment.get_upper())
if 'done' in data and data['done']:
formated_date = GLib.markup_escape_text(self.generate_datetime_format(datetime.strptime(self.chats["chats"][self.chats["selected_chat"]]["messages"][message_id]["date"], '%Y/%m/%d %H:%M:%S')))
@@ -997,8 +1075,8 @@ Generate a title following these rules:
first_paragraph = self.bot_message.get_text(self.bot_message.get_start_iter(), self.bot_message.get_end_iter(), False).split("\n")[0]
GLib.idle_add(self.show_notification, self.chats["selected_chat"], first_paragraph[:100] + (first_paragraph[100:] and '...'), Gio.ThemedIcon.new("chat-message-new-symbolic"))
else:
if not self.chats["chats"][self.chats["selected_chat"]]["messages"][message_id]["content"] and self.loading_spinner:
GLib.idle_add(self.chat_container.remove, self.loading_spinner)
if self.loading_spinner:
GLib.idle_add(self.loading_spinner.get_parent().remove, self.loading_spinner)
self.loading_spinner = None
GLib.idle_add(self.bot_message.insert, self.bot_message.get_end_iter(), data['message']['content'])
self.chats["chats"][self.chats["selected_chat"]]["messages"][message_id]['content'] += data['message']['content']
@@ -1007,23 +1085,24 @@ Generate a title following these rules:
for element in [self.chat_list_box, self.add_chat_button, self.secondary_menu_button]:
element.set_sensitive(status)
def switch_send_stop_button(self):
self.stop_button.set_visible(self.send_button.get_visible())
self.send_button.set_visible(not self.send_button.get_visible())
def switch_send_stop_button(self, send:bool):
self.stop_button.set_visible(not send)
self.send_button.set_visible(send)
def run_message(self, messages, model, message_id):
logger.debug("Running message")
self.bot_message_button_container.set_visible(False)
self.chats["chats"][self.chats["selected_chat"]]["messages"][message_id] = {
"role": "assistant",
"model": model,
"date": datetime.now().strftime("%Y/%m/%d %H:%M:%S"),
"content": ''
}
if message_id not in self.chats["chats"][self.chats["selected_chat"]]["messages"]:
self.chats["chats"][self.chats["selected_chat"]]["messages"][message_id] = {
"role": "assistant",
"model": model,
"date": datetime.now().strftime("%Y/%m/%d %H:%M:%S"),
"content": ''
}
if self.regenerate_button:
GLib.idle_add(self.chat_container.remove, self.regenerate_button)
try:
response = connection_handler.stream_post(f"{connection_handler.url}/api/chat", data=json.dumps({"model": model, "messages": messages}), callback=lambda data, message_id=message_id: self.update_bot_message(data, message_id))
response = connection_handler.stream_post(f"{connection_handler.URL}/api/chat", data=json.dumps({"model": model, "messages": messages}), callback=lambda data, message_id=message_id: self.update_bot_message(data, message_id))
if response.status_code != 200:
raise Exception('Network Error')
GLib.idle_add(self.add_code_blocks)
@@ -1040,40 +1119,46 @@ Generate a title following these rules:
GLib.idle_add(self.chat_container.append, self.regenerate_button)
self.regenerate_button.connect('clicked', lambda button, message_id=message_id, bot_message_box=self.bot_message_box, bot_message_button_container=self.bot_message_button_container : self.regenerate_message(message_id, bot_message_box, bot_message_button_container))
finally:
GLib.idle_add(self.switch_send_stop_button)
GLib.idle_add(self.switch_send_stop_button, True)
GLib.idle_add(self.toggle_ui_sensitive, True)
if self.loading_spinner:
GLib.idle_add(self.chat_container.remove, self.loading_spinner)
GLib.idle_add(self.loading_spinner.get_parent().remove, self.loading_spinner)
self.loading_spinner = None
def regenerate_message(self, message_id, bot_message_box, bot_message_button_container):
self.bot_message_button_container = bot_message_button_container
self.bot_message_view = Gtk.TextView(
editable=False,
focusable=True,
wrap_mode= Gtk.WrapMode.WORD,
margin_top=12,
margin_bottom=12,
hexpand=True,
css_classes=["flat"]
)
self.bot_message = self.bot_message_view.get_buffer()
for widget in list(bot_message_box):
bot_message_box.remove(widget)
bot_message_box.append(self.bot_message_view)
history = self.convert_history_to_ollama()[:list(self.chats["chats"][self.chats["selected_chat"]]["messages"].keys()).index(message_id)]
if message_id in self.chats["chats"][self.chats["selected_chat"]]["messages"]:
del self.chats["chats"][self.chats["selected_chat"]]["messages"][message_id]
data = {
"model": self.convert_model_name(self.model_drop_down.get_selected_item().get_string(), 1),
"messages": history,
"options": {"temperature": self.model_tweaks["temperature"], "seed": self.model_tweaks["seed"]},
"keep_alive": f"{self.model_tweaks['keep_alive']}m"
}
self.switch_send_stop_button()
self.toggle_ui_sensitive(False)
thread = threading.Thread(target=self.run_message, args=(data['messages'], data['model'], message_id))
thread.start()
if not self.bot_message:
self.bot_message_button_container = bot_message_button_container
self.bot_message_view = Gtk.TextView(
editable=False,
focusable=True,
wrap_mode= Gtk.WrapMode.WORD,
margin_top=12,
margin_bottom=12,
hexpand=True,
css_classes=["flat"]
)
self.bot_message = self.bot_message_view.get_buffer()
self.bot_message_box = bot_message_box
for widget in list(bot_message_box):
bot_message_box.remove(widget)
self.loading_spinner = Gtk.Spinner(spinning=True, margin_top=12, margin_bottom=12, hexpand=True)
bot_message_box.append(self.loading_spinner)
bot_message_box.append(self.bot_message_view)
if message_id in self.chats["chats"][self.chats["selected_chat"]]["messages"]:
self.chats["chats"][self.chats["selected_chat"]]["messages"][message_id]['content'] = ''
history = self.convert_history_to_ollama()[:list(self.chats["chats"][self.chats["selected_chat"]]["messages"].keys()).index(message_id)]
data = {
"model": self.get_current_model(1),
"messages": history,
"options": {"temperature": self.model_tweaks["temperature"], "seed": self.model_tweaks["seed"]},
"keep_alive": f"{self.model_tweaks['keep_alive']}m"
}
self.switch_send_stop_button(False)
self.toggle_ui_sensitive(False)
thread = threading.Thread(target=self.run_message, args=(data['messages'], data['model'], message_id))
thread.start()
else:
self.show_toast(_("Message cannot be regenerated while receiving a response"), self.main_overlay)
def pull_model_update(self, data, model_name):
if 'error' in data:
@@ -1093,11 +1178,12 @@ Generate a title following these rules:
def pull_model_process(self, model, modelfile):
if modelfile:
data = {"name": model, "modelfile": modelfile}
response = connection_handler.stream_post(f"{connection_handler.url}/api/create", data=json.dumps(data), callback=lambda data, model_name=model: self.pull_model_update(data, model_name))
response = connection_handler.stream_post(f"{connection_handler.URL}/api/create", data=json.dumps(data), callback=lambda data, model_name=model: self.pull_model_update(data, model_name))
else:
data = {"name": model}
response = connection_handler.stream_post(f"{connection_handler.url}/api/pull", data=json.dumps(data), callback=lambda data, model_name=model: self.pull_model_update(data, model_name))
response = connection_handler.stream_post(f"{connection_handler.URL}/api/pull", data=json.dumps(data), callback=lambda data, model_name=model: self.pull_model_update(data, model_name))
GLib.idle_add(self.update_list_local_models)
GLib.idle_add(self.change_model)
if response.status_code == 200 and 'error' not in self.pulling_models[model]:
GLib.idle_add(self.show_notification, _("Task Complete"), _("Model '{}' pulled successfully.").format(model), Gio.ThemedIcon.new("emblem-ok-symbolic"))
@@ -1164,7 +1250,6 @@ Generate a title following these rules:
self.model_link_button.set_name(self.available_models[model_name]['url'])
self.model_link_button.set_tooltip_text(self.available_models[model_name]['url'])
self.available_model_list_box.unselect_all()
self.model_tag_list_box.connect('row_selected', lambda list_box, row: self.confirm_pull_model(row.get_name()) if row else None)
self.model_tag_list_box.remove_all()
tags = self.available_models[model_name]['tags']
for tag_data in tags:
@@ -1174,12 +1259,24 @@ Generate a title following these rules:
subtitle = tag_data[1],
name = f"{model_name}:{tag_data[0]}"
)
tag_row.add_suffix(Gtk.Image.new_from_icon_name("folder-download-symbolic"))
download_icon = Gtk.Image.new_from_icon_name("folder-download-symbolic")
tag_row.add_suffix(download_icon)
download_icon.update_property([4], [_("Download {}:{}").format(model_name, tag_data[0])])
gesture_click = Gtk.GestureClick.new()
gesture_click.connect("pressed", lambda *_, name=f"{model_name}:{tag_data[0]}" : self.confirm_pull_model(name))
event_controller_key = Gtk.EventControllerKey.new()
event_controller_key.connect("key-pressed", lambda controller, key, *_, name=f"{model_name}:{tag_data[0]}" : self.confirm_pull_model(name) if key in (Gdk.KEY_space, Gdk.KEY_Return) else None)
tag_row.add_controller(gesture_click)
tag_row.add_controller(event_controller_key)
self.model_tag_list_box.append(tag_row)
return True
def update_list_available_models(self):
logger.debug("Updating list of available models")
self.available_model_list_box.connect('row_selected', lambda list_box, row: self.list_available_model_tags(row.get_name()) if row else None)
self.available_model_list_box.remove_all()
for name, model_info in self.available_models.items():
model = Adw.ActionRow(
@@ -1187,13 +1284,19 @@ Generate a title following these rules:
subtitle = available_models_descriptions.descriptions[name] + ("\n\n<b>{}</b>".format(_("Image Recognition")) if model_info['image'] else ""),
name = name
)
if model_info["image"]:
image_icon = Gtk.Image.new_from_icon_name("image-x-generic-symbolic")
image_icon.set_margin_start(5)
#model.add_suffix(image_icon)
next_icon = Gtk.Image.new_from_icon_name("go-next")
next_icon.set_margin_start(5)
next_icon.update_property([4], [_("Enter download menu for {}").format(name.replace("-", ""))])
model.add_suffix(next_icon)
gesture_click = Gtk.GestureClick.new()
gesture_click.connect("pressed", lambda *_, name=name : self.list_available_model_tags(name))
event_controller_key = Gtk.EventControllerKey.new()
event_controller_key.connect("key-pressed", lambda controller, key, *_, name=name : self.list_available_model_tags(name) if key in (Gdk.KEY_space, Gdk.KEY_Return) else None)
model.add_controller(gesture_click)
model.add_controller(event_controller_key)
self.available_model_list_box.append(model)
def save_history(self):
@@ -1201,17 +1304,56 @@ Generate a title following these rules:
with open(os.path.join(self.data_dir, "chats", "chats.json"), "w+", encoding="utf-8") as f:
json.dump(self.chats, f, indent=4)
def send_sample_prompt(self, prompt):
buffer = self.message_text_view.get_buffer()
buffer.delete(buffer.get_start_iter(), buffer.get_end_iter())
buffer.insert(buffer.get_start_iter(), prompt, len(prompt.encode('utf-8')))
self.send_message()
def load_history_into_chat(self):
for widget in list(self.chat_container): self.chat_container.remove(widget)
for key, message in self.chats['chats'][self.chats["selected_chat"]]['messages'].items():
if message:
formated_date = GLib.markup_escape_text(self.generate_datetime_format(datetime.strptime(message['date'] + (":00" if message['date'].count(":") == 1 else ""), '%Y/%m/%d %H:%M:%S')))
if message['role'] == 'user':
self.show_message(message['content'], False, f"\n\n<small>{formated_date}</small>", message['images'] if 'images' in message else None, message['files'] if 'files' in message else None, message_id=key)
else:
self.show_message(message['content'], True, f"\n\n{self.convert_model_name(message['model'], 0)}\n<small>{formated_date}</small>", message_id=key)
self.add_code_blocks()
self.bot_message = None
self.chat_welcome_screen = None
if len(self.chats['chats'][self.chats["selected_chat"]]['messages']) > 0:
for key, message in self.chats['chats'][self.chats["selected_chat"]]['messages'].items():
if message:
formated_date = GLib.markup_escape_text(self.generate_datetime_format(datetime.strptime(message['date'] + (":00" if message['date'].count(":") == 1 else ""), '%Y/%m/%d %H:%M:%S')))
if message['role'] == 'user':
self.show_message(message['content'], False, f"\n\n<small>{formated_date}</small>", message['images'] if 'images' in message else None, message['files'] if 'files' in message else None, message_id=key)
else:
self.show_message(message['content'], True, f"\n\n{self.convert_model_name(message['model'], 0)}\n<small>{formated_date}</small>", message_id=key)
self.add_code_blocks()
self.bot_message = None
else:
button_container = Gtk.Box(
orientation = 1,
spacing = 10,
halign = 3
)
if len(self.local_models) > 0:
for prompt in random.sample(self.possible_prompts, 3):
prompt_button = Gtk.Button(
label=prompt,
tooltip_text=_("Send prompt: '{}'").format(prompt)
)
prompt_button.connect('clicked', lambda *_, prompt=prompt : self.send_sample_prompt(prompt))
button_container.append(prompt_button)
else:
button = Gtk.Button(
label=_("Open Model Manager"),
tooltip_text=_("Open Model Manager"),
css_classes=["accent", "pill"]
)
button.connect('clicked', lambda *_ : self.manage_models_dialog.present(self))
button_container.append(button)
self.chat_welcome_screen = Adw.StatusPage(
icon_name="com.jeffser.Alpaca",
title="Alpaca",
description=_("Try one of these prompts") if len(self.local_models) > 0 else _("It looks like you don't have any models downloaded yet. Download models to get started!"),
child=button_container,
vexpand=True
)
self.chat_container.append(self.chat_welcome_screen)
def load_history(self):
logger.debug("Loading history")
@@ -1227,13 +1369,17 @@ Generate a title following these rules:
self.chats["order"] = []
for chat_name in self.chats["chats"].keys():
self.chats["order"].append(chat_name)
self.model_list_box.select_row(self.model_list_box.get_row_at_index(0))
self.chats["chats"] = {key: value for key, value in self.chats["chats"].items() if key in self.chats["order"]}
if self.chats["selected_chat"] not in self.chats["order"]:
self.chats["selected_chat"] = self.chats["order"][0]
if len(self.chats["chats"][self.chats["selected_chat"]]["messages"].keys()) > 0:
last_model_used = self.chats["chats"][self.chats["selected_chat"]]["messages"][list(self.chats["chats"][self.chats["selected_chat"]]["messages"].keys())[-1]]["model"]
last_model_used = self.convert_model_name(last_model_used, 0)
for i in range(self.model_string_list.get_n_items()):
if self.model_string_list.get_string(i) == last_model_used:
self.model_drop_down.set_selected(i)
for i, m in enumerate(self.local_models):
if m == last_model_used:
self.model_list_box.select_row(self.model_list_box.get_row_at_index(i))
break
except Exception as e:
logger.error(e)
self.chats = {"chats": {}, "selected_chat": None, "order": []}
@@ -1263,8 +1409,9 @@ Generate a title following these rules:
def clear_chat(self):
logger.info("Clearing chat")
for widget in list(self.chat_container): self.chat_container.remove(widget)
self.chats["chats"][self.chats["selected_chat"]]["messages"] = []
self.chats["chats"][self.chats["selected_chat"]]["messages"] = {}
self.save_history()
self.load_history_into_chat()
def delete_chat(self, chat_name):
logger.info("Deleting chat")
@@ -1279,6 +1426,14 @@ Generate a title following these rules:
if self.chats['selected_chat'] == chat_name:
self.chat_list_box.select_row(self.chat_list_box.get_row_at_index(0))
def duplicate_chat(self, chat_name):
new_chat_name = self.generate_numbered_name(_("Copy of {}").format(chat_name), self.chats["chats"].keys())
self.chats["chats"][new_chat_name] = self.chats["chats"][chat_name]
self.chats["order"].insert(0, new_chat_name)
self.save_history()
self.new_chat_element(new_chat_name, True, False)
shutil.copytree(os.path.join(self.data_dir, "chats", chat_name), os.path.join(self.data_dir, "chats", new_chat_name))
def rename_chat(self, old_chat_name, new_chat_name, label_element):
logger.info(f"Renaming chat \"{old_chat_name}\" -> \"{new_chat_name}\"")
new_chat_name = self.generate_numbered_name(new_chat_name, self.chats["chats"].keys())
@@ -1300,6 +1455,7 @@ Generate a title following these rules:
self.chats["order"].insert(0, chat_name)
self.save_history()
self.new_chat_element(chat_name, True, False)
self.set_focus(self.message_text_view)
def stop_pull_model(self, model_name):
logger.debug("Stopping model pull")
@@ -1308,10 +1464,11 @@ Generate a title following these rules:
def delete_model(self, model_name):
logger.debug("Deleting model")
response = connection_handler.simple_delete(f"{connection_handler.url}/api/delete", data={"name": model_name})
response = connection_handler.simple_delete(f"{connection_handler.URL}/api/delete", data={"name": model_name})
self.update_list_local_models()
if response.status_code == 200:
self.show_toast(_("Model deleted successfully"), self.manage_models_overlay)
self.change_model()
else:
self.manage_models_dialog.close()
self.connection_error()
@@ -1322,6 +1479,7 @@ Generate a title following these rules:
menu_model=self.chat_right_click_menu,
has_arrow=False,
halign=1,
height_request=155
)
self.selected_chat_row = chat_row
position = Gdk.Rectangle()
@@ -1372,17 +1530,17 @@ Generate a title following these rules:
def connect_remote(self, url, bearer_token):
logger.debug(f"Connecting to remote: {url}")
connection_handler.url = url
connection_handler.bearer_token = bearer_token
self.remote_url = connection_handler.url
connection_handler.URL = url
connection_handler.BEARER_TOKEN = bearer_token
self.remote_url = connection_handler.URL
self.remote_connection_entry.set_text(self.remote_url)
if self.verify_connection() == False: self.connection_error()
def connect_local(self):
logger.debug("Connecting to Alpaca's Ollama instance")
self.run_remote = False
connection_handler.bearer_token = None
connection_handler.url = f"http://127.0.0.1:{local_instance.port}"
connection_handler.BEARER_TOKEN = None
connection_handler.URL = f"http://127.0.0.1:{local_instance.port}"
local_instance.start()
if self.verify_connection() == False:
self.connection_error()
@@ -1392,7 +1550,7 @@ Generate a title following these rules:
def connection_error(self):
logger.error("Connection error")
if self.run_remote:
dialogs.reconnect_remote(self, connection_handler.url, connection_handler.bearer_token)
dialogs.reconnect_remote(self, connection_handler.URL, connection_handler.BEARER_TOKEN)
else:
local_instance.reset()
self.show_toast(_("There was an error with the local Ollama instance, so it has been reset"), self.main_overlay)
@@ -1403,15 +1561,15 @@ Generate a title following these rules:
if new_value != self.run_remote:
self.run_remote = new_value
if self.run_remote:
connection_handler.bearer_token = self.remote_bearer_token
connection_handler.url = self.remote_url
connection_handler.BEARER_TOKEN = self.remote_bearer_token
connection_handler.URL = self.remote_url
if self.verify_connection() == False:
self.connection_error()
else:
local_instance.stop()
else:
connection_handler.bearer_token = None
connection_handler.url = f"http://127.0.0.1:{local_instance.port}"
connection_handler.BEARER_TOKEN = None
connection_handler.URL = f"http://127.0.0.1:{local_instance.port}"
local_instance.start()
if self.verify_connection() == False:
self.connection_error()
@@ -1428,7 +1586,7 @@ Generate a title following these rules:
with tempfile.TemporaryDirectory() as temp_dir:
json_path = os.path.join(temp_dir, "data.json")
with open(json_path, "wb", encoding="utf-8") as json_file:
with open(json_path, "wb") as json_file:
json_file.write(json_data)
tar_path = os.path.join(temp_dir, chat_name)
@@ -1438,7 +1596,7 @@ Generate a title following these rules:
if os.path.exists(directory) and os.path.isdir(directory):
tar.add(directory, arcname=os.path.basename(directory))
with open(tar_path, "rb", encoding="utf-8") as tar:
with open(tar_path, "rb") as tar:
tar_content = tar.read()
file.replace_contents_async(
@@ -1466,7 +1624,7 @@ Generate a title following these rules:
with tempfile.TemporaryDirectory() as temp_dir:
tar_filename = os.path.join(temp_dir, "imported_chat.tar")
with open(tar_filename, "wb", encoding="utf-8") as tar_file:
with open(tar_filename, "wb") as tar_file:
tar_file.write(tar_content.get_data())
with tarfile.open(tar_filename, "r") as tar:
@@ -1481,6 +1639,7 @@ Generate a title following these rules:
for chat_name, chat_content in data.items():
new_chat_name = self.generate_numbered_name(chat_name, list(self.chats['chats'].keys()))
self.chats['chats'][new_chat_name] = chat_content
self.chats["order"].insert(0, new_chat_name)
src_path = os.path.join(temp_dir, chat_name)
if os.path.exists(src_path) and os.path.isdir(src_path):
dest_path = os.path.join(self.data_dir, "chats", new_chat_name)
@@ -1576,8 +1735,10 @@ Generate a title following these rules:
chat_row = self.selected_chat_row
chat_name = chat_row.get_child().get_name()
action_name = action.get_name()
if action_name == 'delete_chat':
if action_name in ('delete_chat', 'delete_current_chat'):
dialogs.delete_chat(self, chat_name)
elif action_name in ('duplicate_chat', 'duplicate_current_chat'):
self.duplicate_chat(chat_name)
elif action_name in ('rename_chat', 'rename_current_chat'):
dialogs.rename_chat(self, chat_name, chat_row.get_child())
elif action_name in ('export_chat', 'export_current_chat'):
@@ -1625,7 +1786,8 @@ Generate a title following these rules:
self.attach_file(os.path.join(self.cache_dir, 'tmp/images/{}'.format(image_name)), 'image')
else:
self.show_toast(_("Image recognition is only available on specific models"), self.main_overlay)
except Exception as e: 'huh'
except Exception as e:
pass
def on_clipboard_paste(self, textview):
logger.debug("Pasting from clipboard")
@@ -1633,27 +1795,9 @@ Generate a title following these rules:
clipboard.read_text_async(None, self.cb_text_received)
clipboard.read_texture_async(None, self.cb_image_received)
def on_model_dropdown_setup(self, factory, list_item):
label = Gtk.Label()
label.set_ellipsize(2)
label.set_xalign(0)
list_item.set_child(label)
def on_model_dropdown_bind(self, factory, list_item):
label = list_item.get_child()
item = list_item.get_item()
label.set_text(item.get_string())
label.set_tooltip_text(item.get_string())
def setup_model_dropdown(self):
factory = Gtk.SignalListItemFactory()
factory.connect("setup", self.on_model_dropdown_setup)
factory.connect("bind", self.on_model_dropdown_bind)
self.model_drop_down.set_factory(factory)
def handle_enter_key(self):
self.send_message()
if not self.bot_message:
self.send_message()
return True
def __init__(self, **kwargs):
@@ -1663,9 +1807,9 @@ Generate a title following these rules:
self.available_models = json.load(f)
if not os.path.exists(os.path.join(self.data_dir, "chats")):
os.makedirs(os.path.join(self.data_dir, "chats"))
key_controller = Gtk.EventControllerKey.new()
key_controller.connect("key-pressed", lambda controller, keyval, keycode, state: self.handle_enter_key() if keyval==Gdk.KEY_Return else None)
self.message_text_view.add_controller(key_controller)
enter_key_controller = Gtk.EventControllerKey.new()
enter_key_controller.connect("key-pressed", lambda controller, keyval, keycode, state: self.handle_enter_key() if keyval==Gdk.KEY_Return and not (state & Gdk.ModifierType.SHIFT_MASK) else None)
self.message_text_view.add_controller(enter_key_controller)
self.set_help_overlay(self.shortcut_window)
self.get_application().set_accels_for_action("win.show-help-overlay", ['<primary>slash'])
self.get_application().create_action('new_chat', lambda *_: self.new_chat(), ['<primary>n'])
@@ -1674,22 +1818,25 @@ Generate a title following these rules:
self.get_application().create_action('create_model_from_existing', lambda *_: dialogs.create_model_from_existing(self))
self.get_application().create_action('create_model_from_file', lambda *_: dialogs.create_model_from_file(self))
self.get_application().create_action('create_model_from_name', lambda *_: dialogs.create_model_from_name(self))
self.get_application().create_action('duplicate_chat', self.chat_actions)
self.get_application().create_action('duplicate_current_chat', self.current_chat_actions)
self.get_application().create_action('delete_chat', self.chat_actions)
self.get_application().create_action('delete_current_chat', self.current_chat_actions)
self.get_application().create_action('rename_chat', self.chat_actions)
self.get_application().create_action('rename_current_chat', self.current_chat_actions)
self.get_application().create_action('export_chat', self.chat_actions)
self.get_application().create_action('export_current_chat', self.current_chat_actions)
self.get_application().create_action('toggle_sidebar', lambda *_: self.split_view_overlay.set_show_sidebar(not self.split_view_overlay.get_show_sidebar()), ['F9'])
self.get_application().create_action('manage_models', lambda *_: self.manage_models_button_activate(), ['<primary>m'])
self.get_application().create_action('manage_models', lambda *_: self.manage_models_dialog.present(self), ['<primary>m'])
self.message_text_view.connect("paste-clipboard", self.on_clipboard_paste)
self.file_preview_remove_button.connect('clicked', lambda button : dialogs.remove_attached_file(self, button.get_name()))
self.add_chat_button.connect("clicked", lambda button : self.new_chat())
self.attachment_button.connect("clicked", lambda button, file_filter=self.file_filter_attachments: dialogs.attach_file(self, file_filter))
self.create_model_name.get_delegate().connect("insert-text", self.check_alphanumeric)
self.create_model_name.get_delegate().connect("insert-text", lambda *_ : self.check_alphanumeric(*_, ['-', '.', '_']))
self.remote_connection_entry.connect("entry-activated", lambda entry : entry.set_css_classes([]))
self.remote_connection_switch.connect("notify", lambda pspec, user_data : self.connection_switched())
self.background_switch.connect("notify", lambda pspec, user_data : self.switch_run_on_background())
self.setup_model_dropdown()
self.set_focus(self.message_text_view)
if os.path.exists(os.path.join(self.config_dir, "server.json")):
with open(os.path.join(self.config_dir, "server.json"), "r", encoding="utf-8") as f:
data = json.load(f)
@@ -1719,17 +1866,17 @@ Generate a title following these rules:
self.remote_connection_entry.set_text(self.remote_url)
self.remote_bearer_token_entry.set_text(self.remote_bearer_token)
if self.run_remote:
connection_handler.bearer_token = self.remote_bearer_token
connection_handler.url = self.remote_url
connection_handler.BEARER_TOKEN = self.remote_bearer_token
connection_handler.URL = self.remote_url
self.remote_connection_switch.set_active(True)
else:
connection_handler.bearer_token = None
connection_handler.BEARER_TOKEN = None
self.remote_connection_switch.set_active(False)
connection_handler.url = f"http://127.0.0.1:{local_instance.port}"
connection_handler.URL = f"http://127.0.0.1:{local_instance.port}"
local_instance.start()
else:
local_instance.start()
connection_handler.url = f"http://127.0.0.1:{local_instance.port}"
connection_handler.URL = f"http://127.0.0.1:{local_instance.port}"
self.welcome_dialog.present(self)
if self.verify_connection() is False:
self.connection_error()

View File

@@ -12,13 +12,14 @@
<property name="title">Alpaca</property>
<child>
<object class="AdwBreakpoint">
<condition>max-width: 800sp</condition>
<condition>max-width: 690sp</condition>
<setter object="split_view_overlay" property="collapsed">true</setter>
</object>
</child>
<property name="content">
<object class="AdwOverlaySplitView" id="split_view_overlay">
<property name="show-sidebar" bind-source="show_sidebar_button" bind-property="active" bind-flags="sync-create"/>
<property name="sidebar-width-fraction">0.4</property>
<property name="sidebar">
<object class="AdwToolbarView">
<child type="top">
@@ -75,30 +76,84 @@
<property name="orientation">0</property>
<property name="spacing">12</property>
<child>
<object class="GtkDropDown" id="model_drop_down">
<signal name="notify" handler="verify_if_image_can_be_used"/>
<property name="width-request">260</property>
<property name="enable-search">true</property>
<property name="tooltip-text">Select Model</property>
<property name="model">
<object class="GtkStringList" id="model_string_list">
<items>
</items>
<object class="GtkMenuButton" id="model_selector_button">
<property name="tooltip-text" translatable="yes">Select Model</property>
<property name="child">
<object class="GtkBox">
<property name="spacing">10</property>
<child>
<object class="GtkLabel">
<property name="label" translatable="yes">Select a Model</property>
<property name="ellipsize">2</property>
</object>
</child>
<child>
<object class="GtkImage">
<property name="icon-name">down-symbolic</property>
</object>
</child>
</object>
</property>
<property name="halign">1</property>
<style>
<class name="raised"/>
</style>
<property name="popover">
<object class="GtkPopover" id="model_popover">
<style>
<class name="model_popover"/>
</style>
<property name="has-arrow">false</property>
<child>
<object class="GtkBox">
<property name="orientation">1</property>
<property name="spacing">5</property>
<child>
<object class="GtkButton">
<property name="child">
<object class="GtkLabel">
<property name="label" translatable="yes">Manage Models</property>
<property name="justify">left</property>
<property name="halign">1</property>
</object>
</property>
<property name="hexpand">true</property>
<property name="tooltip-text" translatable="yes">Manage Models</property>
<property name="action-name">app.manage_models</property>
<signal name="clicked" handler="close_model_popup"/>
<style>
<class name="flat"/>
<class name="manage_models_button"/>
</style>
</object>
</child>
<child>
<object class="GtkSeparator"/>
</child>
<child>
<object class="GtkScrolledWindow">
<property name="max-content-height">300</property>
<property name="propagate-natural-width">true</property>
<property name="propagate-natural-height">true</property>
<child>
<object class="GtkListBox" id="model_list_box">
<property name="hexpand">true</property>
<style>
<class name="navigation-sidebar"/>
<class name="model_list_box"/>
</style>
<signal name="row-selected" handler="change_model"/>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</property>
</object>
</child>
<!--<child>
<object class="GtkButton" id="manage_models_button">
<signal name="clicked" handler="manage_models_button_activate"/>
<property name="tooltip-text" translatable="yes">Manage Models</property>
<child>
<object class="AdwButtonContent">
<property name="icon-name">brain-augemnted-symbolic</property>
</object>
</child>
</object>
</child>-->
</object>
</property>
<child type="end">
@@ -215,6 +270,10 @@
<property name="top-margin">10</property>
<property name="bottom-margin">10</property>
<property name="hexpand">true</property>
<property name="input-hints">spellcheck</property>
<accessibility>
<property name="label" translatable="yes">Message text box</property>
</accessibility>
</object>
</child>
</object>
@@ -442,6 +501,9 @@
</object>
<object class="AdwDialog" id="manage_models_dialog">
<accessibility>
<property name="label" translatable="yes">Manage models dialog</property>
</accessibility>
<property name="can-close">true</property>
<property name="width-request">400</property>
<property name="height-request">600</property>
@@ -476,12 +538,18 @@
</child>
<child type="top">
<object class="GtkSearchBar" id="model_searchbar">
<accessibility>
<property name="label" translatable="yes">Model search bar</property>
</accessibility>
<property name="key-capture-widget">AlpacaWindow</property>
<child>
<object class="GtkSearchEntry" id="searchentry">
<signal name="search-changed" handler="model_search_changed"/>
<property name="search-delay">100</property>
<property name="placeholder-text" translatable="yes">Search models</property>
<accessibility>
<property name="label" translatable="yes">Search models</property>
</accessibility>
</object>
</child>
</object>
@@ -517,7 +585,7 @@
</child>
<child>
<object class="GtkListBox" id="available_model_list_box">
<property name="selection-mode">single</property>
<property name="selection-mode">none</property>
<style>
<class name="boxed-list"/>
</style>
@@ -580,7 +648,7 @@
<child>
<object class="GtkListBox" id="model_tag_list_box">
<property name="valign">1</property>
<property name="selection-mode">single</property>
<property name="selection-mode">none</property>
<style>
<class name="boxed-list"/>
</style>
@@ -738,9 +806,12 @@
</object>
<object class="AdwDialog" id="file_preview_dialog">
<accessibility>
<property name="label" translatable="yes">File preview dialog</property>
</accessibility>
<property name="can-close">true</property>
<property name="width-request">450</property>
<property name="height-request">450</property>
<property name="width-request">400</property>
<property name="height-request">600</property>
<child>
<object class="AdwToolbarView">
<child type="top">
@@ -805,8 +876,8 @@
<object class="AdwDialog" id="welcome_dialog">
<property name="can-close">false</property>
<property name="width-request">450</property>
<property name="height-request">450</property>
<property name="width-request">400</property>
<property name="height-request">600</property>
<child>
<object class="AdwToolbarView">
<child type="bottom">
@@ -885,141 +956,6 @@
<property name="description" translatable="yes">Alpaca and its developers are not liable for any damages to devices or software resulting from the execution of code generated by an AI model. Please exercise caution and review the code carefully before running it.</property>
</object>
</child>
<child>
<object class="AdwStatusPage">
<property name="hexpand">true</property>
<property name="vexpand">true</property>
<property name="margin-top">12</property>
<property name="margin-bottom">12</property>
<property name="margin-start">12</property>
<property name="margin-end">12</property>
<property name="title" translatable="yes">Featured Models</property>
<property name="description" translatable="yes">Alpaca works locally on your device, to start chatting you'll need an AI model, you can either pull models from this list or the 'Manage Models' menu later.
By downloading any model you accept their license agreement available on the model's website.
</property>
<child>
<object class="GtkListBox">
<property name="selection-mode">none</property>
<style>
<class name="boxed-list"/>
</style>
<child>
<object class="AdwActionRow">
<property name="title" translatable="no">Llama3</property>
<property name="subtitle" translatable="yes">Built by Meta</property>
<child type="suffix">
<object class="GtkButton">
<signal name="clicked" handler="link_button_handler"/>
<property name="vexpand">false</property>
<property name="icon-name">globe-symbolic</property>
<property name="valign">3</property>
<property name="name">https://ollama.com/library/llama3</property>
<property name="tooltip-text">https://ollama.com/library/llama3</property>
</object>
</child>
<child type="suffix">
<object class="GtkButton">
<signal name="clicked" handler="pull_featured_model"/>
<property name="vexpand">false</property>
<property name="icon-name">folder-download-symbolic</property>
<property name="valign">3</property>
<property name="tooltip-text">Pull 'Llama3 (latest)'</property>
<style>
<class name="accent"/>
</style>
</object>
</child>
</object>
</child>
<child>
<object class="AdwActionRow">
<property name="title" translatable="no">Gemma2</property>
<property name="subtitle" translatable="yes">Built by Google DeepMind</property>
<child type="suffix">
<object class="GtkButton">
<signal name="clicked" handler="link_button_handler"/>
<property name="vexpand">false</property>
<property name="icon-name">globe-symbolic</property>
<property name="valign">3</property>
<property name="name">https://ollama.com/library/gemma2</property>
<property name="tooltip-text">https://ollama.com/library/gemma2</property>
</object>
</child>
<child type="suffix">
<object class="GtkButton">
<signal name="clicked" handler="pull_featured_model"/>
<property name="vexpand">false</property>
<property name="icon-name">folder-download-symbolic</property>
<property name="valign">3</property>
<property name="tooltip-text">Pull 'Gemma2 (latest)'</property>
<style>
<class name="accent"/>
</style>
</object>
</child>
</object>
</child>
<child>
<object class="AdwActionRow">
<property name="title" translatable="no">Phi3</property>
<property name="subtitle" translatable="yes">Built by Microsoft</property>
<child type="suffix">
<object class="GtkButton">
<signal name="clicked" handler="link_button_handler"/>
<property name="vexpand">false</property>
<property name="icon-name">globe-symbolic</property>
<property name="valign">3</property>
<property name="name">https://ollama.com/library/phi3</property>
<property name="tooltip-text">https://ollama.com/library/phi3</property>
</object>
</child>
<child type="suffix">
<object class="GtkButton">
<signal name="clicked" handler="pull_featured_model"/>
<property name="vexpand">false</property>
<property name="icon-name">folder-download-symbolic</property>
<property name="valign">3</property>
<property name="tooltip-text">Pull 'Phi3 (latest)'</property>
<style>
<class name="accent"/>
</style>
</object>
</child>
</object>
</child>
<child>
<object class="AdwActionRow">
<property name="title" translatable="no">Llava</property>
<property name="subtitle" translatable="yes">Multimodal AI with image recognition</property>
<child type="suffix">
<object class="GtkButton">
<signal name="clicked" handler="link_button_handler"/>
<property name="vexpand">false</property>
<property name="icon-name">globe-symbolic</property>
<property name="valign">3</property>
<property name="name">https://ollama.com/library/llava</property>
<property name="tooltip-text">https://ollama.com/library/llava</property>
</object>
</child>
<child type="suffix">
<object class="GtkButton">
<signal name="clicked" handler="pull_featured_model"/>
<property name="vexpand">false</property>
<property name="icon-name">folder-download-symbolic</property>
<property name="valign">3</property>
<property name="tooltip-text">Pull 'Llava (latest)'</property>
<style>
<class name="accent"/>
</style>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
@@ -1054,34 +990,52 @@ By downloading any model you accept their license agreement available on the mod
</section>
</menu>
<menu id="secondary_menu">
<item>
<attribute name="label" translatable="yes">Rename Chat</attribute>
<attribute name="action">app.rename_current_chat</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Export Chat</attribute>
<attribute name="action">app.export_current_chat</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Clear Chat</attribute>
<attribute name="action">app.clear</attribute>
</item>
</menu>
<menu id="chat_right_click_menu">
<section>
<item>
<attribute name="label" translatable="yes">Rename Chat</attribute>
<attribute name="action">app.rename_current_chat</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Duplicate Chat</attribute>
<attribute name="action">app.duplicate_current_chat</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Export Chat</attribute>
<attribute name="action">app.export_current_chat</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Clear Chat</attribute>
<attribute name="action">app.clear</attribute>
</item>
</section>
<section>
<item>
<attribute name="label" translatable="yes">Delete Chat</attribute>
<attribute name="action">app.delete_chat</attribute>
<attribute name="action">app.delete_current_chat</attribute>
</item>
</section>
</menu>
<menu id="chat_right_click_menu">
<section>
<item>
<attribute name="label" translatable="yes">Rename Chat</attribute>
<attribute name="action">app.rename_chat</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Duplicate Chat</attribute>
<attribute name="action">app.duplicate_chat</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Export Chat</attribute>
<attribute name="action">app.export_chat</attribute>
</item>
</section>
<section>
<item>
<attribute name="label" translatable="yes">Delete Chat</attribute>
<attribute name="action">app.delete_chat</attribute>
</item>
</section>
</menu>
<menu id="create_model_menu">
<section>

View File

@@ -15,4 +15,6 @@ msgmerge --no-fuzzy-matching -U po/nb_NO.po po/alpaca.pot
echo "Updating Bengali"
msgmerge --no-fuzzy-matching -U po/bn.po po/alpaca.pot
echo "Updating Simplified Chinese"
msgmerge --no-fuzzy-matching -U po/zh_CN.po po/alpaca.pot
msgmerge --no-fuzzy-matching -U po/zh_CN.po po/alpaca.pot
echo "Updating Hindi"
msgmerge --no-fuzzy-matching -U po/hi.po po/alpaca.pot