Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
91fc745d2f | ||
|
|
a719783cce | ||
|
|
766636718a | ||
|
|
59d2430e2f | ||
|
|
074b4dc643 | ||
|
|
92dd56b0c4 | ||
|
|
25c414faa3 | ||
|
|
dc2df81c75 | ||
|
|
ddc50ce621 | ||
|
|
701fae92a0 | ||
|
|
f3d22219b4 | ||
|
|
0027cf7c59 | ||
|
|
b6c45fc346 | ||
|
|
e6f2902bd1 | ||
|
|
1190e367bb | ||
|
|
d619f55ff2 | ||
|
|
605d837716 | ||
|
|
3aa57c6d2f | ||
|
|
1cf2f04b06 | ||
|
|
780de2b753 | ||
|
|
e19511469d | ||
|
|
bedf1a9bc5 |
17
README.md
17
README.md
@@ -22,7 +22,8 @@ Alpaca is an [Ollama](https://github.com/ollama/ollama) client where you can man
|
|||||||
- Delete messages
|
- Delete messages
|
||||||
|
|
||||||
## Future features!
|
## Future features!
|
||||||
- Document recognition
|
- Document recognition (Probably only Markdown because it's really easy for AI to read)
|
||||||
|
- YouTube recognition (Ask questions about a YouTube video using the transcript)
|
||||||
- Edit messages
|
- Edit messages
|
||||||
- Snap Package (maybe)
|
- Snap Package (maybe)
|
||||||
|
|
||||||
@@ -40,6 +41,20 @@ Login to Ollama instance | Chatting with models | Managing
|
|||||||
2. Download the latest flatpak package
|
2. Download the latest flatpak package
|
||||||
3. Open it
|
3. Open it
|
||||||
|
|
||||||
|
## Ollama session tips
|
||||||
|
|
||||||
|
### Migrate Ollama session to Alpaca's integrated session
|
||||||
|
For now Alpaca doesn't offer a way to do this from the GUI but it's really simple, when you are using Ollama it creates a folder in the home directory called `.ollama`, you just need to move that folder to `~/.var/app/com.jeffser.Alpaca/data/`
|
||||||
|
|
||||||
|
### Change the port of the integrated Ollama instance
|
||||||
|
Go to `~/.var/app/com.jeffser.Alpaca/config/server.json` and change the `"local_port"` value, by default it is `11435`.
|
||||||
|
|
||||||
|
### Backup all the chats
|
||||||
|
The chat data is located in `~/.var/app/com.jeffser.Alpaca/config/chats.json` you can copy that file wherever you want to.
|
||||||
|
|
||||||
|
### Force showing the welcome dialog
|
||||||
|
To do that you just need to delete the file `~/.var/app/com.jeffser.Alpaca/config/server.json`, this won't affect your saved chats or models.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Thanks
|
## Thanks
|
||||||
|
|||||||
@@ -5,15 +5,16 @@
|
|||||||
<project_license>GPL-3.0-or-later</project_license>
|
<project_license>GPL-3.0-or-later</project_license>
|
||||||
<launchable type="desktop-id">com.jeffser.Alpaca.desktop</launchable>
|
<launchable type="desktop-id">com.jeffser.Alpaca.desktop</launchable>
|
||||||
<name>Alpaca</name>
|
<name>Alpaca</name>
|
||||||
<summary>Chat with local AI models</summary>
|
<summary>Chat with local AI models powered by Ollama</summary>
|
||||||
<description>
|
<description>
|
||||||
<p>An Ollama client</p>
|
<p>An Ollama client</p>
|
||||||
<p>Features</p>
|
<p>Features</p>
|
||||||
<ul>
|
<ul>
|
||||||
|
<li>Built in Ollama instance</li>
|
||||||
<li>Talk to multiple models in the same conversation</li>
|
<li>Talk to multiple models in the same conversation</li>
|
||||||
<li>Pull and delete models from the app</li>
|
<li>Pull and delete models from the app</li>
|
||||||
<li>Have multiple conversations</li>
|
<li>Have multiple conversations</li>
|
||||||
<li>Image recognition (Only available with LLaVA Model)</li>
|
<li>Image recognition (Only available with compatible models)</li>
|
||||||
<li>Import and export chats</li>
|
<li>Import and export chats</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p>Disclaimer</p>
|
<p>Disclaimer</p>
|
||||||
@@ -36,6 +37,8 @@
|
|||||||
</requires>
|
</requires>
|
||||||
<recommends>
|
<recommends>
|
||||||
<control>keyboard</control>
|
<control>keyboard</control>
|
||||||
|
<control>pointing</control>
|
||||||
|
<control>touch</control>
|
||||||
</recommends>
|
</recommends>
|
||||||
<branding>
|
<branding>
|
||||||
<color type="primary" scheme_preference="light">#8cdef5</color>
|
<color type="primary" scheme_preference="light">#8cdef5</color>
|
||||||
@@ -60,6 +63,28 @@
|
|||||||
<url type="homepage">https://github.com/Jeffser/Alpaca</url>
|
<url type="homepage">https://github.com/Jeffser/Alpaca</url>
|
||||||
<url type="donation">https://github.com/sponsors/Jeffser</url>
|
<url type="donation">https://github.com/sponsors/Jeffser</url>
|
||||||
<releases>
|
<releases>
|
||||||
|
<release version="0.8.6" date="2024-05-26">
|
||||||
|
<url type="details">https://github.com/Jeffser/Alpaca/releases/tag/0.8.5</url>
|
||||||
|
<description>
|
||||||
|
<p>Quick fixes</p>
|
||||||
|
<ul>
|
||||||
|
<li>Fixed: Scroll when message is received</li>
|
||||||
|
<li>Fixed: Content doesn't change when creating a new chat</li>
|
||||||
|
<li>Added 'Featured Models' page on welcome dialog</li>
|
||||||
|
</ul>
|
||||||
|
</description>
|
||||||
|
</release>
|
||||||
|
<release version="0.8.5" date="2024-05-26">
|
||||||
|
<url type="details">https://github.com/Jeffser/Alpaca/releases/tag/0.8.5</url>
|
||||||
|
<description>
|
||||||
|
<p>Nice Update</p>
|
||||||
|
<ul>
|
||||||
|
<li>UI tweaks (Thanks Nokse22)</li>
|
||||||
|
<li>General optimizations</li>
|
||||||
|
<li>Metadata fixes</li>
|
||||||
|
</ul>
|
||||||
|
</description>
|
||||||
|
</release>
|
||||||
<release version="0.8.1" date="2024-05-24">
|
<release version="0.8.1" date="2024-05-24">
|
||||||
<url type="details">https://github.com/Jeffser/Alpaca/releases/tag/0.8.1</url>
|
<url type="details">https://github.com/Jeffser/Alpaca/releases/tag/0.8.1</url>
|
||||||
<description>
|
<description>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
project('Alpaca',
|
project('Alpaca',
|
||||||
version: '0.8.1',
|
version: '0.8.6',
|
||||||
meson_version: '>= 0.62.0',
|
meson_version: '>= 0.62.0',
|
||||||
default_options: [ 'warning_level=2', 'werror=false', ],
|
default_options: [ 'warning_level=2', 'werror=false', ],
|
||||||
)
|
)
|
||||||
|
|||||||
688
po/alpaca.pot
688
po/alpaca.pot
File diff suppressed because it is too large
Load Diff
838
po/pt_BR.po
838
po/pt_BR.po
File diff suppressed because it is too large
Load Diff
843
po/pt_BR.po~
843
po/pt_BR.po~
File diff suppressed because it is too large
Load Diff
708
po/ru.po
708
po/ru.po
@@ -2,8 +2,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: \n"
|
"Project-Id-Version: \n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2024-05-24 18:50-0600\n"
|
"POT-Creation-Date: 2024-05-25 10:21+0800\n"
|
||||||
"PO-Revision-Date: 2024-05-22 19:33+0800\n"
|
"PO-Revision-Date: 2024-05-25 10:44+0800\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: \n"
|
"Language-Team: \n"
|
||||||
"Language: ru_RU\n"
|
"Language: ru_RU\n"
|
||||||
@@ -14,752 +14,312 @@ msgstr ""
|
|||||||
"X-Poedit-Basepath: ../src\n"
|
"X-Poedit-Basepath: ../src\n"
|
||||||
"X-Poedit-SearchPath-0: .\n"
|
"X-Poedit-SearchPath-0: .\n"
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.desktop.in:3
|
#: gtk/help-overlay.ui:11
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:7
|
msgctxt "shortcut window"
|
||||||
#, fuzzy
|
msgid "General"
|
||||||
msgid "Alpaca"
|
msgstr "Общие"
|
||||||
msgstr "О Программе"
|
|
||||||
|
#: gtk/help-overlay.ui:14
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:8
|
msgctxt "shortcut window"
|
||||||
#, fuzzy
|
msgid "Show Shortcuts"
|
||||||
msgid "Chat with local AI models"
|
msgstr "Показывать комбинации клавиш"
|
||||||
msgstr "Не удалось перечислить локальные модели"
|
|
||||||
|
#: gtk/help-overlay.ui:20
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:10
|
msgctxt "shortcut window"
|
||||||
#, fuzzy
|
msgid "Quit"
|
||||||
msgid "An Ollama client"
|
msgstr "Выйти"
|
||||||
msgstr "Веб-сайт Ollama"
|
|
||||||
|
#: window.py:57 window.py:783
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:11
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:286
|
|
||||||
msgid "Features"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:13
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:288
|
|
||||||
msgid "Talk to multiple models in the same conversation"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:14
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:289
|
|
||||||
msgid "Pull and delete models from the app"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:15
|
|
||||||
msgid "Have multiple conversations"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:16
|
|
||||||
msgid "Image recognition (Only available with LLaVA Model)"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:17
|
|
||||||
#, fuzzy
|
|
||||||
msgid "Import and export chats"
|
|
||||||
msgstr "Импорт чата"
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:19 src/window.ui:432
|
|
||||||
msgid "Disclaimer"
|
|
||||||
msgstr "Отказ от ответственности"
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:20
|
|
||||||
msgid ""
|
|
||||||
"This project is not affiliated at all with Ollama, I'm not responsible for "
|
|
||||||
"any damages to your device or software caused by running code given by any "
|
|
||||||
"models."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:23
|
|
||||||
msgid "Jeffry Samuel Eduarte Rojas"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:47
|
|
||||||
msgid "A conversation showing code highlight"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:51
|
|
||||||
msgid "A conversation involving multiple models"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:55
|
|
||||||
#, fuzzy
|
|
||||||
msgid "Managing models"
|
|
||||||
msgstr "Управление моделями"
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:66
|
|
||||||
msgid "Quick fix"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:68
|
|
||||||
msgid "Updated Spanish translation"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:69
|
|
||||||
msgid "Added compatibility for PNG"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:76
|
|
||||||
#, fuzzy
|
|
||||||
msgid "New Update"
|
|
||||||
msgstr "Новый Чат"
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:78
|
|
||||||
msgid "Updated model list"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:79
|
|
||||||
msgid "Added image recognition to more models"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:80
|
|
||||||
msgid "Added Brazilian Portuguese translation (Thanks Daimaar Stein)"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:81
|
|
||||||
msgid "Refined the general UI (Thanks Nokse22)"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:82
|
|
||||||
msgid "Added 'delete message' feature"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:83
|
|
||||||
msgid ""
|
|
||||||
"Added metadata so that software distributors know that the app is compatible "
|
|
||||||
"with mobile"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:84
|
|
||||||
msgid ""
|
|
||||||
"Changed 'send' shortcut to just the return/enter key (to add a new line use "
|
|
||||||
"shift+return)"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:91
|
|
||||||
msgid "Bug Fixes"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:93
|
|
||||||
msgid "Fixed: Minor spelling mistake"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:94
|
|
||||||
msgid "Added 'mobile' as a supported form factor"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:95
|
|
||||||
msgid "Fixed: 'Connection Error' dialog not working properly"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:96
|
|
||||||
msgid "Fixed: App might freeze randomly on startup"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:97
|
|
||||||
msgid "Changed 'chats' label on sidebar for 'Alpaca'"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:104
|
|
||||||
msgid "Cool Update"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:106
|
|
||||||
msgid "Better design for chat window"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:107
|
|
||||||
msgid "Better design for chat sidebar"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:108
|
|
||||||
#, fuzzy
|
|
||||||
msgid "Fixed remote connections"
|
|
||||||
msgstr "Использовать удаленное подключение"
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:109
|
|
||||||
msgid "Fixed Ollama restarting in loop"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:110
|
|
||||||
msgid "Other cool backend stuff"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:117
|
|
||||||
msgid "Huge Update"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:119
|
|
||||||
msgid "Added Ollama as part of Alpaca, Ollama will run in a sandbox"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:120
|
|
||||||
msgid "Added option to connect to remote instances (how it worked before)"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:121
|
|
||||||
msgid "Added option to import and export chats"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:122
|
|
||||||
msgid "Added option to run Alpaca with Ollama in the background"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:123
|
|
||||||
msgid "Added preferences dialog"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:124
|
|
||||||
msgid "Changed the welcome dialog"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:126
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:143
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:155
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:174
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:195
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:211
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:227
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:241
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:251
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:269
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:291
|
|
||||||
msgid "Please report any errors to the issues page, thank you."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:134
|
|
||||||
msgid "Yet Another Daily Update"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:136
|
|
||||||
msgid "Added better UI for 'Manage Models' dialog"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:137
|
|
||||||
msgid "Added better UI for the chat sidebar"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:138
|
|
||||||
msgid ""
|
|
||||||
"Replaced model description with a button to open Ollama's website for the "
|
|
||||||
"model"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:139
|
|
||||||
msgid "Added myself to the credits as the spanish translator"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:140
|
|
||||||
msgid "Using XDG properly to get config folder"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:141
|
|
||||||
msgid "Update for translations"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:151
|
|
||||||
msgid "Quick Fix"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:153
|
|
||||||
msgid "The last update had some mistakes in the description of the update"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:163
|
|
||||||
msgid "Another Daily Update"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:165
|
|
||||||
msgid "Added full Spanish translation"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:166
|
|
||||||
msgid "Added support for background pulling of multiple models"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:167
|
|
||||||
msgid "Added interrupt button"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:168
|
|
||||||
#, fuzzy
|
|
||||||
msgid "Added basic shortcuts"
|
|
||||||
msgstr "Показывать ярлыки"
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:169
|
|
||||||
msgid "Better translation support"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:170
|
|
||||||
msgid ""
|
|
||||||
"User can now leave chat name empty when creating a new one, it will add a "
|
|
||||||
"placeholder name"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:171
|
|
||||||
msgid "Better scalling for different window sizes"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:172
|
|
||||||
msgid "Fixed: Can't close app if first time setup fails"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:182
|
|
||||||
msgid "Really Big Update"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:184
|
|
||||||
msgid "Added multiple chats support!"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:185
|
|
||||||
msgid "Added Pango Markup support (bold, list, title, subtitle, monospace)"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:186
|
|
||||||
msgid "Added autoscroll if the user is at the bottom of the chat"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:187
|
|
||||||
msgid "Added support for multiple tags on a single model"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:188
|
|
||||||
msgid "Added better model management dialog"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:189
|
|
||||||
msgid "Added loading spinner when sending message"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:190
|
|
||||||
msgid "Added notifications if app is not active and a model pull finishes"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:191
|
|
||||||
msgid "Added new symbolic icon"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:192
|
|
||||||
msgid "Added frame to message textview widget"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:193
|
|
||||||
msgid "Fixed \"code blocks shouldn't be editable\""
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:203
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:260
|
|
||||||
msgid "Big Update"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:205
|
|
||||||
msgid "Added code highlighting"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:206
|
|
||||||
msgid "Added image recognition (llava model)"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:207
|
|
||||||
msgid "Added multiline prompt"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:208
|
|
||||||
msgid "Fixed some small bugs"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:209
|
|
||||||
msgid "General optimization"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:219
|
|
||||||
msgid "Fixes and features"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:221
|
|
||||||
msgid "Russian translation (thanks github/alexkdeveloper)"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:222
|
|
||||||
msgid "Fixed: Cannot close app on first setup"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:223
|
|
||||||
msgid "Fixed: Brand colors for Flathub"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:224
|
|
||||||
msgid "Fixed: App description"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:225
|
|
||||||
msgid "Fixed: Only show 'save changes dialog' when you actually change the url"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:235
|
|
||||||
msgid "0.2.2 Bug fixes"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:237
|
|
||||||
msgid "Toast messages appearing behind dialogs"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:238
|
|
||||||
msgid "Local model list not updating when changing servers"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:239
|
|
||||||
msgid "Closing the setup dialog closes the whole app"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:249
|
|
||||||
msgid "0.2.1 Data saving fix"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:250
|
|
||||||
msgid ""
|
|
||||||
"The app didn't save the config files and chat history to the right "
|
|
||||||
"directory, this is now fixed"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:259
|
|
||||||
msgid "0.2.0"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:261
|
|
||||||
#, fuzzy
|
|
||||||
msgid "New Features"
|
|
||||||
msgstr "Новый Чат"
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:263
|
|
||||||
msgid "Restore chat after closing the app"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:264
|
|
||||||
#, fuzzy
|
|
||||||
msgid "A button to clear the chat"
|
|
||||||
msgstr "Вы уверены, что хотите очистить чат?"
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:265
|
|
||||||
msgid "Fixed multiple bugs involving how messages are shown"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:266
|
|
||||||
msgid "Added welcome dialog"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:267
|
|
||||||
msgid "More stability"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:277
|
|
||||||
msgid "0.1.2 Quick fixes"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:278
|
|
||||||
msgid ""
|
|
||||||
"This release fixes some metadata needed to have a proper Flatpak application"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:284
|
|
||||||
msgid "0.1.1 Stable Release"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:285
|
|
||||||
msgid "This is the first public version of Alpaca"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: src/window.py:57 src/window.py:783
|
|
||||||
msgid "New Chat"
|
msgid "New Chat"
|
||||||
msgstr "Новый Чат"
|
msgstr "Новый Чат"
|
||||||
|
|
||||||
#: src/window.py:102
|
#: window.py:102
|
||||||
msgid "An error occurred"
|
msgid "An error occurred"
|
||||||
msgstr "Произошла ошибка"
|
msgstr "Произошла ошибка"
|
||||||
|
|
||||||
#: src/window.py:103
|
#: window.py:103
|
||||||
msgid "Failed to connect to server"
|
msgid "Failed to connect to server"
|
||||||
msgstr "Не удалось подключиться к серверу"
|
msgstr "Не удалось подключиться к серверу"
|
||||||
|
|
||||||
#: src/window.py:104
|
#: window.py:104
|
||||||
msgid "Could not list local models"
|
msgid "Could not list local models"
|
||||||
msgstr "Не удалось перечислить локальные модели"
|
msgstr "Не удалось перечислить локальные модели"
|
||||||
|
|
||||||
#: src/window.py:105
|
#: window.py:105
|
||||||
msgid "Could not delete model"
|
msgid "Could not delete model"
|
||||||
msgstr "Не удалось удалить модель"
|
msgstr "Не удалось удалить модель"
|
||||||
|
|
||||||
#: src/window.py:106
|
#: window.py:106
|
||||||
msgid "Could not pull model"
|
msgid "Could not pull model"
|
||||||
msgstr "Не удалось извлечь модель"
|
msgstr "Не удалось извлечь модель"
|
||||||
|
|
||||||
#: src/window.py:107
|
#: window.py:107
|
||||||
msgid "Cannot open image"
|
msgid "Cannot open image"
|
||||||
msgstr "Не удается открыть изображение"
|
msgstr "Не удается открыть изображение"
|
||||||
|
|
||||||
#: src/window.py:108
|
#: window.py:108
|
||||||
msgid "Cannot delete chat because it's the only one left"
|
msgid "Cannot delete chat because it's the only one left"
|
||||||
msgstr "Не удается удалить чат, потому что он единственный оставшийся"
|
msgstr "Не удается удалить чат, потому что он единственный оставшийся"
|
||||||
|
|
||||||
#: src/window.py:109
|
#: window.py:109
|
||||||
msgid "There was an error with the local Ollama instance, so it has been reset"
|
msgid "There was an error with the local Ollama instance, so it has been reset"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Произошла ошибка с локальным экземпляром Ollama, поэтому он был сброшен"
|
"Произошла ошибка с локальным экземпляром Ollama, поэтому он был сброшен"
|
||||||
|
|
||||||
#: src/window.py:112
|
#: window.py:112
|
||||||
msgid "Please select a model before chatting"
|
msgid "Please select a model before chatting"
|
||||||
msgstr "Пожалуйста, выберите модель перед началом общения"
|
msgstr "Пожалуйста, выберите модель перед началом общения"
|
||||||
|
|
||||||
#: src/window.py:113
|
#: window.py:113
|
||||||
msgid "Chat cannot be cleared while receiving a message"
|
msgid "Chat cannot be cleared while receiving a message"
|
||||||
msgstr "Чат не может быть удален при получении сообщения"
|
msgstr "Чат не может быть удален при получении сообщения"
|
||||||
|
|
||||||
#: src/window.py:114
|
#: window.py:114
|
||||||
msgid "That tag is already being pulled"
|
msgid "That tag is already being pulled"
|
||||||
msgstr "Этот тег уже удален"
|
msgstr "Этот тег уже удален"
|
||||||
|
|
||||||
#: src/window.py:115
|
#: window.py:115
|
||||||
#, fuzzy
|
|
||||||
msgid "That tag has been pulled already"
|
msgid "That tag has been pulled already"
|
||||||
msgstr "Этот тег уже был удален"
|
msgstr "Этот тег уже был удален"
|
||||||
|
|
||||||
#: src/window.py:118
|
#: window.py:118
|
||||||
msgid "Model deleted successfully"
|
msgid "Model deleted successfully"
|
||||||
msgstr "Модель успешно удалена"
|
msgstr "Модель успешно удалена"
|
||||||
|
|
||||||
#: src/window.py:119
|
#: window.py:119
|
||||||
msgid "Model pulled successfully"
|
msgid "Model pulled successfully"
|
||||||
msgstr "Модель успешно извлечена"
|
msgstr "Модель успешно извлечена"
|
||||||
|
|
||||||
#: src/window.py:120
|
#: window.py:120
|
||||||
msgid "Chat exported successfully"
|
msgid "Chat exported successfully"
|
||||||
msgstr "Чат успешно экспортирован"
|
msgstr "Чат успешно экспортирован"
|
||||||
|
|
||||||
#: src/window.py:121
|
#: window.py:121
|
||||||
msgid "Chat imported successfully"
|
msgid "Chat imported successfully"
|
||||||
msgstr "Чат успешно импортирован"
|
msgstr "Чат успешно импортирован"
|
||||||
|
|
||||||
#: src/window.py:476
|
#: window.py:476
|
||||||
msgid "Task Complete"
|
msgid "Task Complete"
|
||||||
msgstr "Задача выполнена"
|
msgstr "Задача выполнена"
|
||||||
|
|
||||||
#: src/window.py:476
|
#: window.py:476
|
||||||
msgid "Model '{}' pulled successfully."
|
msgid "Model '{}' pulled successfully."
|
||||||
msgstr "Модель '{}' успешно извлечена."
|
msgstr "Модель '{}' успешно извлечена."
|
||||||
|
|
||||||
#: src/window.py:481
|
#: window.py:481
|
||||||
msgid "Pull Model Error"
|
msgid "Pull Model Error"
|
||||||
msgstr "Ошибка Извлечения Модели"
|
msgstr "Ошибка Извлечения Модели"
|
||||||
|
|
||||||
#: src/window.py:481
|
#: window.py:481
|
||||||
msgid "Failed to pull model '{}' due to network error."
|
msgid "Failed to pull model '{}' due to network error."
|
||||||
msgstr "Не удалось извлечь модель '{}' из-за сетевой ошибки."
|
msgstr "Не удалось извлечь модель '{}' из-за сетевой ошибки."
|
||||||
|
|
||||||
#: src/window.py:496
|
#: window.py:496
|
||||||
msgid "Stop Model"
|
msgid "Stop Model"
|
||||||
msgstr "Остановить Модель"
|
msgstr "Остановить Модель"
|
||||||
|
|
||||||
#: src/window.py:497
|
#: window.py:497
|
||||||
msgid "Are you sure you want to stop pulling '{}'?"
|
msgid "Are you sure you want to stop pulling '{}'?"
|
||||||
msgstr "Вы уверены, что хотите прекратить извлечение '{}'?"
|
msgstr "Вы уверены, что хотите прекратить извлечение '{}'?"
|
||||||
|
|
||||||
#: src/window.py:500 src/window.py:543 src/window.py:566 src/window.py:638
|
#: window.py:500 window.py:543 window.py:566 window.py:638 window.py:711
|
||||||
#: src/window.py:711 src/window.py:737 src/window.py:769 src/window.py:810
|
#: window.py:737 window.py:769 window.py:810
|
||||||
msgid "Cancel"
|
msgid "Cancel"
|
||||||
msgstr "Отмена"
|
msgstr "Отмена"
|
||||||
|
|
||||||
#: src/window.py:501
|
#: window.py:501
|
||||||
msgid "Stop"
|
msgid "Stop"
|
||||||
msgstr "Стоп"
|
msgstr "Стоп"
|
||||||
|
|
||||||
#: src/window.py:539
|
#: window.py:539
|
||||||
msgid "Delete Model"
|
msgid "Delete Model"
|
||||||
msgstr "Удалить Модель"
|
msgstr "Удалить Модель"
|
||||||
|
|
||||||
#: src/window.py:540 src/window.py:734
|
#: window.py:540 window.py:734
|
||||||
msgid "Are you sure you want to delete '{}'?"
|
msgid "Are you sure you want to delete '{}'?"
|
||||||
msgstr "Вы уверены, что хотите удалить '{}'?"
|
msgstr "Вы уверены, что хотите удалить '{}'?"
|
||||||
|
|
||||||
#: src/window.py:544 src/window.py:738
|
#: window.py:544 window.py:738
|
||||||
msgid "Delete"
|
msgid "Delete"
|
||||||
msgstr "Удалить"
|
msgstr "Удалить"
|
||||||
|
|
||||||
#: src/window.py:561
|
#: window.py:561
|
||||||
msgid "Pull Model"
|
msgid "Pull Model"
|
||||||
msgstr "Извлечение модели"
|
msgstr "Извлечение модели"
|
||||||
|
|
||||||
#: src/window.py:562
|
#: window.py:562
|
||||||
msgid "Please select a tag to pull '{}'"
|
msgid "Please select a tag to pull '{}'"
|
||||||
msgstr "Пожалуйста, выберите тег для извлечения '{}'"
|
msgstr "Пожалуйста, выберите тег для извлечения '{}'"
|
||||||
|
|
||||||
#: src/window.py:567
|
#: window.py:567
|
||||||
msgid "Pull"
|
msgid "Pull"
|
||||||
msgstr "Извлечение"
|
msgstr "Извлечение"
|
||||||
|
|
||||||
#: src/window.py:634 src/window.ui:446
|
#: window.py:634 window.ui:446
|
||||||
msgid "Clear Chat"
|
msgid "Clear Chat"
|
||||||
msgstr "Очистить Чат"
|
msgstr "Очистить Чат"
|
||||||
|
|
||||||
#: src/window.py:635
|
#: window.py:635
|
||||||
msgid "Are you sure you want to clear the chat?"
|
msgid "Are you sure you want to clear the chat?"
|
||||||
msgstr "Вы уверены, что хотите очистить чат?"
|
msgstr "Вы уверены, что хотите очистить чат?"
|
||||||
|
|
||||||
#: src/window.py:639
|
#: window.py:639
|
||||||
msgid "Clear"
|
msgid "Clear"
|
||||||
msgstr "Очистить"
|
msgstr "Очистить"
|
||||||
|
|
||||||
#: src/window.py:707
|
#: window.py:707
|
||||||
msgid "Remove Image"
|
msgid "Remove Image"
|
||||||
msgstr "Удалить Изображение"
|
msgstr "Удалить Изображение"
|
||||||
|
|
||||||
#: src/window.py:708
|
#: window.py:708
|
||||||
msgid "Are you sure you want to remove image?"
|
msgid "Are you sure you want to remove image?"
|
||||||
msgstr "Вы уверены, что хотите удалить изображение?"
|
msgstr "Вы уверены, что хотите удалить изображение?"
|
||||||
|
|
||||||
#: src/window.py:712 src/window.ui:466
|
#: window.py:712 window.ui:466
|
||||||
msgid "Remove"
|
msgid "Remove"
|
||||||
msgstr "Удалить"
|
msgstr "Удалить"
|
||||||
|
|
||||||
#: src/window.py:733
|
#: window.py:733
|
||||||
msgid "Delete Chat"
|
msgid "Delete Chat"
|
||||||
msgstr "Удалить Чат"
|
msgstr "Удалить Чат"
|
||||||
|
|
||||||
#: src/window.py:763
|
#: window.py:763
|
||||||
msgid "Rename Chat"
|
msgid "Rename Chat"
|
||||||
msgstr "Переименовать Чат"
|
msgstr "Переименовать Чат"
|
||||||
|
|
||||||
#: src/window.py:770
|
#: window.py:770
|
||||||
msgid "Rename"
|
msgid "Rename"
|
||||||
msgstr "Переименовать"
|
msgstr "Переименовать"
|
||||||
|
|
||||||
#: src/window.py:791
|
#: window.py:791
|
||||||
msgid "The name '{}' is already in use"
|
msgid "The name '{}' is already in use"
|
||||||
msgstr "Имя '{}' уже используется"
|
msgstr "Имя '{}' уже используется"
|
||||||
|
|
||||||
#: src/window.py:804
|
#: window.py:804
|
||||||
msgid "Create Chat"
|
msgid "Create Chat"
|
||||||
msgstr "Создать Чат"
|
msgstr "Создать Чат"
|
||||||
|
|
||||||
#: src/window.py:811
|
#: window.py:811
|
||||||
msgid "Create"
|
msgid "Create"
|
||||||
msgstr "Создать"
|
msgstr "Создать"
|
||||||
|
|
||||||
#: src/window.py:914
|
#: window.py:914
|
||||||
#, fuzzy
|
|
||||||
msgid "Connection Error"
|
msgid "Connection Error"
|
||||||
msgstr "Удаленное подключение"
|
msgstr "Ошибка Соединения"
|
||||||
|
|
||||||
#: src/window.py:915
|
#: window.py:915
|
||||||
msgid "The remote instance has disconnected"
|
msgid "The remote instance has disconnected"
|
||||||
msgstr ""
|
msgstr "Удаленный экземпляр отключился"
|
||||||
|
|
||||||
#: src/window.py:919
|
#: window.py:919
|
||||||
#, fuzzy
|
|
||||||
msgid "Close Alpaca"
|
msgid "Close Alpaca"
|
||||||
msgstr "Добро пожаловать в Alpaca"
|
msgstr "Закрыть Программу"
|
||||||
|
|
||||||
#: src/window.py:920
|
#: window.py:920
|
||||||
#, fuzzy
|
|
||||||
msgid "Use local instance"
|
msgid "Use local instance"
|
||||||
msgstr "URL-адрес удаленного экземпляра"
|
msgstr "Использовать локальный экземпляр"
|
||||||
|
|
||||||
#: src/window.py:921
|
#: window.py:921
|
||||||
msgid "Connect"
|
msgid "Connect"
|
||||||
msgstr ""
|
msgstr "Подключить"
|
||||||
|
|
||||||
#: src/window.ui:40
|
#: window.ui:40
|
||||||
msgid "New chat"
|
msgid "New chat"
|
||||||
msgstr "Новый чат"
|
msgstr "Новый чат"
|
||||||
|
|
||||||
#: src/window.ui:49
|
#: window.ui:49
|
||||||
msgid "Import chat"
|
msgid "Import chat"
|
||||||
msgstr "Импорт чата"
|
msgstr "Импорт чата"
|
||||||
|
|
||||||
#: src/window.ui:58
|
#: window.ui:58
|
||||||
msgid "Export chat"
|
msgid "Export chat"
|
||||||
msgstr "Экспорт чата"
|
msgstr "Экспорт чата"
|
||||||
|
|
||||||
#: src/window.ui:90
|
#: window.ui:90
|
||||||
msgid "Toggle Sidebar"
|
msgid "Toggle Sidebar"
|
||||||
msgstr "Переключение боковой панели"
|
msgstr "Переключение боковой панели"
|
||||||
|
|
||||||
#: src/window.ui:111 src/window.ui:303
|
#: window.ui:111 window.ui:303
|
||||||
msgid "Manage models"
|
msgid "Manage models"
|
||||||
msgstr "Управление моделями"
|
msgstr "Управление моделями"
|
||||||
|
|
||||||
#: src/window.ui:125
|
#: window.ui:125
|
||||||
msgid "Menu"
|
msgid "Menu"
|
||||||
msgstr "Меню"
|
msgstr "Меню"
|
||||||
|
|
||||||
#: src/window.ui:216
|
#: window.ui:216
|
||||||
msgid "Send"
|
msgid "Send"
|
||||||
msgstr "Отправить"
|
msgstr "Отправить"
|
||||||
|
|
||||||
#: src/window.ui:225
|
#: window.ui:225
|
||||||
msgid "Only available on selected models"
|
msgid "Only available on selected models"
|
||||||
msgstr ""
|
msgstr "Доступно только для некоторых моделей"
|
||||||
|
|
||||||
#: src/window.ui:228
|
#: window.ui:228
|
||||||
msgid "Image"
|
msgid "Image"
|
||||||
msgstr "Изображение"
|
msgstr "Изображение"
|
||||||
|
|
||||||
#: src/window.ui:254 src/window.ui:450 src/window.ui:508
|
#: window.ui:254 window.ui:450 window.ui:508
|
||||||
msgid "Preferences"
|
msgid "Preferences"
|
||||||
msgstr "Настройки"
|
msgstr "Настройки"
|
||||||
|
|
||||||
#: src/window.ui:257 src/window.ui:492
|
#: window.ui:257 window.ui:492
|
||||||
msgid "General"
|
msgid "General"
|
||||||
msgstr "Общие"
|
msgstr "Общие"
|
||||||
|
|
||||||
#: src/window.ui:261
|
#: window.ui:261
|
||||||
msgid "Remote Connection"
|
msgid "Remote Connection"
|
||||||
msgstr "Удаленное подключение"
|
msgstr "Удаленное подключение"
|
||||||
|
|
||||||
#: src/window.ui:262
|
#: window.ui:262
|
||||||
msgid "Manage a remote connection to Ollama"
|
msgid "Manage a remote connection to Ollama"
|
||||||
msgstr "Управление удаленным подключением к Ollama"
|
msgstr "Управление удаленным подключением к Ollama"
|
||||||
|
|
||||||
#: src/window.ui:265
|
#: window.ui:265
|
||||||
msgid "Use remote connection"
|
msgid "Use remote connection"
|
||||||
msgstr "Использовать удаленное подключение"
|
msgstr "Использовать удаленное подключение"
|
||||||
|
|
||||||
#: src/window.ui:270
|
#: window.ui:270
|
||||||
msgid "URL of remote instance"
|
msgid "URL of remote instance"
|
||||||
msgstr "URL-адрес удаленного экземпляра"
|
msgstr "URL-адрес удаленного экземпляра"
|
||||||
|
|
||||||
#: src/window.ui:278
|
#: window.ui:278
|
||||||
msgid "Behavior"
|
msgid "Behavior"
|
||||||
msgstr "Поведение"
|
msgstr "Поведение"
|
||||||
|
|
||||||
#: src/window.ui:279
|
#: window.ui:279
|
||||||
msgid "Manage Alpaca's Behavior"
|
msgid "Manage Alpaca's Behavior"
|
||||||
msgstr "Управление поведением Alpaca"
|
msgstr "Управление поведением Alpaca"
|
||||||
|
|
||||||
#: src/window.ui:282
|
#: window.ui:282
|
||||||
msgid "Run in background"
|
msgid "Run in background"
|
||||||
msgstr "Запуск в фоновом режиме"
|
msgstr "Запуск в фоновом режиме"
|
||||||
|
|
||||||
#: src/window.ui:371
|
#: window.ui:371
|
||||||
msgid "Previous"
|
msgid "Previous"
|
||||||
msgstr "Предыдущий"
|
msgstr "Предыдущий"
|
||||||
|
|
||||||
#: src/window.ui:386
|
#: window.ui:386
|
||||||
msgid "Next"
|
msgid "Next"
|
||||||
msgstr "Следующий"
|
msgstr "Следующий"
|
||||||
|
|
||||||
#: src/window.ui:412
|
#: window.ui:412
|
||||||
msgid "Welcome to Alpaca"
|
msgid "Welcome to Alpaca"
|
||||||
msgstr "Добро пожаловать в Alpaca"
|
msgstr "Добро пожаловать в Alpaca"
|
||||||
|
|
||||||
#: src/window.ui:413
|
#: window.ui:413
|
||||||
msgid "Powered by Ollama"
|
msgid "Powered by Ollama"
|
||||||
msgstr "При поддержке Ollama"
|
msgstr "При поддержке Ollama"
|
||||||
|
|
||||||
#: src/window.ui:416
|
#: window.ui:416
|
||||||
msgid "Ollama Website"
|
msgid "Ollama Website"
|
||||||
msgstr "Веб-сайт Ollama"
|
msgstr "Веб-сайт Ollama"
|
||||||
|
|
||||||
#: src/window.ui:433
|
#: window.ui:432
|
||||||
|
msgid "Disclaimer"
|
||||||
|
msgstr "Отказ от ответственности"
|
||||||
|
|
||||||
|
#: window.ui:433
|
||||||
msgid ""
|
msgid ""
|
||||||
"Alpaca and its developers are not liable for any damages to devices or "
|
"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. "
|
"software resulting from the execution of code generated by an AI model. "
|
||||||
@@ -771,57 +331,85 @@ msgstr ""
|
|||||||
"Пожалуйста, будьте осторожны и внимательно ознакомьтесь с кодом перед его "
|
"Пожалуйста, будьте осторожны и внимательно ознакомьтесь с кодом перед его "
|
||||||
"запуском."
|
"запуском."
|
||||||
|
|
||||||
#: src/window.ui:454
|
#: window.ui:454
|
||||||
msgid "Keyboard Shortcuts"
|
msgid "Keyboard Shortcuts"
|
||||||
msgstr "Комбинации Клавиш"
|
msgstr "Комбинации Клавиш"
|
||||||
|
|
||||||
#: src/window.ui:458
|
#: window.ui:458
|
||||||
msgid "About Alpaca"
|
msgid "About Alpaca"
|
||||||
msgstr "О Программе"
|
msgstr "О Программе"
|
||||||
|
|
||||||
#: src/window.ui:496
|
#: window.ui:496
|
||||||
msgid "Close application"
|
msgid "Close application"
|
||||||
msgstr "Закрыть приложение"
|
msgstr "Закрыть приложение"
|
||||||
|
|
||||||
#: src/window.ui:502
|
#: window.ui:502
|
||||||
msgid "Clear chat"
|
msgid "Clear chat"
|
||||||
msgstr "Очистить чат"
|
msgstr "Очистить чат"
|
||||||
|
|
||||||
#: src/window.ui:514
|
#: window.ui:514
|
||||||
msgid "Show shortcuts window"
|
msgid "Show shortcuts window"
|
||||||
msgstr "Показать окно комбинаций клавиш"
|
msgstr "Показать окно комбинаций клавиш"
|
||||||
|
|
||||||
#: src/window.ui:521
|
#: window.ui:521
|
||||||
msgid "Editor"
|
msgid "Editor"
|
||||||
msgstr "Редактор"
|
msgstr "Редактор"
|
||||||
|
|
||||||
#: src/window.ui:525
|
#: window.ui:525
|
||||||
msgid "Copy"
|
msgid "Copy"
|
||||||
msgstr "Копировать"
|
msgstr "Копировать"
|
||||||
|
|
||||||
#: src/window.ui:531
|
#: window.ui:531
|
||||||
msgid "Paste"
|
msgid "Paste"
|
||||||
msgstr "Вставить"
|
msgstr "Вставить"
|
||||||
|
|
||||||
#: src/window.ui:537
|
#: window.ui:537
|
||||||
msgid "Insert new line"
|
msgid "Insert new line"
|
||||||
msgstr ""
|
msgstr "Вставить новую строку"
|
||||||
|
|
||||||
#: src/window.ui:543
|
#: window.ui:543
|
||||||
msgid "Send Message"
|
msgid "Send Message"
|
||||||
msgstr "Отправить Сообщение"
|
msgstr "Отправить Сообщение"
|
||||||
|
|
||||||
#~ msgctxt "shortcut window"
|
#, fuzzy
|
||||||
#~ msgid "General"
|
#~ msgid "Alpaca"
|
||||||
#~ msgstr "Общие"
|
#~ msgstr "О Программе"
|
||||||
|
|
||||||
#~ msgctxt "shortcut window"
|
#, fuzzy
|
||||||
#~ msgid "Show Shortcuts"
|
#~ msgid "Chat with local AI models"
|
||||||
#~ msgstr "Показывать комбинации клавиш"
|
#~ msgstr "Не удалось перечислить локальные модели"
|
||||||
|
|
||||||
#~ msgctxt "shortcut window"
|
#, fuzzy
|
||||||
#~ msgid "Quit"
|
#~ msgid "An Ollama client"
|
||||||
#~ msgstr "Выйти"
|
#~ msgstr "Веб-сайт Ollama"
|
||||||
|
|
||||||
|
#, fuzzy
|
||||||
|
#~ msgid "Import and export chats"
|
||||||
|
#~ msgstr "Импорт чата"
|
||||||
|
|
||||||
|
#, fuzzy
|
||||||
|
#~ msgid "Managing models"
|
||||||
|
#~ msgstr "Управление моделями"
|
||||||
|
|
||||||
|
#, fuzzy
|
||||||
|
#~ msgid "New Update"
|
||||||
|
#~ msgstr "Новый Чат"
|
||||||
|
|
||||||
|
#, fuzzy
|
||||||
|
#~ msgid "Fixed remote connections"
|
||||||
|
#~ msgstr "Использовать удаленное подключение"
|
||||||
|
|
||||||
|
#, fuzzy
|
||||||
|
#~ msgid "Added basic shortcuts"
|
||||||
|
#~ msgstr "Показывать ярлыки"
|
||||||
|
|
||||||
|
#, fuzzy
|
||||||
|
#~ msgid "New Features"
|
||||||
|
#~ msgstr "Новый Чат"
|
||||||
|
|
||||||
|
#, fuzzy
|
||||||
|
#~ msgid "A button to clear the chat"
|
||||||
|
#~ msgstr "Вы уверены, что хотите очистить чат?"
|
||||||
|
|
||||||
#~ msgid "Chats"
|
#~ msgid "Chats"
|
||||||
#~ msgstr "Чаты"
|
#~ msgstr "Чаты"
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
# connectionhandler.py
|
# connectionhandler.py
|
||||||
import json, requests
|
import json, requests
|
||||||
|
|
||||||
|
url = None
|
||||||
|
|
||||||
def simple_get(connection_url:str) -> dict:
|
def simple_get(connection_url:str) -> dict:
|
||||||
try:
|
try:
|
||||||
response = requests.get(connection_url)
|
response = requests.get(connection_url)
|
||||||
@@ -37,25 +39,3 @@ def stream_post(connection_url:str, data, callback:callable) -> dict:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
return {"status": "error", "status_code": 0}
|
return {"status": "error", "status_code": 0}
|
||||||
|
|
||||||
|
|
||||||
from time import sleep
|
|
||||||
def stream_post_fake(connection_url:str, data, callback:callable) -> dict:
|
|
||||||
data = {
|
|
||||||
"status": "pulling manifest"
|
|
||||||
}
|
|
||||||
callback(data)
|
|
||||||
for i in range(2):
|
|
||||||
for a in range(11):
|
|
||||||
sleep(.1)
|
|
||||||
data = {
|
|
||||||
"status": f"downloading digestname {i}",
|
|
||||||
"digest": f"digestname {i}",
|
|
||||||
"total": 500,
|
|
||||||
"completed": a * 50
|
|
||||||
}
|
|
||||||
callback(data)
|
|
||||||
for msg in ["verifying sha256 digest", "writting manifest", "removing any unused layers", "success"]:
|
|
||||||
sleep(.1)
|
|
||||||
data = {"status": msg}
|
|
||||||
callback(data)
|
|
||||||
return {"status": "ok", "status_code": 200}
|
|
||||||
|
|||||||
228
src/dialogs.py
Normal file
228
src/dialogs.py
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
# dialogs.py
|
||||||
|
|
||||||
|
from gi.repository import Adw, Gtk, Gdk, GLib, GtkSource, Gio, GdkPixbuf
|
||||||
|
from .available_models import available_models
|
||||||
|
|
||||||
|
# CLEAR CHAT | WORKS
|
||||||
|
|
||||||
|
def clear_chat_response(self, dialog, task):
|
||||||
|
if dialog.choose_finish(task) == "clear":
|
||||||
|
self.clear_chat()
|
||||||
|
|
||||||
|
def clear_chat(self):
|
||||||
|
if self.bot_message is not None:
|
||||||
|
self.show_toast("info", 1, self.main_overlay)
|
||||||
|
return
|
||||||
|
dialog = Adw.AlertDialog(
|
||||||
|
heading=_("Clear Chat"),
|
||||||
|
body=_("Are you sure you want to clear the chat?"),
|
||||||
|
close_response="cancel"
|
||||||
|
)
|
||||||
|
dialog.add_response("cancel", _("Cancel"))
|
||||||
|
dialog.add_response("clear", _("Clear"))
|
||||||
|
dialog.set_response_appearance("clear", Adw.ResponseAppearance.DESTRUCTIVE)
|
||||||
|
dialog.choose(
|
||||||
|
parent = self,
|
||||||
|
cancellable = None,
|
||||||
|
callback = lambda dialog, task: clear_chat_response(self, dialog, task)
|
||||||
|
)
|
||||||
|
|
||||||
|
# DELETE CHAT | WORKS
|
||||||
|
|
||||||
|
def delete_chat_response(self, dialog, task, chat_name):
|
||||||
|
if dialog.choose_finish(task) == "delete":
|
||||||
|
self.delete_chat(chat_name)
|
||||||
|
|
||||||
|
def delete_chat(self, chat_name):
|
||||||
|
dialog = Adw.AlertDialog(
|
||||||
|
heading=_("Delete Chat"),
|
||||||
|
body=_("Are you sure you want to delete '{}'?").format(chat_name),
|
||||||
|
close_response="cancel"
|
||||||
|
)
|
||||||
|
dialog.add_response("cancel", _("Cancel"))
|
||||||
|
dialog.add_response("delete", _("Delete"))
|
||||||
|
dialog.set_response_appearance("delete", Adw.ResponseAppearance.DESTRUCTIVE)
|
||||||
|
dialog.choose(
|
||||||
|
parent = self,
|
||||||
|
cancellable = None,
|
||||||
|
callback = lambda dialog, task, chat_name=chat_name: delete_chat_response(self, dialog, task, chat_name)
|
||||||
|
)
|
||||||
|
|
||||||
|
# RENAME CHAT | WORKS
|
||||||
|
|
||||||
|
def rename_chat_response(self, dialog, task, old_chat_name, entry, label_element):
|
||||||
|
if not entry: return
|
||||||
|
new_chat_name = entry.get_text()
|
||||||
|
if old_chat_name == new_chat_name: return
|
||||||
|
if new_chat_name and (task is None or dialog.choose_finish(task) == "rename"):
|
||||||
|
self.rename_chat(old_chat_name, new_chat_name, label_element)
|
||||||
|
|
||||||
|
def rename_chat(self, chat_name:str, label_element):
|
||||||
|
entry = Gtk.Entry()
|
||||||
|
dialog = Adw.AlertDialog(
|
||||||
|
heading=_("Rename Chat"),
|
||||||
|
body=_("Renaming '{}'").format(chat_name),
|
||||||
|
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.choose(
|
||||||
|
parent = self,
|
||||||
|
cancellable = None,
|
||||||
|
callback = lambda dialog, task, old_chat_name=chat_name, entry=entry, label_element=label_element: rename_chat_response(self, dialog, task, old_chat_name, entry, label_element)
|
||||||
|
)
|
||||||
|
|
||||||
|
# NEW CHAT | WORKS
|
||||||
|
|
||||||
|
def new_chat_response(self, dialog, task, entry):
|
||||||
|
chat_name = _("New Chat")
|
||||||
|
if entry is not None and entry.get_text() != "": chat_name = entry.get_text()
|
||||||
|
if chat_name and (task is None or dialog.choose_finish(task) == "create"):
|
||||||
|
self.new_chat(chat_name)
|
||||||
|
|
||||||
|
|
||||||
|
def new_chat(self):
|
||||||
|
entry = Gtk.Entry()
|
||||||
|
dialog = Adw.AlertDialog(
|
||||||
|
heading=_("Create Chat"),
|
||||||
|
body=_("Enter name for new chat"),
|
||||||
|
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.choose(
|
||||||
|
parent = self,
|
||||||
|
cancellable = None,
|
||||||
|
callback = lambda dialog, task, entry=entry: new_chat_response(self, dialog, task, entry)
|
||||||
|
)
|
||||||
|
|
||||||
|
# STOP PULL MODEL | WORKS
|
||||||
|
|
||||||
|
def stop_pull_model_response(self, dialog, task, model_name):
|
||||||
|
if dialog.choose_finish(task) == "stop":
|
||||||
|
self.stop_pull_model(model_name)
|
||||||
|
|
||||||
|
def stop_pull_model(self, model_name):
|
||||||
|
dialog = Adw.AlertDialog(
|
||||||
|
heading=_("Stop Model"),
|
||||||
|
body=_("Are you sure you want to stop pulling '{}'?").format(model_name),
|
||||||
|
close_response="cancel"
|
||||||
|
)
|
||||||
|
dialog.add_response("cancel", _("Cancel"))
|
||||||
|
dialog.add_response("stop", _("Stop"))
|
||||||
|
dialog.set_response_appearance("stop", Adw.ResponseAppearance.DESTRUCTIVE)
|
||||||
|
dialog.choose(
|
||||||
|
parent = self.manage_models_dialog,
|
||||||
|
cancellable = None,
|
||||||
|
callback = lambda dialog, task, model_name = model_name: stop_pull_model_response(self, dialog, task, model_name)
|
||||||
|
)
|
||||||
|
|
||||||
|
# DELETE MODEL | WORKS
|
||||||
|
|
||||||
|
def delete_model_response(self, dialog, task, model_name):
|
||||||
|
if dialog.choose_finish(task) == "delete":
|
||||||
|
self.delete_model(model_name)
|
||||||
|
|
||||||
|
def delete_model(self, model_name):
|
||||||
|
dialog = Adw.AlertDialog(
|
||||||
|
heading=_("Delete Model"),
|
||||||
|
body=_("Are you sure you want to delete '{}'?").format(model_name),
|
||||||
|
close_response="cancel"
|
||||||
|
)
|
||||||
|
dialog.add_response("cancel", _("Cancel"))
|
||||||
|
dialog.add_response("delete", _("Delete"))
|
||||||
|
dialog.set_response_appearance("delete", Adw.ResponseAppearance.DESTRUCTIVE)
|
||||||
|
dialog.choose(
|
||||||
|
parent = self.manage_models_dialog,
|
||||||
|
cancellable = None,
|
||||||
|
callback = lambda dialog, task, model_name = model_name: delete_model_response(self, dialog, task, model_name)
|
||||||
|
)
|
||||||
|
|
||||||
|
# PULL MODEL | WORKS
|
||||||
|
|
||||||
|
def pull_model_response(self, dialog, task, model_name, tag_drop_down):
|
||||||
|
if dialog.choose_finish(task) == "pull":
|
||||||
|
model = f"{model_name}:{tag_drop_down.get_selected_item().get_string()}"
|
||||||
|
self.pull_model(model)
|
||||||
|
|
||||||
|
def pull_model(self, model_name):
|
||||||
|
tag_list = Gtk.StringList()
|
||||||
|
for tag in available_models[model_name]['tags']:
|
||||||
|
tag_list.append(tag)
|
||||||
|
tag_drop_down = Gtk.DropDown(
|
||||||
|
enable_search=True,
|
||||||
|
model=tag_list
|
||||||
|
)
|
||||||
|
dialog = Adw.AlertDialog(
|
||||||
|
heading=_("Pull Model"),
|
||||||
|
body=_("Please select a tag to pull '{}'").format(model_name),
|
||||||
|
extra_child=tag_drop_down,
|
||||||
|
close_response="cancel"
|
||||||
|
)
|
||||||
|
dialog.add_response("cancel", _("Cancel"))
|
||||||
|
dialog.add_response("pull", _("Pull"))
|
||||||
|
dialog.set_response_appearance("pull", Adw.ResponseAppearance.SUGGESTED)
|
||||||
|
dialog.choose(
|
||||||
|
parent = self.manage_models_dialog,
|
||||||
|
cancellable = None,
|
||||||
|
callback = lambda dialog, task, model_name = model_name, tag_drop_down = tag_drop_down: pull_model_response(self, dialog, task, model_name, tag_drop_down)
|
||||||
|
)
|
||||||
|
|
||||||
|
# REMOVE IMAGE | WORKS
|
||||||
|
|
||||||
|
def remove_image_response(self, dialog, task):
|
||||||
|
if dialog.choose_finish(task) == 'remove':
|
||||||
|
self.remove_image()
|
||||||
|
|
||||||
|
def remove_image(self):
|
||||||
|
dialog = Adw.AlertDialog(
|
||||||
|
heading=_("Remove Image"),
|
||||||
|
body=_("Are you sure you want to remove image?"),
|
||||||
|
close_response="cancel"
|
||||||
|
)
|
||||||
|
dialog.add_response("cancel", _("Cancel"))
|
||||||
|
dialog.add_response("remove", _("Remove"))
|
||||||
|
dialog.set_response_appearance("remove", Adw.ResponseAppearance.DESTRUCTIVE)
|
||||||
|
dialog.choose(
|
||||||
|
parent = self,
|
||||||
|
cancellable = None,
|
||||||
|
callback = lambda dialog, task: remove_image_response(self, dialog, task)
|
||||||
|
)
|
||||||
|
|
||||||
|
# RECONNECT REMOTE |
|
||||||
|
|
||||||
|
def reconnect_remote_response(self, dialog, task, entry):
|
||||||
|
response = dialog.choose_finish(task)
|
||||||
|
if not task or response == "remote":
|
||||||
|
self.connect_remote(entry.get_text())
|
||||||
|
elif response == "local":
|
||||||
|
self.connect_local()
|
||||||
|
elif response == "close":
|
||||||
|
self.destroy()
|
||||||
|
|
||||||
|
def reconnect_remote(self):
|
||||||
|
entry = Gtk.Entry(
|
||||||
|
css_classes = ["error"],
|
||||||
|
text = self.ollama_url
|
||||||
|
)
|
||||||
|
dialog = Adw.AlertDialog(
|
||||||
|
heading=_("Connection Error"),
|
||||||
|
body=_("The remote instance has disconnected"),
|
||||||
|
extra_child=entry
|
||||||
|
)
|
||||||
|
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.choose(
|
||||||
|
parent = self,
|
||||||
|
cancellable = None,
|
||||||
|
callback = lambda dialog, task, entry=entry: reconnect_remote_response(self, dialog, task, entry)
|
||||||
|
)
|
||||||
23
src/local_instance.py
Normal file
23
src/local_instance.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# local_instance.py
|
||||||
|
import subprocess, os
|
||||||
|
from time import sleep
|
||||||
|
|
||||||
|
instance = None
|
||||||
|
port = 11435
|
||||||
|
|
||||||
|
def start(data_dir):
|
||||||
|
instance = subprocess.Popen(["/app/bin/ollama", "serve"], env={**os.environ, 'OLLAMA_HOST': f"127.0.0.1:{port}", "HOME": data_dir}, stderr=subprocess.PIPE, text=True)
|
||||||
|
print("Starting Alpaca's Ollama instance...")
|
||||||
|
sleep(1)
|
||||||
|
while True:
|
||||||
|
err = instance.stderr.readline()
|
||||||
|
if err == '' and instance.poll() is not None:
|
||||||
|
break
|
||||||
|
if 'msg="inference compute"' in err: #Ollama outputs a line with this when it finishes loading, yeah
|
||||||
|
break
|
||||||
|
print("Started Alpaca's Ollama instance")
|
||||||
|
|
||||||
|
def stop():
|
||||||
|
if instance: instance.kill()
|
||||||
|
print("Stopped Alpaca's Ollama instance")
|
||||||
|
|
||||||
@@ -33,7 +33,6 @@ class AlpacaApplication(Adw.Application):
|
|||||||
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>q'])
|
self.create_action('quit', lambda *_: self.quit(), ['<primary>q'])
|
||||||
self.create_action('clear', lambda *_: AlpacaWindow.clear_chat_dialog(self.props.active_window), ['<primary>e'])
|
|
||||||
self.create_action('preferences', lambda *_: AlpacaWindow.show_preferences_dialog(self.props.active_window), ['<primary>p'])
|
self.create_action('preferences', lambda *_: AlpacaWindow.show_preferences_dialog(self.props.active_window), ['<primary>p'])
|
||||||
self.create_action('about', self.on_about_action)
|
self.create_action('about', self.on_about_action)
|
||||||
|
|
||||||
@@ -48,7 +47,7 @@ class AlpacaApplication(Adw.Application):
|
|||||||
application_name='Alpaca',
|
application_name='Alpaca',
|
||||||
application_icon='com.jeffser.Alpaca',
|
application_icon='com.jeffser.Alpaca',
|
||||||
developer_name='Jeffry Samuel Eduarte Rojas',
|
developer_name='Jeffry Samuel Eduarte Rojas',
|
||||||
version='0.8.1',
|
version='0.8.6',
|
||||||
developers=['Jeffser https://jeffser.com'],
|
developers=['Jeffser https://jeffser.com'],
|
||||||
designers=['Jeffser https://jeffser.com'],
|
designers=['Jeffser https://jeffser.com'],
|
||||||
translator_credits='Alex K (Russian) https://github.com/alexkdeveloper\nJeffser (Spanish) https://jeffser.com\nDaimar Stein (Brazilian Portuguese) https://github.com/not-a-dev-stein',
|
translator_credits='Alex K (Russian) https://github.com/alexkdeveloper\nJeffser (Spanish) https://jeffser.com\nDaimar Stein (Brazilian Portuguese) https://github.com/not-a-dev-stein',
|
||||||
|
|||||||
@@ -31,7 +31,9 @@ alpaca_sources = [
|
|||||||
'main.py',
|
'main.py',
|
||||||
'window.py',
|
'window.py',
|
||||||
'connection_handler.py',
|
'connection_handler.py',
|
||||||
'available_models.py'
|
'available_models.py',
|
||||||
|
'dialogs.py',
|
||||||
|
'local_instance.py'
|
||||||
]
|
]
|
||||||
|
|
||||||
install_data(alpaca_sources, install_dir: moduledir)
|
install_data(alpaca_sources, install_dir: moduledir)
|
||||||
|
|||||||
714
src/window.py
714
src/window.py
@@ -26,8 +26,8 @@ from time import sleep
|
|||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from .connection_handler import simple_get, simple_delete, stream_post, stream_post_fake
|
|
||||||
from .available_models import available_models
|
from .available_models import available_models
|
||||||
|
from . import dialogs, local_instance, connection_handler
|
||||||
|
|
||||||
@Gtk.Template(resource_path='/com/jeffser/Alpaca/window.ui')
|
@Gtk.Template(resource_path='/com/jeffser/Alpaca/window.ui')
|
||||||
class AlpacaWindow(Adw.ApplicationWindow):
|
class AlpacaWindow(Adw.ApplicationWindow):
|
||||||
@@ -46,17 +46,13 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
|
|
||||||
#Variables
|
#Variables
|
||||||
run_on_background = False
|
run_on_background = False
|
||||||
ollama_url = ""
|
|
||||||
remote_url = ""
|
remote_url = ""
|
||||||
run_remote = False
|
run_remote = False
|
||||||
local_ollama_port = 11435
|
|
||||||
local_ollama_instance = None
|
|
||||||
local_models = []
|
local_models = []
|
||||||
pulling_models = {}
|
pulling_models = {}
|
||||||
current_chat_elements = [] #Used for deleting
|
current_chat_elements = [] #Used for deleting
|
||||||
chats = {"chats": {_("New Chat"): {"messages": []}}, "selected_chat": "New Chat"}
|
chats = {"chats": {_("New Chat"): {"messages": []}}, "selected_chat": "New Chat"}
|
||||||
attached_image = {"path": None, "base64": None}
|
attached_image = {"path": None, "base64": None}
|
||||||
first_time_setup = False
|
|
||||||
|
|
||||||
#Elements
|
#Elements
|
||||||
preferences_dialog = Gtk.Template.Child()
|
preferences_dialog = Gtk.Template.Child()
|
||||||
@@ -80,7 +76,6 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
model_drop_down = Gtk.Template.Child()
|
model_drop_down = Gtk.Template.Child()
|
||||||
model_string_list = Gtk.Template.Child()
|
model_string_list = Gtk.Template.Child()
|
||||||
|
|
||||||
manage_models_button = Gtk.Template.Child()
|
|
||||||
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()
|
||||||
local_model_list_box = Gtk.Template.Child()
|
local_model_list_box = Gtk.Template.Child()
|
||||||
@@ -112,7 +107,8 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
_("Please select a model before chatting"),
|
_("Please select a model before chatting"),
|
||||||
_("Chat cannot be cleared while receiving a message"),
|
_("Chat cannot be cleared while receiving a message"),
|
||||||
_("That tag is already being pulled"),
|
_("That tag is already being pulled"),
|
||||||
_("That tag has been pulled already")
|
_("That tag has been pulled already"),
|
||||||
|
_("Code copied to the clipboard")
|
||||||
],
|
],
|
||||||
"good": [
|
"good": [
|
||||||
_("Model deleted successfully"),
|
_("Model deleted successfully"),
|
||||||
@@ -122,6 +118,138 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
style_manager = Adw.StyleManager()
|
||||||
|
|
||||||
|
@Gtk.Template.Callback()
|
||||||
|
def verify_if_image_can_be_used(self, pspec=None, user_data=None):
|
||||||
|
if self.model_drop_down.get_selected_item() == None: return True
|
||||||
|
selected = self.model_drop_down.get_selected_item().get_string().split(":")[0]
|
||||||
|
if selected in ['llava', 'bakllava', 'moondream', 'llava-llama3']:
|
||||||
|
self.image_button.set_sensitive(True)
|
||||||
|
self.image_button.set_tooltip_text(_("Upload image"))
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
self.image_button.set_sensitive(False)
|
||||||
|
self.image_button.set_tooltip_text(_("Only available on selected models"))
|
||||||
|
self.image_button.set_css_classes([])
|
||||||
|
self.image_button.get_child().set_icon_name("image-x-generic-symbolic")
|
||||||
|
self.attached_image = {"path": None, "base64": None}
|
||||||
|
return False
|
||||||
|
|
||||||
|
@Gtk.Template.Callback()
|
||||||
|
def send_message(self, button=None):
|
||||||
|
if button and self.bot_message: #STOP BUTTON
|
||||||
|
if self.loading_spinner: self.chat_container.remove(self.loading_spinner)
|
||||||
|
if self.verify_if_image_can_be_used(): self.image_button.set_sensitive(True)
|
||||||
|
self.image_button.set_css_classes([])
|
||||||
|
self.image_button.get_child().set_icon_name("image-x-generic-symbolic")
|
||||||
|
self.attached_image = {"path": None, "base64": None}
|
||||||
|
self.toggle_ui_sensitive(True)
|
||||||
|
self.send_button.set_css_classes(["suggested-action"])
|
||||||
|
self.send_button.get_child().set_label("Send")
|
||||||
|
self.send_button.get_child().set_icon_name("send-to-symbolic")
|
||||||
|
self.bot_message = None
|
||||||
|
self.bot_message_box = None
|
||||||
|
self.bot_message_view = None
|
||||||
|
else:
|
||||||
|
if not self.message_text_view.get_buffer().get_text(self.message_text_view.get_buffer().get_start_iter(), self.message_text_view.get_buffer().get_end_iter(), False): return
|
||||||
|
current_model = self.model_drop_down.get_selected_item()
|
||||||
|
if current_model is None:
|
||||||
|
self.show_toast("info", 0, self.main_overlay)
|
||||||
|
return
|
||||||
|
formated_datetime = datetime.now().strftime("%Y/%m/%d %H:%M")
|
||||||
|
self.chats["chats"][self.chats["selected_chat"]]["messages"].append({
|
||||||
|
"role": "user",
|
||||||
|
"model": "User",
|
||||||
|
"date": formated_datetime,
|
||||||
|
"content": self.message_text_view.get_buffer().get_text(self.message_text_view.get_buffer().get_start_iter(), self.message_text_view.get_buffer().get_end_iter(), False)
|
||||||
|
})
|
||||||
|
data = {
|
||||||
|
"model": current_model.get_string(),
|
||||||
|
"messages": self.chats["chats"][self.chats["selected_chat"]]["messages"]
|
||||||
|
}
|
||||||
|
if self.verify_if_image_can_be_used() and self.attached_image["base64"] is not None:
|
||||||
|
data["messages"][-1]["images"] = [self.attached_image["base64"]]
|
||||||
|
self.send_button.set_css_classes(["destructive-action"])
|
||||||
|
self.send_button.get_child().set_label("Stop")
|
||||||
|
self.send_button.get_child().set_icon_name("edit-delete-symbolic")
|
||||||
|
self.toggle_ui_sensitive(False)
|
||||||
|
self.image_button.set_sensitive(False)
|
||||||
|
|
||||||
|
self.show_message(self.message_text_view.get_buffer().get_text(self.message_text_view.get_buffer().get_start_iter(), self.message_text_view.get_buffer().get_end_iter(), False), False, f"\n\n<small>{formated_datetime}</small>", self.attached_image["base64"])
|
||||||
|
self.message_text_view.get_buffer().set_text("", 0)
|
||||||
|
self.loading_spinner = Gtk.Spinner(spinning=True, margin_top=12, margin_bottom=12, hexpand=True)
|
||||||
|
self.chat_container.append(self.loading_spinner)
|
||||||
|
self.show_message("", True)
|
||||||
|
|
||||||
|
thread = threading.Thread(target=self.run_message, args=(data['messages'], data['model']))
|
||||||
|
thread.start()
|
||||||
|
|
||||||
|
@Gtk.Template.Callback()
|
||||||
|
def manage_models_button_activate(self, button=None):
|
||||||
|
self.update_list_local_models()
|
||||||
|
self.manage_models_dialog.present(self)
|
||||||
|
|
||||||
|
@Gtk.Template.Callback()
|
||||||
|
def welcome_carousel_page_changed(self, carousel, index):
|
||||||
|
if index == 0: self.welcome_previous_button.set_sensitive(False)
|
||||||
|
else: self.welcome_previous_button.set_sensitive(True)
|
||||||
|
if index == carousel.get_n_pages()-1: self.welcome_next_button.set_label(_("Close"))
|
||||||
|
else: self.welcome_next_button.set_label(_("Next"))
|
||||||
|
|
||||||
|
@Gtk.Template.Callback()
|
||||||
|
def welcome_previous_button_activate(self, button):
|
||||||
|
self.welcome_carousel.scroll_to(self.welcome_carousel.get_nth_page(self.welcome_carousel.get_position()-1), True)
|
||||||
|
|
||||||
|
@Gtk.Template.Callback()
|
||||||
|
def welcome_next_button_activate(self, button):
|
||||||
|
if button.get_label() == "Next": self.welcome_carousel.scroll_to(self.welcome_carousel.get_nth_page(self.welcome_carousel.get_position()+1), True)
|
||||||
|
else:
|
||||||
|
self.welcome_dialog.force_close()
|
||||||
|
if not self.verify_connection():
|
||||||
|
self.connection_error()
|
||||||
|
|
||||||
|
@Gtk.Template.Callback()
|
||||||
|
def open_image(self, button):
|
||||||
|
if "destructive-action" in button.get_css_classes():
|
||||||
|
dialogs.remove_image(self)
|
||||||
|
else:
|
||||||
|
file_dialog = Gtk.FileDialog(default_filter=self.file_filter_image)
|
||||||
|
file_dialog.open(self, None, self.load_image)
|
||||||
|
|
||||||
|
@Gtk.Template.Callback()
|
||||||
|
def chat_changed(self, listbox, row):
|
||||||
|
if row and row.get_name() != self.chats["selected_chat"]:
|
||||||
|
self.chats["selected_chat"] = row.get_name()
|
||||||
|
self.load_history_into_chat()
|
||||||
|
if len(self.chats["chats"][self.chats["selected_chat"]]["messages"]) > 0:
|
||||||
|
for i in range(self.model_string_list.get_n_items()):
|
||||||
|
if self.model_string_list.get_string(i) == self.chats["chats"][self.chats["selected_chat"]]["messages"][-1]["model"]:
|
||||||
|
self.model_drop_down.set_selected(i)
|
||||||
|
break
|
||||||
|
|
||||||
|
@Gtk.Template.Callback()
|
||||||
|
def change_remote_url(self, entry):
|
||||||
|
self.remote_url = entry.get_text()
|
||||||
|
if self.run_remote:
|
||||||
|
connection_handler.url = self.remote_url
|
||||||
|
if self.verify_connection() == False:
|
||||||
|
entry.set_css_classes(["error"])
|
||||||
|
self.show_toast("error", 1, 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)
|
||||||
|
|
||||||
|
|
||||||
def show_toast(self, message_type:str, message_id:int, overlay):
|
def show_toast(self, message_type:str, message_id:int, overlay):
|
||||||
if message_type not in self.toast_messages or message_id > len(self.toast_messages[message_type] or message_id < 0):
|
if message_type not in self.toast_messages or message_id > len(self.toast_messages[message_type] or message_id < 0):
|
||||||
message_type = "error"
|
message_type = "error"
|
||||||
@@ -132,8 +260,8 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
)
|
)
|
||||||
overlay.add_toast(toast)
|
overlay.add_toast(toast)
|
||||||
|
|
||||||
def show_notification(self, title:str, body:str, only_when_focus:bool, icon:Gio.ThemedIcon=None):
|
def show_notification(self, title:str, body:str, only_when_unfocus:bool, icon:Gio.ThemedIcon=None):
|
||||||
if only_when_focus==False or self.is_active()==False:
|
if not only_when_unfocus or (only_when_unfocus and self.is_active()==False):
|
||||||
notification = Gio.Notification.new(title)
|
notification = Gio.Notification.new(title)
|
||||||
notification.set_body(body)
|
notification.set_body(body)
|
||||||
if icon: notification.set_icon(icon)
|
if icon: notification.set_icon(icon)
|
||||||
@@ -174,7 +302,8 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
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"],
|
||||||
|
margin_start=0 if bot else 50,
|
||||||
)
|
)
|
||||||
message_text.set_valign(Gtk.Align.CENTER)
|
message_text.set_valign(Gtk.Align.CENTER)
|
||||||
|
|
||||||
@@ -209,22 +338,9 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
self.bot_message_view = message_text
|
self.bot_message_view = message_text
|
||||||
self.bot_message_box = message_box
|
self.bot_message_box = message_box
|
||||||
|
|
||||||
def verify_if_image_can_be_used(self, pspec=None, user_data=None):
|
|
||||||
if self.model_drop_down.get_selected_item() == None: return True
|
|
||||||
selected = self.model_drop_down.get_selected_item().get_string().split(":")[0]
|
|
||||||
if selected in ['llava', 'bakllava', 'moondream', 'llava-llama3']:
|
|
||||||
self.image_button.set_sensitive(True)
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
self.image_button.set_sensitive(False)
|
|
||||||
self.image_button.set_css_classes([])
|
|
||||||
self.image_button.get_child().set_icon_name("image-x-generic-symbolic")
|
|
||||||
self.attached_image = {"path": None, "base64": None}
|
|
||||||
return False
|
|
||||||
|
|
||||||
def update_list_local_models(self):
|
def update_list_local_models(self):
|
||||||
self.local_models = []
|
self.local_models = []
|
||||||
response = simple_get(self.ollama_url + "/api/tags")
|
response = connection_handler.simple_get(connection_handler.url + "/api/tags")
|
||||||
for i in range(self.model_string_list.get_n_items() -1, -1, -1):
|
for i in range(self.model_string_list.get_n_items() -1, -1, -1):
|
||||||
self.model_string_list.remove(i)
|
self.model_string_list.remove(i)
|
||||||
if response['status'] == 'ok':
|
if response['status'] == 'ok':
|
||||||
@@ -244,7 +360,7 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
valign = 3,
|
valign = 3,
|
||||||
css_classes = ["error"]
|
css_classes = ["error"]
|
||||||
)
|
)
|
||||||
button.connect("clicked", lambda button=button, model_name=model["name"]: self.model_delete_button_activate(model_name))
|
button.connect("clicked", lambda button=button, model_name=model["name"]: dialogs.delete_model(self, model_name))
|
||||||
model_row.add_suffix(button)
|
model_row.add_suffix(button)
|
||||||
self.local_model_list_box.append(model_row)
|
self.local_model_list_box.append(model_row)
|
||||||
|
|
||||||
@@ -257,12 +373,11 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
self.connection_error()
|
self.connection_error()
|
||||||
|
|
||||||
def verify_connection(self):
|
def verify_connection(self):
|
||||||
response = simple_get(self.ollama_url)
|
response = connection_handler.simple_get(connection_handler.url)
|
||||||
if response['status'] == 'ok':
|
if response['status'] == 'ok':
|
||||||
if "Ollama is running" in response['text']:
|
if "Ollama is running" in response['text']:
|
||||||
with open(os.path.join(self.config_dir, "server.json"), "w+") as f:
|
with open(os.path.join(self.config_dir, "server.json"), "w+") as f:
|
||||||
json.dump({'remote_url': self.remote_url, 'run_remote': self.run_remote, 'local_port': self.local_ollama_port, 'run_on_background': self.run_on_background}, f)
|
json.dump({'remote_url': self.remote_url, 'run_remote': self.run_remote, 'local_port': local_instance.port, 'run_on_background': self.run_on_background}, f)
|
||||||
#self.message_text_view.grab_focus_without_selecting()
|
|
||||||
self.update_list_local_models()
|
self.update_list_local_models()
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
@@ -301,8 +416,6 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
wrap_mode= Gtk.WrapMode.WORD,
|
wrap_mode= Gtk.WrapMode.WORD,
|
||||||
margin_top=12,
|
margin_top=12,
|
||||||
margin_bottom=12,
|
margin_bottom=12,
|
||||||
margin_start=12,
|
|
||||||
margin_end=12,
|
|
||||||
hexpand=True,
|
hexpand=True,
|
||||||
css_classes=["flat"]
|
css_classes=["flat"]
|
||||||
)
|
)
|
||||||
@@ -336,18 +449,52 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
self.bot_message_box.append(message_text)
|
self.bot_message_box.append(message_text)
|
||||||
else:
|
else:
|
||||||
language = GtkSource.LanguageManager.get_default().get_language(part['language'])
|
language = GtkSource.LanguageManager.get_default().get_language(part['language'])
|
||||||
|
if language:
|
||||||
buffer = GtkSource.Buffer.new_with_language(language)
|
buffer = GtkSource.Buffer.new_with_language(language)
|
||||||
|
else:
|
||||||
|
buffer = GtkSource.Buffer()
|
||||||
buffer.set_text(part['text'])
|
buffer.set_text(part['text'])
|
||||||
buffer.set_style_scheme(GtkSource.StyleSchemeManager.get_default().get_scheme('classic-dark'))
|
if self.style_manager.get_dark():
|
||||||
|
source_style = GtkSource.StyleSchemeManager.get_default().get_scheme('Adwaita-dark')
|
||||||
|
else:
|
||||||
|
source_style = GtkSource.StyleSchemeManager.get_default().get_scheme('Adwaita')
|
||||||
|
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
|
||||||
)
|
)
|
||||||
source_view.set_editable(False)
|
source_view.set_editable(False)
|
||||||
source_view.get_style_context().add_class("card")
|
code_block_box = Gtk.Box(css_classes=["card"], orientation=1, overflow=1)
|
||||||
self.bot_message_box.append(source_view)
|
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 part['language'], hexpand=True, xalign=0))
|
||||||
|
copy_button = Gtk.Button(icon_name="edit-copy-symbolic", css_classes=["flat", "circular"])
|
||||||
|
copy_button.connect("clicked", self.on_copy_code_clicked, buffer)
|
||||||
|
title_box.append(copy_button)
|
||||||
|
code_block_box.append(title_box)
|
||||||
|
code_block_box.append(Gtk.Separator())
|
||||||
|
code_block_box.append(source_view)
|
||||||
|
self.bot_message_box.append(code_block_box)
|
||||||
|
self.style_manager.connect("notify::dark", self.on_theme_changed, buffer)
|
||||||
|
vadjustment = self.chat_window.get_vadjustment()
|
||||||
|
vadjustment.set_value(vadjustment.get_upper())
|
||||||
self.bot_message = None
|
self.bot_message = None
|
||||||
self.bot_message_box = None
|
self.bot_message_box = None
|
||||||
|
|
||||||
|
def on_theme_changed(self, manager, dark, buffer):
|
||||||
|
if manager.get_dark():
|
||||||
|
source_style = GtkSource.StyleSchemeManager.get_default().get_scheme('Adwaita-dark')
|
||||||
|
else:
|
||||||
|
source_style = GtkSource.StyleSchemeManager.get_default().get_scheme('Adwaita')
|
||||||
|
buffer.set_style_scheme(source_style)
|
||||||
|
|
||||||
|
def on_copy_code_clicked(self, btn, text_buffer):
|
||||||
|
clipboard = Gdk.Display().get_default().get_clipboard()
|
||||||
|
start = text_buffer.get_start_iter()
|
||||||
|
end = text_buffer.get_end_iter()
|
||||||
|
text = text_buffer.get_text(start, end, False)
|
||||||
|
clipboard.set(text)
|
||||||
|
self.show_toast("info", 4, self.main_overlay)
|
||||||
|
|
||||||
def update_bot_message(self, data):
|
def update_bot_message(self, data):
|
||||||
if self.bot_message is None:
|
if self.bot_message is None:
|
||||||
self.save_history()
|
self.save_history()
|
||||||
@@ -373,92 +520,24 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
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"][-1]['content'] += data['message']['content']
|
self.chats["chats"][self.chats["selected_chat"]]["messages"][-1]['content'] += data['message']['content']
|
||||||
|
|
||||||
|
def toggle_ui_sensitive(self, status):
|
||||||
|
for element in [self.chat_list_box, self.export_chat_button, self.import_chat_button, self.add_chat_button]:
|
||||||
|
element.set_sensitive(status)
|
||||||
|
|
||||||
def run_message(self, messages, model):
|
def run_message(self, messages, model):
|
||||||
response = stream_post(f"{self.ollama_url}/api/chat", data=json.dumps({"model": model, "messages": messages}), callback=self.update_bot_message)
|
response = connection_handler.stream_post(f"{connection_handler.url}/api/chat", data=json.dumps({"model": model, "messages": messages}), callback=self.update_bot_message)
|
||||||
GLib.idle_add(self.add_code_blocks)
|
GLib.idle_add(self.add_code_blocks)
|
||||||
GLib.idle_add(self.send_button.set_css_classes, ["suggested-action"])
|
GLib.idle_add(self.send_button.set_css_classes, ["suggested-action"])
|
||||||
GLib.idle_add(self.send_button.get_child().set_label, "Send")
|
GLib.idle_add(self.send_button.get_child().set_label, "Send")
|
||||||
GLib.idle_add(self.send_button.get_child().set_icon_name, "send-to-symbolic")
|
GLib.idle_add(self.send_button.get_child().set_icon_name, "send-to-symbolic")
|
||||||
GLib.idle_add(self.chat_list_box.set_sensitive, True)
|
GLib.idle_add(self.toggle_ui_sensitive, True)
|
||||||
GLib.idle_add(self.export_chat_button.set_sensitive, True)
|
|
||||||
GLib.idle_add(self.import_chat_button.set_sensitive, True)
|
|
||||||
GLib.idle_add(self.add_chat_button.set_sensitive, True)
|
|
||||||
if self.verify_if_image_can_be_used(): GLib.idle_add(self.image_button.set_sensitive, True)
|
if self.verify_if_image_can_be_used(): GLib.idle_add(self.image_button.set_sensitive, True)
|
||||||
GLib.idle_add(self.image_button.set_css_classes, [])
|
GLib.idle_add(self.image_button.set_css_classes, [])
|
||||||
GLib.idle_add(self.image_button.get_child().set_icon_name, "image-x-generic-symbolic")
|
GLib.idle_add(self.image_button.get_child().set_icon_name, "image-x-generic-symbolic")
|
||||||
self.attached_image = {"path": None, "base64": None}
|
self.attached_image = {"path": None, "base64": None}
|
||||||
# GLib.idle_add(self.message_text_view.set_sensitive, True)
|
|
||||||
if response['status'] == 'error':
|
if response['status'] == 'error':
|
||||||
GLib.idle_add(self.connection_error)
|
GLib.idle_add(self.connection_error)
|
||||||
|
|
||||||
def send_message(self, button=None):
|
|
||||||
if button and self.bot_message: #STOP BUTTON
|
|
||||||
if self.loading_spinner: self.chat_container.remove(self.loading_spinner)
|
|
||||||
if self.verify_if_image_can_be_used(): self.image_button.set_sensitive(True)
|
|
||||||
self.image_button.set_css_classes([])
|
|
||||||
self.image_button.get_child().set_icon_name("image-x-generic-symbolic")
|
|
||||||
self.attached_image = {"path": None, "base64": None}
|
|
||||||
self.chat_list_box.set_sensitive(True)
|
|
||||||
self.add_chat_button.set_sensitive(True)
|
|
||||||
self.export_chat_button.set_sensitive(True)
|
|
||||||
self.import_chat_button.set_sensitive(True)
|
|
||||||
# self.message_text_view.set_sensitive(True)
|
|
||||||
self.send_button.set_css_classes(["suggested-action"])
|
|
||||||
self.send_button.get_child().set_label("Send")
|
|
||||||
self.send_button.get_child().set_icon_name("send-to-symbolic")
|
|
||||||
self.bot_message = None
|
|
||||||
self.bot_message_box = None
|
|
||||||
self.bot_message_view = None
|
|
||||||
else:
|
|
||||||
if not self.message_text_view.get_buffer().get_text(self.message_text_view.get_buffer().get_start_iter(), self.message_text_view.get_buffer().get_end_iter(), False): return
|
|
||||||
current_model = self.model_drop_down.get_selected_item()
|
|
||||||
if current_model is None:
|
|
||||||
self.show_toast("info", 0, self.main_overlay)
|
|
||||||
return
|
|
||||||
formated_datetime = datetime.now().strftime("%Y/%m/%d %H:%M")
|
|
||||||
self.chats["chats"][self.chats["selected_chat"]]["messages"].append({
|
|
||||||
"role": "user",
|
|
||||||
"model": "User",
|
|
||||||
"date": formated_datetime,
|
|
||||||
"content": self.message_text_view.get_buffer().get_text(self.message_text_view.get_buffer().get_start_iter(), self.message_text_view.get_buffer().get_end_iter(), False)
|
|
||||||
})
|
|
||||||
data = {
|
|
||||||
"model": current_model.get_string(),
|
|
||||||
"messages": self.chats["chats"][self.chats["selected_chat"]]["messages"]
|
|
||||||
}
|
|
||||||
if self.verify_if_image_can_be_used() and self.attached_image["base64"] is not None:
|
|
||||||
data["messages"][-1]["images"] = [self.attached_image["base64"]]
|
|
||||||
# self.message_text_view.set_sensitive(False)
|
|
||||||
self.send_button.set_css_classes(["destructive-action"])
|
|
||||||
self.send_button.get_child().set_label("Stop")
|
|
||||||
self.send_button.get_child().set_icon_name("edit-delete-symbolic")
|
|
||||||
self.chat_list_box.set_sensitive(False)
|
|
||||||
self.add_chat_button.set_sensitive(False)
|
|
||||||
self.export_chat_button.set_sensitive(False)
|
|
||||||
self.import_chat_button.set_sensitive(False)
|
|
||||||
self.image_button.set_sensitive(False)
|
|
||||||
|
|
||||||
self.show_message(self.message_text_view.get_buffer().get_text(self.message_text_view.get_buffer().get_start_iter(), self.message_text_view.get_buffer().get_end_iter(), False), False, f"\n\n<small>{formated_datetime}</small>", self.attached_image["base64"])
|
|
||||||
self.message_text_view.get_buffer().set_text("", 0)
|
|
||||||
self.loading_spinner = Gtk.Spinner(spinning=True, margin_top=12, margin_bottom=12, hexpand=True)
|
|
||||||
self.chat_container.append(self.loading_spinner)
|
|
||||||
self.show_message("", True)
|
|
||||||
|
|
||||||
vadjustment = self.chat_window.get_vadjustment()
|
|
||||||
vadjustment.set_value(vadjustment.get_upper())
|
|
||||||
thread = threading.Thread(target=self.run_message, args=(data['messages'], data['model']))
|
|
||||||
thread.start()
|
|
||||||
|
|
||||||
def delete_model(self, dialog, task, model_name):
|
|
||||||
if dialog.choose_finish(task) == "delete":
|
|
||||||
response = simple_delete(self.ollama_url + "/api/delete", data={"name": model_name})
|
|
||||||
self.update_list_local_models()
|
|
||||||
if response['status'] == 'ok':
|
|
||||||
self.show_toast("good", 0, self.manage_models_overlay)
|
|
||||||
else:
|
|
||||||
self.manage_models_dialog.close()
|
|
||||||
self.connection_error()
|
|
||||||
|
|
||||||
def pull_model_update(self, data, model_name):
|
def pull_model_update(self, data, model_name):
|
||||||
if model_name in list(self.pulling_models.keys()):
|
if model_name in list(self.pulling_models.keys()):
|
||||||
GLib.idle_add(self.pulling_models[model_name].set_subtitle, data['status'] + (f" | {round(data['completed'] / data['total'] * 100, 2)}%" if 'completed' in data and 'total' in data else ""))
|
GLib.idle_add(self.pulling_models[model_name].set_subtitle, data['status'] + (f" | {round(data['completed'] / data['total'] * 100, 2)}%" if 'completed' in data and 'total' in data else ""))
|
||||||
@@ -467,111 +546,49 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
GLib.idle_add(self.pulling_model_list_box.set_visible, False)
|
GLib.idle_add(self.pulling_model_list_box.set_visible, False)
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
def pull_model(self, model_name, tag):
|
def pull_model_process(self, model):
|
||||||
data = {"name":f"{model_name}:{tag}"}
|
data = {"name":model}
|
||||||
response = stream_post(f"{self.ollama_url}/api/pull", data=json.dumps(data), callback=lambda data, model_name=f"{model_name}:{tag}": self.pull_model_update(data, model_name))
|
response = connection_handler.stream_post(f"{connection_handler.url}/api/pull", data=json.dumps(data), callback=lambda data, model_name=model: self.pull_model_update(data, model_name))
|
||||||
GLib.idle_add(self.update_list_local_models)
|
GLib.idle_add(self.update_list_local_models)
|
||||||
|
|
||||||
if response['status'] == 'ok':
|
if response['status'] == 'ok':
|
||||||
GLib.idle_add(self.show_notification, _("Task Complete"), _("Model '{}' pulled successfully.").format(f"{model_name}:{tag}"), True, Gio.ThemedIcon.new("emblem-ok-symbolic"))
|
GLib.idle_add(self.show_notification, _("Task Complete"), _("Model '{}' pulled successfully.").format(model), True, Gio.ThemedIcon.new("emblem-ok-symbolic"))
|
||||||
GLib.idle_add(self.show_toast, "good", 1, self.manage_models_overlay)
|
GLib.idle_add(self.show_toast, "good", 1, self.manage_models_overlay)
|
||||||
GLib.idle_add(self.pulling_models[f"{model_name}:{tag}"].get_parent().remove, self.pulling_models[f"{model_name}:{tag}"])
|
GLib.idle_add(self.pulling_models[model].get_parent().remove, self.pulling_models[model])
|
||||||
del self.pulling_models[f"{model_name}:{tag}"]
|
del self.pulling_models[model]
|
||||||
else:
|
else:
|
||||||
GLib.idle_add(self.show_notification, _("Pull Model Error"), _("Failed to pull model '{}' due to network error.").format(f"{model_name}:{tag}"), True, Gio.ThemedIcon.new("dialog-error-symbolic"))
|
GLib.idle_add(self.show_notification, _("Pull Model Error"), _("Failed to pull model '{}' due to network error.").format(model), True, Gio.ThemedIcon.new("dialog-error-symbolic"))
|
||||||
GLib.idle_add(self.pulling_models[f"{model_name}:{tag}"].get_parent().remove, self.pulling_models[f"{model_name}:{tag}"])
|
GLib.idle_add(self.pulling_models[model].get_parent().remove, self.pulling_models[model])
|
||||||
del self.pulling_models[f"{model_name}:{tag}"]
|
del self.pulling_models[model]
|
||||||
GLib.idle_add(self.manage_models_dialog.close)
|
GLib.idle_add(self.manage_models_dialog.close)
|
||||||
GLib.idle_add(self.connection_error)
|
GLib.idle_add(self.connection_error)
|
||||||
if len(list(self.pulling_models.keys())) == 0:
|
if len(list(self.pulling_models.keys())) == 0:
|
||||||
GLib.idle_add(self.pulling_model_list_box.set_visible, False)
|
GLib.idle_add(self.pulling_model_list_box.set_visible, False)
|
||||||
|
|
||||||
def stop_pull_model(self, dialog, task, model_name):
|
def pull_model(self, model):
|
||||||
if dialog.choose_finish(task) == "stop":
|
if model in list(self.pulling_models.keys()):
|
||||||
GLib.idle_add(self.pulling_models[model_name].get_parent().remove, self.pulling_models[model_name])
|
|
||||||
del self.pulling_models[model_name]
|
|
||||||
|
|
||||||
def stop_pull_model_dialog(self, model_name):
|
|
||||||
dialog = Adw.AlertDialog(
|
|
||||||
heading=_("Stop Model"),
|
|
||||||
body=_("Are you sure you want to stop pulling '{}'?").format(model_name),
|
|
||||||
close_response="cancel"
|
|
||||||
)
|
|
||||||
dialog.add_response("cancel", _("Cancel"))
|
|
||||||
dialog.add_response("stop", _("Stop"))
|
|
||||||
dialog.set_response_appearance("stop", Adw.ResponseAppearance.DESTRUCTIVE)
|
|
||||||
dialog.choose(
|
|
||||||
parent = self.manage_models_dialog,
|
|
||||||
cancellable = None,
|
|
||||||
callback = lambda dialog, task, model_name = model_name: self.stop_pull_model(dialog, task, model_name)
|
|
||||||
)
|
|
||||||
|
|
||||||
def pull_model_start(self, dialog, task, model_name, tag_drop_down):
|
|
||||||
if dialog.choose_finish(task) == "pull":
|
|
||||||
tag = tag_drop_down.get_selected_item().get_string()
|
|
||||||
if f"{model_name}:{tag}" in list(self.pulling_models.keys()):
|
|
||||||
self.show_toast("info", 3, self.manage_models_overlay)
|
self.show_toast("info", 3, self.manage_models_overlay)
|
||||||
return
|
return
|
||||||
if f"{model_name}:{tag}" in self.local_models:
|
if model in self.local_models:
|
||||||
self.show_toast("info", 4, self.manage_models_overlay)
|
self.show_toast("info", 4, self.manage_models_overlay)
|
||||||
return
|
return
|
||||||
#self.pull_model_status_page.set_description(f"{model_name}:{tag}")
|
|
||||||
self.pulling_model_list_box.set_visible(True)
|
self.pulling_model_list_box.set_visible(True)
|
||||||
model_row = Adw.ActionRow(
|
model_row = Adw.ActionRow(
|
||||||
title = f"{model_name}:{tag}",
|
title = model
|
||||||
subtitle = ""
|
|
||||||
)
|
)
|
||||||
thread = threading.Thread(target=self.pull_model, args=(model_name, tag))
|
thread = threading.Thread(target=self.pull_model_process, kwargs={"model": model})
|
||||||
self.pulling_models[f"{model_name}:{tag}"] = model_row
|
self.pulling_models[model] = model_row
|
||||||
button = Gtk.Button(
|
button = Gtk.Button(
|
||||||
icon_name = "media-playback-stop-symbolic",
|
icon_name = "media-playback-stop-symbolic",
|
||||||
vexpand = False,
|
vexpand = False,
|
||||||
valign = 3,
|
valign = 3,
|
||||||
css_classes = ["error"]
|
css_classes = ["error"]
|
||||||
)
|
)
|
||||||
button.connect("clicked", lambda button, model_name=f"{model_name}:{tag}" : self.stop_pull_model_dialog(model_name))
|
button.connect("clicked", lambda button, model_name=model : dialogs.stop_pull_model(self, model_name))
|
||||||
model_row.add_suffix(button)
|
model_row.add_suffix(button)
|
||||||
self.pulling_model_list_box.append(model_row)
|
self.pulling_model_list_box.append(model_row)
|
||||||
thread.start()
|
thread.start()
|
||||||
|
|
||||||
def model_delete_button_activate(self, model_name):
|
|
||||||
dialog = Adw.AlertDialog(
|
|
||||||
heading=_("Delete Model"),
|
|
||||||
body=_("Are you sure you want to delete '{}'?").format(model_name),
|
|
||||||
close_response="cancel"
|
|
||||||
)
|
|
||||||
dialog.add_response("cancel", _("Cancel"))
|
|
||||||
dialog.add_response("delete", _("Delete"))
|
|
||||||
dialog.set_response_appearance("delete", Adw.ResponseAppearance.DESTRUCTIVE)
|
|
||||||
dialog.choose(
|
|
||||||
parent = self.manage_models_dialog,
|
|
||||||
cancellable = None,
|
|
||||||
callback = lambda dialog, task, model_name = model_name: self.delete_model(dialog, task, model_name)
|
|
||||||
)
|
|
||||||
|
|
||||||
def model_pull_button_activate(self, model_name):
|
|
||||||
tag_list = Gtk.StringList()
|
|
||||||
for tag in available_models[model_name]['tags']:
|
|
||||||
tag_list.append(tag)
|
|
||||||
tag_drop_down = Gtk.DropDown(
|
|
||||||
enable_search=True,
|
|
||||||
model=tag_list
|
|
||||||
)
|
|
||||||
dialog = Adw.AlertDialog(
|
|
||||||
heading=_("Pull Model"),
|
|
||||||
body=_("Please select a tag to pull '{}'").format(model_name),
|
|
||||||
extra_child=tag_drop_down,
|
|
||||||
close_response="cancel"
|
|
||||||
)
|
|
||||||
dialog.add_response("cancel", _("Cancel"))
|
|
||||||
dialog.add_response("pull", _("Pull"))
|
|
||||||
dialog.set_response_appearance("pull", Adw.ResponseAppearance.SUGGESTED)
|
|
||||||
dialog.choose(
|
|
||||||
parent = self.manage_models_dialog,
|
|
||||||
cancellable = None,
|
|
||||||
callback = lambda dialog, task, model_name = model_name, tag_drop_down = tag_drop_down: self.pull_model_start(dialog, task, model_name, tag_drop_down)
|
|
||||||
)
|
|
||||||
|
|
||||||
def update_list_available_models(self):
|
def update_list_available_models(self):
|
||||||
self.available_model_list_box.remove_all()
|
self.available_model_list_box.remove_all()
|
||||||
for name, model_info in available_models.items():
|
for name, model_info in available_models.items():
|
||||||
@@ -592,58 +609,11 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
css_classes = ["accent"]
|
css_classes = ["accent"]
|
||||||
)
|
)
|
||||||
link_button.connect("clicked", lambda button=link_button, link=model_info["url"]: webbrowser.open(link))
|
link_button.connect("clicked", lambda button=link_button, link=model_info["url"]: webbrowser.open(link))
|
||||||
pull_button.connect("clicked", lambda button=pull_button, model_name=name: self.model_pull_button_activate(model_name))
|
pull_button.connect("clicked", lambda button=pull_button, model_name=name: dialogs.pull_model(self, model_name))
|
||||||
model.add_suffix(link_button)
|
model.add_suffix(link_button)
|
||||||
model.add_suffix(pull_button)
|
model.add_suffix(pull_button)
|
||||||
self.available_model_list_box.append(model)
|
self.available_model_list_box.append(model)
|
||||||
|
|
||||||
def manage_models_button_activate(self, button=None):
|
|
||||||
self.update_list_local_models()
|
|
||||||
self.manage_models_dialog.present(self)
|
|
||||||
|
|
||||||
def welcome_carousel_page_changed(self, carousel, index):
|
|
||||||
if index == 0: self.welcome_previous_button.set_sensitive(False)
|
|
||||||
else: self.welcome_previous_button.set_sensitive(True)
|
|
||||||
if index == carousel.get_n_pages()-1: self.welcome_next_button.set_label("Connect")
|
|
||||||
else: self.welcome_next_button.set_label("Next")
|
|
||||||
|
|
||||||
def welcome_previous_button_activate(self, button):
|
|
||||||
self.welcome_carousel.scroll_to(self.welcome_carousel.get_nth_page(self.welcome_carousel.get_position()-1), True)
|
|
||||||
|
|
||||||
def welcome_next_button_activate(self, button):
|
|
||||||
if button.get_label() == "Next": self.welcome_carousel.scroll_to(self.welcome_carousel.get_nth_page(self.welcome_carousel.get_position()+1), True)
|
|
||||||
else:
|
|
||||||
self.welcome_dialog.force_close()
|
|
||||||
if not self.verify_connection():
|
|
||||||
self.connection_error()
|
|
||||||
|
|
||||||
def clear_chat(self):
|
|
||||||
for widget in list(self.chat_container): self.chat_container.remove(widget)
|
|
||||||
self.chats["chats"][self.chats["selected_chat"]]["messages"] = []
|
|
||||||
|
|
||||||
def clear_chat_dialog_response(self, dialog, task):
|
|
||||||
if dialog.choose_finish(task) == "clear":
|
|
||||||
self.clear_chat()
|
|
||||||
self.save_history()
|
|
||||||
|
|
||||||
def clear_chat_dialog(self):
|
|
||||||
if self.bot_message is not None:
|
|
||||||
self.show_toast("info", 1, self.main_overlay)
|
|
||||||
return
|
|
||||||
dialog = Adw.AlertDialog(
|
|
||||||
heading=_("Clear Chat"),
|
|
||||||
body=_("Are you sure you want to clear the chat?"),
|
|
||||||
close_response="cancel"
|
|
||||||
)
|
|
||||||
dialog.add_response("cancel", _("Cancel"))
|
|
||||||
dialog.add_response("clear", _("Clear"))
|
|
||||||
dialog.set_response_appearance("clear", Adw.ResponseAppearance.DESTRUCTIVE)
|
|
||||||
dialog.choose(
|
|
||||||
parent = self,
|
|
||||||
cancellable = None,
|
|
||||||
callback = self.clear_chat_dialog_response
|
|
||||||
)
|
|
||||||
|
|
||||||
def save_history(self):
|
def save_history(self):
|
||||||
with open(os.path.join(self.config_dir, "chats.json"), "w+") as f:
|
with open(os.path.join(self.config_dir, "chats.json"), "w+") as f:
|
||||||
json.dump(self.chats, f, indent=4)
|
json.dump(self.chats, f, indent=4)
|
||||||
@@ -660,14 +630,13 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
|
|
||||||
def load_history(self):
|
def load_history(self):
|
||||||
if os.path.exists(os.path.join(self.config_dir, "chats.json")):
|
if os.path.exists(os.path.join(self.config_dir, "chats.json")):
|
||||||
self.clear_chat()
|
|
||||||
try:
|
try:
|
||||||
with open(os.path.join(self.config_dir, "chats.json"), "r") as f:
|
with open(os.path.join(self.config_dir, "chats.json"), "r") as f:
|
||||||
self.chats = json.load(f)
|
self.chats = json.load(f)
|
||||||
if "selected_chat" not in self.chats or self.chats["selected_chat"] not in self.chats["chats"]: self.chats["selected_chat"] = list(self.chats["chats"].keys())[0]
|
if "selected_chat" not in self.chats or self.chats["selected_chat"] not in self.chats["chats"]: self.chats["selected_chat"] = list(self.chats["chats"].keys())[0]
|
||||||
if len(list(self.chats["chats"].keys())) == 0: self.chats["chats"]["New Chat"] = {"messages": []}
|
if len(list(self.chats["chats"].keys())) == 0: self.chats["chats"][_("New Chat")] = {"messages": []}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.chats = {"chats": {"New Chat": {"messages": []}}, "selected_chat": "New Chat"}
|
self.chats = {"chats": {_("New Chat"): {"messages": []}}, "selected_chat": _("New Chat")}
|
||||||
self.load_history_into_chat()
|
self.load_history_into_chat()
|
||||||
|
|
||||||
def load_image(self, file_dialog, result):
|
def load_image(self, file_dialog, result):
|
||||||
@@ -695,141 +664,70 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.show_toast("error", 5, self.main_overlay)
|
self.show_toast("error", 5, self.main_overlay)
|
||||||
|
|
||||||
def remove_image(self, dialog, task):
|
def remove_image(self):
|
||||||
if dialog.choose_finish(task) == 'remove':
|
|
||||||
self.image_button.set_css_classes([])
|
self.image_button.set_css_classes([])
|
||||||
self.image_button.get_child().set_icon_name("image-x-generic-symbolic")
|
self.image_button.get_child().set_icon_name("image-x-generic-symbolic")
|
||||||
self.attached_image = {"path": None, "base64": None}
|
self.attached_image = {"path": None, "base64": None}
|
||||||
|
|
||||||
def open_image(self, button):
|
def generate_numbered_chat_name(self, chat_name) -> str:
|
||||||
if "destructive-action" in button.get_css_classes():
|
if chat_name in self.chats["chats"]:
|
||||||
dialog = Adw.AlertDialog(
|
for i in range(len(list(self.chats["chats"].keys()))):
|
||||||
heading=_("Remove Image"),
|
if chat_name + f" {i+1}" not in self.chats["chats"]:
|
||||||
body=_("Are you sure you want to remove image?"),
|
chat_name += f" {i+1}"
|
||||||
close_response="cancel"
|
break
|
||||||
)
|
return chat_name
|
||||||
dialog.add_response("cancel", _("Cancel"))
|
|
||||||
dialog.add_response("remove", _("Remove"))
|
|
||||||
dialog.set_response_appearance("remove", Adw.ResponseAppearance.DESTRUCTIVE)
|
|
||||||
dialog.choose(
|
|
||||||
parent = self,
|
|
||||||
cancellable = None,
|
|
||||||
callback = self.remove_image
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
file_dialog = Gtk.FileDialog(default_filter=self.file_filter_image)
|
|
||||||
file_dialog.open(self, None, self.load_image)
|
|
||||||
|
|
||||||
def chat_delete(self, dialog, task, chat_name):
|
def clear_chat(self):
|
||||||
if dialog.choose_finish(task) == "delete":
|
for widget in list(self.chat_container): self.chat_container.remove(widget)
|
||||||
|
self.chats["chats"][self.chats["selected_chat"]]["messages"] = []
|
||||||
|
self.save_history()
|
||||||
|
|
||||||
|
def delete_chat(self, chat_name):
|
||||||
del self.chats['chats'][chat_name]
|
del self.chats['chats'][chat_name]
|
||||||
self.save_history()
|
self.save_history()
|
||||||
self.update_chat_list()
|
self.update_chat_list()
|
||||||
if len(self.chats['chats'])==0:
|
if len(self.chats['chats'])==0:
|
||||||
self.chat_new()
|
self.chat_new()
|
||||||
|
|
||||||
def chat_delete_dialog(self, chat_name):
|
def rename_chat(self, old_chat_name, new_chat_name, label_element):
|
||||||
dialog = Adw.AlertDialog(
|
new_chat_name = self.generate_numbered_chat_name(new_chat_name)
|
||||||
heading=_("Delete Chat"),
|
|
||||||
body=_("Are you sure you want to delete '{}'?").format(chat_name),
|
|
||||||
close_response="cancel"
|
|
||||||
)
|
|
||||||
dialog.add_response("cancel", _("Cancel"))
|
|
||||||
dialog.add_response("delete", _("Delete"))
|
|
||||||
dialog.set_response_appearance("delete", Adw.ResponseAppearance.DESTRUCTIVE)
|
|
||||||
dialog.choose(
|
|
||||||
parent = self,
|
|
||||||
cancellable = None,
|
|
||||||
callback = lambda dialog, task, chat_name=chat_name: self.chat_delete(dialog, task, chat_name)
|
|
||||||
)
|
|
||||||
def chat_rename(self, dialog=None, task=None, old_chat_name:str="", entry=None):
|
|
||||||
if not entry: return
|
|
||||||
new_chat_name = entry.get_text()
|
|
||||||
if old_chat_name == new_chat_name: return
|
|
||||||
if new_chat_name and (not task or dialog.choose_finish(task) == "rename"):
|
|
||||||
dialog.force_close()
|
|
||||||
if new_chat_name in self.chats["chats"]: self.chat_rename_dialog(old_chat_name, f"The name '{new_chat_name}' is already in use", True)
|
|
||||||
else:
|
|
||||||
self.chats["chats"][new_chat_name] = self.chats["chats"][old_chat_name]
|
self.chats["chats"][new_chat_name] = self.chats["chats"][old_chat_name]
|
||||||
del self.chats["chats"][old_chat_name]
|
del self.chats["chats"][old_chat_name]
|
||||||
|
label_element.set_label(new_chat_name)
|
||||||
self.save_history()
|
self.save_history()
|
||||||
self.update_chat_list()
|
|
||||||
|
|
||||||
def chat_rename_dialog(self, chat_name:str, body:str, error:bool=False):
|
def new_chat(self, chat_name):
|
||||||
entry = Gtk.Entry(
|
chat_name = self.generate_numbered_chat_name(chat_name)
|
||||||
css_classes = ["error"] if error else None
|
|
||||||
)
|
|
||||||
dialog = Adw.AlertDialog(
|
|
||||||
heading=_("Rename Chat"),
|
|
||||||
body=body,
|
|
||||||
extra_child=entry,
|
|
||||||
close_response="cancel"
|
|
||||||
)
|
|
||||||
entry.connect("activate", lambda entry, dialog=dialog, old_chat_name=chat_name: self.chat_rename(dialog=dialog, old_chat_name=old_chat_name, entry=entry))
|
|
||||||
dialog.add_response("cancel", _("Cancel"))
|
|
||||||
dialog.add_response("rename", _("Rename"))
|
|
||||||
dialog.set_response_appearance("rename", Adw.ResponseAppearance.SUGGESTED)
|
|
||||||
dialog.choose(
|
|
||||||
parent = self,
|
|
||||||
cancellable = None,
|
|
||||||
callback = lambda dialog, task, old_chat_name=chat_name, entry=entry: self.chat_rename(dialog=dialog, task=task, old_chat_name=old_chat_name, entry=entry)
|
|
||||||
)
|
|
||||||
|
|
||||||
def chat_new(self, dialog=None, task=None, entry=None):
|
|
||||||
#if not entry: return
|
|
||||||
chat_name = None
|
|
||||||
if entry is not None: chat_name = entry.get_text()
|
|
||||||
if not chat_name:
|
|
||||||
chat_name=_("New Chat")
|
|
||||||
if chat_name in self.chats["chats"]:
|
|
||||||
for i in range(len(list(self.chats["chats"].keys()))):
|
|
||||||
if chat_name + f" {i+1}" not in self.chats["chats"]:
|
|
||||||
chat_name += f" {i+1}"
|
|
||||||
break
|
|
||||||
if not task or dialog.choose_finish(task) == "create":
|
|
||||||
if dialog is not None: dialog.force_close()
|
|
||||||
if chat_name in self.chats["chats"]: self.chat_new_dialog(_("The name '{}' is already in use").format(chat_name), True)
|
|
||||||
else:
|
|
||||||
self.chats["chats"][chat_name] = {"messages": []}
|
self.chats["chats"][chat_name] = {"messages": []}
|
||||||
self.chats["selected_chat"] = chat_name
|
#self.chats["selected_chat"] = chat_name
|
||||||
self.save_history()
|
self.save_history()
|
||||||
self.update_chat_list()
|
self.new_chat_element(chat_name, True)
|
||||||
self.load_history_into_chat()
|
|
||||||
|
|
||||||
def chat_new_dialog(self, body:str, error:bool=False):
|
def stop_pull_model(self, model_name):
|
||||||
entry = Gtk.Entry(
|
self.pulling_models[model_name].get_parent().remove(self.pulling_models[model_name])
|
||||||
css_classes = ["error"] if error else None
|
del self.pulling_models[model_name]
|
||||||
)
|
|
||||||
dialog = Adw.AlertDialog(
|
|
||||||
heading=_("Create Chat"),
|
|
||||||
body=body,
|
|
||||||
extra_child=entry,
|
|
||||||
close_response="cancel"
|
|
||||||
)
|
|
||||||
entry.connect("activate", lambda entry, dialog=dialog: self.chat_new(dialog=dialog, entry=entry))
|
|
||||||
dialog.add_response("cancel", _("Cancel"))
|
|
||||||
dialog.add_response("create", _("Create"))
|
|
||||||
dialog.set_response_appearance("rename", Adw.ResponseAppearance.SUGGESTED)
|
|
||||||
dialog.choose(
|
|
||||||
parent = self,
|
|
||||||
cancellable = None,
|
|
||||||
callback = lambda dialog, task, entry=entry: self.chat_new(dialog=dialog, task=task, entry=entry)
|
|
||||||
)
|
|
||||||
|
|
||||||
def update_chat_list(self):
|
def delete_model(self, model_name):
|
||||||
self.chat_list_box.remove_all()
|
response = connection_handler.simple_delete(connection_handler.url + "/api/delete", data={"name": model_name})
|
||||||
for name, content in self.chats['chats'].items():
|
self.update_list_local_models()
|
||||||
|
if response['status'] == 'ok':
|
||||||
|
self.show_toast("good", 0, self.manage_models_overlay)
|
||||||
|
else:
|
||||||
|
self.manage_models_dialog.close()
|
||||||
|
self.connection_error()
|
||||||
|
|
||||||
|
def new_chat_element(self, chat_name:str, select:bool):
|
||||||
chat_content = Gtk.Box(
|
chat_content = Gtk.Box(
|
||||||
spacing = 6,
|
spacing=6
|
||||||
)
|
)
|
||||||
chat_row = Gtk.ListBoxRow(
|
chat_row = Gtk.ListBoxRow(
|
||||||
css_classes = ["chat_row"],
|
css_classes = ["chat_row"],
|
||||||
height_request = 45,
|
height_request = 45,
|
||||||
child = chat_content,
|
child = chat_content,
|
||||||
name = name
|
name = chat_name
|
||||||
)
|
)
|
||||||
chat_label = Gtk.Label(
|
chat_label = Gtk.Label(
|
||||||
label=name,
|
label=chat_name,
|
||||||
hexpand=True,
|
hexpand=True,
|
||||||
halign=1
|
halign=1
|
||||||
)
|
)
|
||||||
@@ -839,94 +737,47 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
valign = 3,
|
valign = 3,
|
||||||
css_classes = ["error", "flat"]
|
css_classes = ["error", "flat"]
|
||||||
)
|
)
|
||||||
button_delete.connect("clicked", lambda button, chat_name=name: self.chat_delete_dialog(chat_name=chat_name))
|
button_delete.connect("clicked", lambda button, chat_name=chat_name: dialogs.delete_chat(self, chat_name))
|
||||||
button_rename = Gtk.Button(
|
button_rename = Gtk.Button(
|
||||||
icon_name = "document-edit-symbolic",
|
icon_name = "document-edit-symbolic",
|
||||||
vexpand = False,
|
vexpand = False,
|
||||||
valign = 3,
|
valign = 3,
|
||||||
css_classes = ["accent", "flat"]
|
css_classes = ["accent", "flat"]
|
||||||
)
|
)
|
||||||
button_rename.connect("clicked", lambda button, chat_name=name: self.chat_rename_dialog(chat_name=chat_name, body=f"Renaming '{chat_name}'", error=False))
|
button_rename.connect("clicked", lambda button, chat_name=chat_name, label_element=chat_label: dialogs.rename_chat(self, chat_name, label_element))
|
||||||
|
|
||||||
chat_content.append(chat_label)
|
chat_content.append(chat_label)
|
||||||
chat_content.append(button_delete)
|
chat_content.append(button_delete)
|
||||||
chat_content.append(button_rename)
|
chat_content.append(button_rename)
|
||||||
self.chat_list_box.append(chat_row)
|
self.chat_list_box.append(chat_row)
|
||||||
if name==self.chats["selected_chat"]: self.chat_list_box.select_row(chat_row)
|
if select: self.chat_list_box.select_row(chat_row)
|
||||||
|
|
||||||
def chat_changed(self, listbox, row):
|
def update_chat_list(self):
|
||||||
if row and row.get_name() != self.chats["selected_chat"]:
|
self.chat_list_box.remove_all()
|
||||||
self.chats["selected_chat"] = row.get_name()
|
for name, content in self.chats['chats'].items():
|
||||||
self.load_history_into_chat()
|
self.new_chat_element(name, self.chats["selected_chat"] == name)
|
||||||
if len(self.chats["chats"][self.chats["selected_chat"]]["messages"]) > 0:
|
|
||||||
for i in range(self.model_string_list.get_n_items()):
|
|
||||||
if self.model_string_list.get_string(i) == self.chats["chats"][self.chats["selected_chat"]]["messages"][-1]["model"]:
|
|
||||||
self.model_drop_down.set_selected(i)
|
|
||||||
break
|
|
||||||
|
|
||||||
def show_preferences_dialog(self):
|
def show_preferences_dialog(self):
|
||||||
self.preferences_dialog.present(self)
|
self.preferences_dialog.present(self)
|
||||||
|
|
||||||
def start_instance(self):
|
def connect_remote(self, url):
|
||||||
self.ollama_instance = subprocess.Popen(["/app/bin/ollama", "serve"], env={**os.environ, 'OLLAMA_HOST': f"127.0.0.1:{self.local_ollama_port}", "HOME": self.data_dir}, stderr=subprocess.PIPE, text=True)
|
connection_handler.url = url
|
||||||
print("Starting Alpaca's Ollama instance...")
|
self.remote_url = connection_handler.url
|
||||||
sleep(1)
|
|
||||||
while True:
|
|
||||||
err = self.ollama_instance.stderr.readline()
|
|
||||||
if err == '' and self.ollama_instance.poll() is not None:
|
|
||||||
break
|
|
||||||
if 'msg="inference compute"' in err: #Ollama outputs a line with this when it finishes loading, yeah
|
|
||||||
break
|
|
||||||
print("Started Alpaca's Ollama instance")
|
|
||||||
|
|
||||||
def stop_instance(self):
|
|
||||||
self.ollama_instance.kill()
|
|
||||||
print("Stopped Alpaca's Ollama instance")
|
|
||||||
|
|
||||||
def restart_instance(self):
|
|
||||||
if self.ollama_instance is not None: self.stop_instance()
|
|
||||||
start_instance(self)
|
|
||||||
|
|
||||||
def reconnect_remote(self, dialog, task=None, entry=None):
|
|
||||||
response = dialog.choose_finish(task)
|
|
||||||
dialog.force_close()
|
|
||||||
if not task or response == "remote":
|
|
||||||
self.ollama_url = entry.get_text()
|
|
||||||
self.remote_url = self.ollama_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()
|
||||||
elif response == "local":
|
|
||||||
|
def connect_local(self):
|
||||||
self.run_remote = False
|
self.run_remote = False
|
||||||
self.ollama_url = f"http://127.0.0.1:{self.local_ollama_port}"
|
connection_handler.url = f"http://127.0.0.1:{local_instance.port}"
|
||||||
self.start_instance()
|
local_instance.start(self.data_dir)
|
||||||
if self.verify_connection() == False: self.connection_error()
|
if self.verify_connection() == False: self.connection_error()
|
||||||
else: self.remote_connection_switch.set_active(False)
|
else: self.remote_connection_switch.set_active(False)
|
||||||
elif response == "close":
|
|
||||||
self.destroy()
|
|
||||||
|
|
||||||
def connection_error(self):
|
def connection_error(self):
|
||||||
if self.run_remote:
|
if self.run_remote:
|
||||||
entry = Gtk.Entry(
|
dialogs.reconnect_remote(self)
|
||||||
css_classes = ["error"],
|
|
||||||
text = self.ollama_url
|
|
||||||
)
|
|
||||||
dialog = Adw.AlertDialog(
|
|
||||||
heading=_("Connection Error"),
|
|
||||||
body=_("The remote instance has disconnected"),
|
|
||||||
extra_child=entry
|
|
||||||
)
|
|
||||||
entry.connect("activate", lambda entry, dialog=dialog: self.reconnect_remote(dialog=dialog, entry=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.choose(
|
|
||||||
parent = self,
|
|
||||||
cancellable = None,
|
|
||||||
callback = lambda dialog, task, entry=entry: self.reconnect_remote(dialog=dialog, task=task, entry=entry)
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
self.restart_instance()
|
local_instance.stop()
|
||||||
|
local_instance.start(self.data_dir)
|
||||||
self.show_toast("error", 7, self.main_overlay)
|
self.show_toast("error", 7, self.main_overlay)
|
||||||
|
|
||||||
def connection_switched(self):
|
def connection_switched(self):
|
||||||
@@ -934,23 +785,14 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
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:
|
||||||
self.ollama_url = self.remote_url
|
connection_handler.url = self.remote_url
|
||||||
if self.verify_connection() == False: self.connection_error()
|
if self.verify_connection() == False: self.connection_error()
|
||||||
else: self.stop_instance()
|
else: local_instance.stop()
|
||||||
else:
|
else:
|
||||||
self.ollama_url = f"http://127.0.0.1:{self.local_ollama_port}"
|
connection_handler.url = f"http://127.0.0.1:{local_instance.port}"
|
||||||
self.start_instance()
|
local_instance.start(self.data_dir)
|
||||||
if self.verify_connection() == False: self.connection_error()
|
if self.verify_connection() == False: self.connection_error()
|
||||||
self.update_list_available_models()
|
self.update_list_available_models()
|
||||||
self.update_list_local_models()
|
|
||||||
|
|
||||||
def change_remote_url(self, entry):
|
|
||||||
self.remote_url = entry.get_text()
|
|
||||||
if self.run_remote:
|
|
||||||
self.ollama_url = self.remote_url
|
|
||||||
if self.verify_connection() == False:
|
|
||||||
entry.set_css_classes(["error"])
|
|
||||||
self.show_toast("error", 1, self.preferences_dialog)
|
|
||||||
|
|
||||||
def on_replace_contents(self, file, result):
|
def on_replace_contents(self, file, result):
|
||||||
file.replace_contents_finish(result)
|
file.replace_contents_finish(result)
|
||||||
@@ -999,46 +841,36 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
|||||||
GtkSource.init()
|
GtkSource.init()
|
||||||
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('clear', lambda *_: dialogs.clear_chat(self), ['<primary>e'])
|
||||||
self.get_application().create_action('send', lambda *_: self.send_message(self), ['Return'])
|
self.get_application().create_action('send', lambda *_: self.send_message(self), ['Return'])
|
||||||
self.manage_models_button.connect("clicked", self.manage_models_button_activate)
|
self.add_chat_button.connect("clicked", lambda button : dialogs.new_chat(self))
|
||||||
self.send_button.connect("clicked", self.send_message)
|
|
||||||
self.image_button.connect("clicked", self.open_image)
|
|
||||||
self.add_chat_button.connect("clicked", lambda button : self.chat_new_dialog("Enter name for new chat", False))
|
|
||||||
self.set_default_widget(self.send_button)
|
|
||||||
self.model_drop_down.connect("notify", self.verify_if_image_can_be_used)
|
|
||||||
self.chat_list_box.connect("row-selected", self.chat_changed)
|
|
||||||
self.welcome_carousel.connect("page-changed", self.welcome_carousel_page_changed)
|
|
||||||
self.welcome_previous_button.connect("clicked", self.welcome_previous_button_activate)
|
|
||||||
self.welcome_next_button.connect("clicked", self.welcome_next_button_activate)
|
|
||||||
|
|
||||||
self.export_chat_button.connect("clicked", lambda button : self.export_current_chat())
|
self.export_chat_button.connect("clicked", lambda button : self.export_current_chat())
|
||||||
self.import_chat_button.connect("clicked", lambda button : self.import_chat())
|
self.import_chat_button.connect("clicked", lambda button : self.import_chat())
|
||||||
#Preferences
|
|
||||||
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_entry.connect("apply", self.change_remote_url)
|
|
||||||
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())
|
||||||
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") as f:
|
with open(os.path.join(self.config_dir, "server.json"), "r") as f:
|
||||||
data = json.load(f)
|
data = json.load(f)
|
||||||
self.run_remote = data['run_remote']
|
self.run_remote = data['run_remote']
|
||||||
self.local_ollama_port = data['local_port']
|
local_instance.port = data['local_port']
|
||||||
self.remote_url = data['remote_url']
|
self.remote_url = data['remote_url']
|
||||||
self.run_on_background = data['run_on_background']
|
self.run_on_background = data['run_on_background']
|
||||||
self.background_switch.set_active(self.run_on_background)
|
self.background_switch.set_active(self.run_on_background)
|
||||||
self.set_hide_on_close(self.run_on_background)
|
self.set_hide_on_close(self.run_on_background)
|
||||||
self.remote_connection_entry.set_text(self.remote_url)
|
self.remote_connection_entry.set_text(self.remote_url)
|
||||||
if self.run_remote:
|
if self.run_remote:
|
||||||
self.ollama_url = data['remote_url']
|
connection_handler.url = data['remote_url']
|
||||||
self.remote_connection_switch.set_active(True)
|
self.remote_connection_switch.set_active(True)
|
||||||
else:
|
else:
|
||||||
self.remote_connection_switch.set_active(False)
|
self.remote_connection_switch.set_active(False)
|
||||||
self.ollama_url = f"http://127.0.0.1:{self.local_ollama_port}"
|
connection_handler.url = f"http://127.0.0.1:{local_instance.port}"
|
||||||
self.start_instance()
|
local_instance.start(self.data_dir)
|
||||||
else:
|
else:
|
||||||
self.start_instance()
|
local_instance.start(self.data_dir)
|
||||||
self.ollama_url = f"http://127.0.0.1:{self.local_ollama_port}"
|
connection_handler.url = f"http://127.0.0.1:{local_instance.port}"
|
||||||
self.first_time_setup = True
|
|
||||||
self.welcome_dialog.present(self)
|
self.welcome_dialog.present(self)
|
||||||
if self.verify_connection() is False and self.run_remote == False: self.connection_error()
|
if self.verify_connection() is False and self.run_remote == False: self.connection_error()
|
||||||
self.update_list_available_models()
|
self.update_list_available_models()
|
||||||
|
|||||||
108
src/window.ui
108
src/window.ui
@@ -27,8 +27,6 @@
|
|||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<property name="content">
|
<property name="content">
|
||||||
<object class="AdwToastOverlay" id="main_overlay">
|
|
||||||
<child>
|
|
||||||
<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">
|
<property name="sidebar">
|
||||||
@@ -38,7 +36,7 @@
|
|||||||
<child type="start">
|
<child type="start">
|
||||||
<object class="GtkButton" id="add_chat_button">
|
<object class="GtkButton" id="add_chat_button">
|
||||||
<property name="tooltip-text" translatable="yes">New chat</property>
|
<property name="tooltip-text" translatable="yes">New chat</property>
|
||||||
<property name="icon-name">tab-new-symbolic</property>
|
<property name="icon-name">chat-message-new-symbolic</property>
|
||||||
<style>
|
<style>
|
||||||
<class name="flat"/>
|
<class name="flat"/>
|
||||||
</style>
|
</style>
|
||||||
@@ -70,6 +68,7 @@
|
|||||||
<property name="hexpand">true</property>
|
<property name="hexpand">true</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkListBox" id="chat_list_box">
|
<object class="GtkListBox" id="chat_list_box">
|
||||||
|
<signal name="row-selected" handler="chat_changed"/>
|
||||||
<property name="selection-mode">single</property>
|
<property name="selection-mode">single</property>
|
||||||
<style>
|
<style>
|
||||||
<class name="navigation-sidebar"></class>
|
<class name="navigation-sidebar"></class>
|
||||||
@@ -97,6 +96,7 @@
|
|||||||
<property name="spacing">12</property>
|
<property name="spacing">12</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkDropDown" id="model_drop_down">
|
<object class="GtkDropDown" id="model_drop_down">
|
||||||
|
<signal name="notify" handler="verify_if_image_can_be_used"/>
|
||||||
<property name="enable-search">true</property>
|
<property name="enable-search">true</property>
|
||||||
<property name="model">
|
<property name="model">
|
||||||
<object class="GtkStringList" id="model_string_list">
|
<object class="GtkStringList" id="model_string_list">
|
||||||
@@ -108,6 +108,7 @@
|
|||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkButton" id="manage_models_button">
|
<object class="GtkButton" id="manage_models_button">
|
||||||
|
<signal name="clicked" handler="manage_models_button_activate"/>
|
||||||
<property name="tooltip-text" translatable="yes">Manage models</property>
|
<property name="tooltip-text" translatable="yes">Manage models</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="AdwButtonContent">
|
<object class="AdwButtonContent">
|
||||||
@@ -133,6 +134,8 @@
|
|||||||
<property name="orientation">1</property>
|
<property name="orientation">1</property>
|
||||||
<property name="vexpand">true</property>
|
<property name="vexpand">true</property>
|
||||||
<property name="hexpand">true</property>
|
<property name="hexpand">true</property>
|
||||||
|
<child>
|
||||||
|
<object class="AdwToastOverlay" id="main_overlay">
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkScrolledWindow" id="chat_window">
|
<object class="GtkScrolledWindow" id="chat_window">
|
||||||
<property name="propagate-natural-height">true</property>
|
<property name="propagate-natural-height">true</property>
|
||||||
@@ -162,7 +165,8 @@
|
|||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="AdwClamp">
|
<object class="AdwClamp">
|
||||||
<property name="maximum-size">1000</property>
|
<property name="maximum-size">1000</property>
|
||||||
@@ -208,6 +212,7 @@
|
|||||||
<property name="spacing">12</property>
|
<property name="spacing">12</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkButton" id="send_button">
|
<object class="GtkButton" id="send_button">
|
||||||
|
<signal name="clicked" handler="send_message"/>
|
||||||
<style>
|
<style>
|
||||||
<class name="suggested-action"/>
|
<class name="suggested-action"/>
|
||||||
</style>
|
</style>
|
||||||
@@ -221,6 +226,7 @@
|
|||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkButton" id="image_button">
|
<object class="GtkButton" id="image_button">
|
||||||
|
<signal name="clicked" handler="open_image"/>
|
||||||
<property name="sensitive">false</property>
|
<property name="sensitive">false</property>
|
||||||
<property name="tooltip-text" translatable="yes">Only available on selected models</property>
|
<property name="tooltip-text" translatable="yes">Only available on selected models</property>
|
||||||
<child>
|
<child>
|
||||||
@@ -244,8 +250,6 @@
|
|||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</property>
|
</property>
|
||||||
<object class="AdwPreferencesDialog" id="preferences_dialog">
|
<object class="AdwPreferencesDialog" id="preferences_dialog">
|
||||||
<property name="can-close">true</property>
|
<property name="can-close">true</property>
|
||||||
@@ -267,6 +271,7 @@
|
|||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="AdwEntryRow" id="remote_connection_entry">
|
<object class="AdwEntryRow" id="remote_connection_entry">
|
||||||
|
<signal name="apply" handler="change_remote_url"/>
|
||||||
<property name="title" translatable="yes">URL of remote instance</property>
|
<property name="title" translatable="yes">URL of remote instance</property>
|
||||||
<property name="show-apply-button">true</property>
|
<property name="show-apply-button">true</property>
|
||||||
</object>
|
</object>
|
||||||
@@ -368,6 +373,7 @@
|
|||||||
<property name="margin-bottom">5</property>
|
<property name="margin-bottom">5</property>
|
||||||
<child type="start">
|
<child type="start">
|
||||||
<object class="GtkButton" id="welcome_previous_button">
|
<object class="GtkButton" id="welcome_previous_button">
|
||||||
|
<signal name="clicked" handler="welcome_previous_button_activate"/>
|
||||||
<property name="tooltip-text" translatable="yes">Previous</property>
|
<property name="tooltip-text" translatable="yes">Previous</property>
|
||||||
<property name="label">Previous</property>
|
<property name="label">Previous</property>
|
||||||
<property name="sensitive">false</property>
|
<property name="sensitive">false</property>
|
||||||
@@ -383,6 +389,7 @@
|
|||||||
</child>
|
</child>
|
||||||
<child type="end">
|
<child type="end">
|
||||||
<object class="GtkButton" id="welcome_next_button">
|
<object class="GtkButton" id="welcome_next_button">
|
||||||
|
<signal name="clicked" handler="welcome_next_button_activate"/>
|
||||||
<property name="tooltip-text" translatable="yes">Next</property>
|
<property name="tooltip-text" translatable="yes">Next</property>
|
||||||
<property name="label">Next</property>
|
<property name="label">Next</property>
|
||||||
<style>
|
<style>
|
||||||
@@ -395,6 +402,7 @@
|
|||||||
|
|
||||||
<child>
|
<child>
|
||||||
<object class="AdwCarousel" id="welcome_carousel">
|
<object class="AdwCarousel" id="welcome_carousel">
|
||||||
|
<signal name="page-changed" handler="welcome_carousel_page_changed"/>
|
||||||
<property name="hexpand">true</property>
|
<property name="hexpand">true</property>
|
||||||
<property name="vexpand">true</property>
|
<property name="vexpand">true</property>
|
||||||
<property name="allow-long-swipes">true</property>
|
<property name="allow-long-swipes">true</property>
|
||||||
@@ -433,6 +441,94 @@
|
|||||||
<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.</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="pull_featured_model"/>
|
||||||
|
<property name="vexpand">false</property>
|
||||||
|
<property name="icon-name">folder-download-symbolic</property>
|
||||||
|
<property name="valign">3</property>
|
||||||
|
<style>
|
||||||
|
<class name="accent"/>
|
||||||
|
</style>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="AdwActionRow">
|
||||||
|
<property name="title" translatable="no">Gemma</property>
|
||||||
|
<property name="subtitle" translatable="yes">Built by Google DeepMind</property>
|
||||||
|
<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>
|
||||||
|
<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="pull_featured_model"/>
|
||||||
|
<property name="vexpand">false</property>
|
||||||
|
<property name="icon-name">folder-download-symbolic</property>
|
||||||
|
<property name="valign">3</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="pull_featured_model"/>
|
||||||
|
<property name="vexpand">false</property>
|
||||||
|
<property name="icon-name">folder-download-symbolic</property>
|
||||||
|
<property name="valign">3</property>
|
||||||
|
<style>
|
||||||
|
<class name="accent"/>
|
||||||
|
</style>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ echo "Preparing template..."
|
|||||||
xgettext --output=po/alpaca.pot --files-from=po/POTFILES
|
xgettext --output=po/alpaca.pot --files-from=po/POTFILES
|
||||||
echo "Updating Spanish..."
|
echo "Updating Spanish..."
|
||||||
msgmerge -U po/es.po po/alpaca.pot
|
msgmerge -U po/es.po po/alpaca.pot
|
||||||
echo "Updating Russian..."
|
#echo "Updating Russian..."
|
||||||
msgmerge -U po/ru.po po/alpaca.pot
|
#msgmerge -U po/ru.po po/alpaca.pot
|
||||||
echo "Updating Brazilian Portuguese"
|
echo "Updating Brazilian Portuguese"
|
||||||
msgmerge -U po/pt_BR.po po/alpaca.pot
|
msgmerge -U po/pt_BR.po po/alpaca.pot
|
||||||
|
|||||||
Reference in New Issue
Block a user