Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc2df81c75 | ||
|
|
ddc50ce621 | ||
|
|
701fae92a0 | ||
|
|
f3d22219b4 | ||
|
|
0027cf7c59 | ||
|
|
b6c45fc346 | ||
|
|
e6f2902bd1 | ||
|
|
1190e367bb | ||
|
|
d619f55ff2 | ||
|
|
605d837716 | ||
|
|
3aa57c6d2f | ||
|
|
1cf2f04b06 | ||
|
|
780de2b753 | ||
|
|
e19511469d | ||
|
|
bedf1a9bc5 |
@@ -22,7 +22,8 @@ Alpaca is an [Ollama](https://github.com/ollama/ollama) client where you can man
|
||||
- Delete messages
|
||||
|
||||
## 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
|
||||
- Snap Package (maybe)
|
||||
|
||||
|
||||
@@ -5,15 +5,16 @@
|
||||
<project_license>GPL-3.0-or-later</project_license>
|
||||
<launchable type="desktop-id">com.jeffser.Alpaca.desktop</launchable>
|
||||
<name>Alpaca</name>
|
||||
<summary>Chat with local AI models</summary>
|
||||
<summary>Chat with local AI models powered by Ollama</summary>
|
||||
<description>
|
||||
<p>An Ollama client</p>
|
||||
<p>Features</p>
|
||||
<ul>
|
||||
<li>Built in Ollama instance</li>
|
||||
<li>Talk to multiple models in the same conversation</li>
|
||||
<li>Pull and delete models from the app</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>
|
||||
</ul>
|
||||
<p>Disclaimer</p>
|
||||
@@ -36,6 +37,8 @@
|
||||
</requires>
|
||||
<recommends>
|
||||
<control>keyboard</control>
|
||||
<control>pointing</control>
|
||||
<control>touch</control>
|
||||
</recommends>
|
||||
<branding>
|
||||
<color type="primary" scheme_preference="light">#8cdef5</color>
|
||||
@@ -60,6 +63,17 @@
|
||||
<url type="homepage">https://github.com/Jeffser/Alpaca</url>
|
||||
<url type="donation">https://github.com/sponsors/Jeffser</url>
|
||||
<releases>
|
||||
<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">
|
||||
<url type="details">https://github.com/Jeffser/Alpaca/releases/tag/0.8.1</url>
|
||||
<description>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
project('Alpaca',
|
||||
version: '0.8.1',
|
||||
version: '0.8.5',
|
||||
meson_version: '>= 0.62.0',
|
||||
default_options: [ 'warning_level=2', 'werror=false', ],
|
||||
)
|
||||
|
||||
565
po/alpaca.pot
565
po/alpaca.pot
File diff suppressed because it is too large
Load Diff
647
po/pt_BR.po
647
po/pt_BR.po
File diff suppressed because it is too large
Load Diff
688
po/pt_BR.po~
688
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 ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-05-24 18:50-0600\n"
|
||||
"PO-Revision-Date: 2024-05-22 19:33+0800\n"
|
||||
"POT-Creation-Date: 2024-05-25 10:21+0800\n"
|
||||
"PO-Revision-Date: 2024-05-25 10:44+0800\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"Language: ru_RU\n"
|
||||
@@ -14,752 +14,312 @@ msgstr ""
|
||||
"X-Poedit-Basepath: ../src\n"
|
||||
"X-Poedit-SearchPath-0: .\n"
|
||||
|
||||
#: data/com.jeffser.Alpaca.desktop.in:3
|
||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:7
|
||||
#, fuzzy
|
||||
msgid "Alpaca"
|
||||
msgstr "О Программе"
|
||||
|
||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:8
|
||||
#, fuzzy
|
||||
msgid "Chat with local AI models"
|
||||
msgstr "Не удалось перечислить локальные модели"
|
||||
|
||||
#: data/com.jeffser.Alpaca.metainfo.xml.in:10
|
||||
#, fuzzy
|
||||
msgid "An Ollama client"
|
||||
msgstr "Веб-сайт Ollama"
|
||||
|
||||
#: 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
|
||||
#: gtk/help-overlay.ui:11
|
||||
msgctxt "shortcut window"
|
||||
msgid "General"
|
||||
msgstr "Общие"
|
||||
|
||||
#: gtk/help-overlay.ui:14
|
||||
msgctxt "shortcut window"
|
||||
msgid "Show Shortcuts"
|
||||
msgstr "Показывать комбинации клавиш"
|
||||
|
||||
#: gtk/help-overlay.ui:20
|
||||
msgctxt "shortcut window"
|
||||
msgid "Quit"
|
||||
msgstr "Выйти"
|
||||
|
||||
#: window.py:57 window.py:783
|
||||
msgid "New Chat"
|
||||
msgstr "Новый Чат"
|
||||
|
||||
#: src/window.py:102
|
||||
#: window.py:102
|
||||
msgid "An error occurred"
|
||||
msgstr "Произошла ошибка"
|
||||
|
||||
#: src/window.py:103
|
||||
#: window.py:103
|
||||
msgid "Failed to connect to server"
|
||||
msgstr "Не удалось подключиться к серверу"
|
||||
|
||||
#: src/window.py:104
|
||||
#: window.py:104
|
||||
msgid "Could not list local models"
|
||||
msgstr "Не удалось перечислить локальные модели"
|
||||
|
||||
#: src/window.py:105
|
||||
#: window.py:105
|
||||
msgid "Could not delete model"
|
||||
msgstr "Не удалось удалить модель"
|
||||
|
||||
#: src/window.py:106
|
||||
#: window.py:106
|
||||
msgid "Could not pull model"
|
||||
msgstr "Не удалось извлечь модель"
|
||||
|
||||
#: src/window.py:107
|
||||
#: window.py:107
|
||||
msgid "Cannot open image"
|
||||
msgstr "Не удается открыть изображение"
|
||||
|
||||
#: src/window.py:108
|
||||
#: window.py:108
|
||||
msgid "Cannot delete chat because it's the only one left"
|
||||
msgstr "Не удается удалить чат, потому что он единственный оставшийся"
|
||||
|
||||
#: src/window.py:109
|
||||
#: window.py:109
|
||||
msgid "There was an error with the local Ollama instance, so it has been reset"
|
||||
msgstr ""
|
||||
"Произошла ошибка с локальным экземпляром Ollama, поэтому он был сброшен"
|
||||
|
||||
#: src/window.py:112
|
||||
#: window.py:112
|
||||
msgid "Please select a model before chatting"
|
||||
msgstr "Пожалуйста, выберите модель перед началом общения"
|
||||
|
||||
#: src/window.py:113
|
||||
#: window.py:113
|
||||
msgid "Chat cannot be cleared while receiving a message"
|
||||
msgstr "Чат не может быть удален при получении сообщения"
|
||||
|
||||
#: src/window.py:114
|
||||
#: window.py:114
|
||||
msgid "That tag is already being pulled"
|
||||
msgstr "Этот тег уже удален"
|
||||
|
||||
#: src/window.py:115
|
||||
#, fuzzy
|
||||
#: window.py:115
|
||||
msgid "That tag has been pulled already"
|
||||
msgstr "Этот тег уже был удален"
|
||||
|
||||
#: src/window.py:118
|
||||
#: window.py:118
|
||||
msgid "Model deleted successfully"
|
||||
msgstr "Модель успешно удалена"
|
||||
|
||||
#: src/window.py:119
|
||||
#: window.py:119
|
||||
msgid "Model pulled successfully"
|
||||
msgstr "Модель успешно извлечена"
|
||||
|
||||
#: src/window.py:120
|
||||
#: window.py:120
|
||||
msgid "Chat exported successfully"
|
||||
msgstr "Чат успешно экспортирован"
|
||||
|
||||
#: src/window.py:121
|
||||
#: window.py:121
|
||||
msgid "Chat imported successfully"
|
||||
msgstr "Чат успешно импортирован"
|
||||
|
||||
#: src/window.py:476
|
||||
#: window.py:476
|
||||
msgid "Task Complete"
|
||||
msgstr "Задача выполнена"
|
||||
|
||||
#: src/window.py:476
|
||||
#: window.py:476
|
||||
msgid "Model '{}' pulled successfully."
|
||||
msgstr "Модель '{}' успешно извлечена."
|
||||
|
||||
#: src/window.py:481
|
||||
#: window.py:481
|
||||
msgid "Pull Model Error"
|
||||
msgstr "Ошибка Извлечения Модели"
|
||||
|
||||
#: src/window.py:481
|
||||
#: window.py:481
|
||||
msgid "Failed to pull model '{}' due to network error."
|
||||
msgstr "Не удалось извлечь модель '{}' из-за сетевой ошибки."
|
||||
|
||||
#: src/window.py:496
|
||||
#: window.py:496
|
||||
msgid "Stop Model"
|
||||
msgstr "Остановить Модель"
|
||||
|
||||
#: src/window.py:497
|
||||
#: window.py:497
|
||||
msgid "Are you sure you want to stop pulling '{}'?"
|
||||
msgstr "Вы уверены, что хотите прекратить извлечение '{}'?"
|
||||
|
||||
#: src/window.py:500 src/window.py:543 src/window.py:566 src/window.py:638
|
||||
#: src/window.py:711 src/window.py:737 src/window.py:769 src/window.py:810
|
||||
#: window.py:500 window.py:543 window.py:566 window.py:638 window.py:711
|
||||
#: window.py:737 window.py:769 window.py:810
|
||||
msgid "Cancel"
|
||||
msgstr "Отмена"
|
||||
|
||||
#: src/window.py:501
|
||||
#: window.py:501
|
||||
msgid "Stop"
|
||||
msgstr "Стоп"
|
||||
|
||||
#: src/window.py:539
|
||||
#: window.py:539
|
||||
msgid "Delete Model"
|
||||
msgstr "Удалить Модель"
|
||||
|
||||
#: src/window.py:540 src/window.py:734
|
||||
#: window.py:540 window.py:734
|
||||
msgid "Are you sure you want to delete '{}'?"
|
||||
msgstr "Вы уверены, что хотите удалить '{}'?"
|
||||
|
||||
#: src/window.py:544 src/window.py:738
|
||||
#: window.py:544 window.py:738
|
||||
msgid "Delete"
|
||||
msgstr "Удалить"
|
||||
|
||||
#: src/window.py:561
|
||||
#: window.py:561
|
||||
msgid "Pull Model"
|
||||
msgstr "Извлечение модели"
|
||||
|
||||
#: src/window.py:562
|
||||
#: window.py:562
|
||||
msgid "Please select a tag to pull '{}'"
|
||||
msgstr "Пожалуйста, выберите тег для извлечения '{}'"
|
||||
|
||||
#: src/window.py:567
|
||||
#: window.py:567
|
||||
msgid "Pull"
|
||||
msgstr "Извлечение"
|
||||
|
||||
#: src/window.py:634 src/window.ui:446
|
||||
#: window.py:634 window.ui:446
|
||||
msgid "Clear Chat"
|
||||
msgstr "Очистить Чат"
|
||||
|
||||
#: src/window.py:635
|
||||
#: window.py:635
|
||||
msgid "Are you sure you want to clear the chat?"
|
||||
msgstr "Вы уверены, что хотите очистить чат?"
|
||||
|
||||
#: src/window.py:639
|
||||
#: window.py:639
|
||||
msgid "Clear"
|
||||
msgstr "Очистить"
|
||||
|
||||
#: src/window.py:707
|
||||
#: window.py:707
|
||||
msgid "Remove Image"
|
||||
msgstr "Удалить Изображение"
|
||||
|
||||
#: src/window.py:708
|
||||
#: window.py:708
|
||||
msgid "Are you sure you want to remove image?"
|
||||
msgstr "Вы уверены, что хотите удалить изображение?"
|
||||
|
||||
#: src/window.py:712 src/window.ui:466
|
||||
#: window.py:712 window.ui:466
|
||||
msgid "Remove"
|
||||
msgstr "Удалить"
|
||||
|
||||
#: src/window.py:733
|
||||
#: window.py:733
|
||||
msgid "Delete Chat"
|
||||
msgstr "Удалить Чат"
|
||||
|
||||
#: src/window.py:763
|
||||
#: window.py:763
|
||||
msgid "Rename Chat"
|
||||
msgstr "Переименовать Чат"
|
||||
|
||||
#: src/window.py:770
|
||||
#: window.py:770
|
||||
msgid "Rename"
|
||||
msgstr "Переименовать"
|
||||
|
||||
#: src/window.py:791
|
||||
#: window.py:791
|
||||
msgid "The name '{}' is already in use"
|
||||
msgstr "Имя '{}' уже используется"
|
||||
|
||||
#: src/window.py:804
|
||||
#: window.py:804
|
||||
msgid "Create Chat"
|
||||
msgstr "Создать Чат"
|
||||
|
||||
#: src/window.py:811
|
||||
#: window.py:811
|
||||
msgid "Create"
|
||||
msgstr "Создать"
|
||||
|
||||
#: src/window.py:914
|
||||
#, fuzzy
|
||||
#: window.py:914
|
||||
msgid "Connection Error"
|
||||
msgstr "Удаленное подключение"
|
||||
msgstr "Ошибка Соединения"
|
||||
|
||||
#: src/window.py:915
|
||||
#: window.py:915
|
||||
msgid "The remote instance has disconnected"
|
||||
msgstr ""
|
||||
msgstr "Удаленный экземпляр отключился"
|
||||
|
||||
#: src/window.py:919
|
||||
#, fuzzy
|
||||
#: window.py:919
|
||||
msgid "Close Alpaca"
|
||||
msgstr "Добро пожаловать в Alpaca"
|
||||
msgstr "Закрыть Программу"
|
||||
|
||||
#: src/window.py:920
|
||||
#, fuzzy
|
||||
#: window.py:920
|
||||
msgid "Use local instance"
|
||||
msgstr "URL-адрес удаленного экземпляра"
|
||||
msgstr "Использовать локальный экземпляр"
|
||||
|
||||
#: src/window.py:921
|
||||
#: window.py:921
|
||||
msgid "Connect"
|
||||
msgstr ""
|
||||
msgstr "Подключить"
|
||||
|
||||
#: src/window.ui:40
|
||||
#: window.ui:40
|
||||
msgid "New chat"
|
||||
msgstr "Новый чат"
|
||||
|
||||
#: src/window.ui:49
|
||||
#: window.ui:49
|
||||
msgid "Import chat"
|
||||
msgstr "Импорт чата"
|
||||
|
||||
#: src/window.ui:58
|
||||
#: window.ui:58
|
||||
msgid "Export chat"
|
||||
msgstr "Экспорт чата"
|
||||
|
||||
#: src/window.ui:90
|
||||
#: window.ui:90
|
||||
msgid "Toggle Sidebar"
|
||||
msgstr "Переключение боковой панели"
|
||||
|
||||
#: src/window.ui:111 src/window.ui:303
|
||||
#: window.ui:111 window.ui:303
|
||||
msgid "Manage models"
|
||||
msgstr "Управление моделями"
|
||||
|
||||
#: src/window.ui:125
|
||||
#: window.ui:125
|
||||
msgid "Menu"
|
||||
msgstr "Меню"
|
||||
|
||||
#: src/window.ui:216
|
||||
#: window.ui:216
|
||||
msgid "Send"
|
||||
msgstr "Отправить"
|
||||
|
||||
#: src/window.ui:225
|
||||
#: window.ui:225
|
||||
msgid "Only available on selected models"
|
||||
msgstr ""
|
||||
msgstr "Доступно только для некоторых моделей"
|
||||
|
||||
#: src/window.ui:228
|
||||
#: window.ui:228
|
||||
msgid "Image"
|
||||
msgstr "Изображение"
|
||||
|
||||
#: src/window.ui:254 src/window.ui:450 src/window.ui:508
|
||||
#: window.ui:254 window.ui:450 window.ui:508
|
||||
msgid "Preferences"
|
||||
msgstr "Настройки"
|
||||
|
||||
#: src/window.ui:257 src/window.ui:492
|
||||
#: window.ui:257 window.ui:492
|
||||
msgid "General"
|
||||
msgstr "Общие"
|
||||
|
||||
#: src/window.ui:261
|
||||
#: window.ui:261
|
||||
msgid "Remote Connection"
|
||||
msgstr "Удаленное подключение"
|
||||
|
||||
#: src/window.ui:262
|
||||
#: window.ui:262
|
||||
msgid "Manage a remote connection to Ollama"
|
||||
msgstr "Управление удаленным подключением к Ollama"
|
||||
|
||||
#: src/window.ui:265
|
||||
#: window.ui:265
|
||||
msgid "Use remote connection"
|
||||
msgstr "Использовать удаленное подключение"
|
||||
|
||||
#: src/window.ui:270
|
||||
#: window.ui:270
|
||||
msgid "URL of remote instance"
|
||||
msgstr "URL-адрес удаленного экземпляра"
|
||||
|
||||
#: src/window.ui:278
|
||||
#: window.ui:278
|
||||
msgid "Behavior"
|
||||
msgstr "Поведение"
|
||||
|
||||
#: src/window.ui:279
|
||||
#: window.ui:279
|
||||
msgid "Manage Alpaca's Behavior"
|
||||
msgstr "Управление поведением Alpaca"
|
||||
|
||||
#: src/window.ui:282
|
||||
#: window.ui:282
|
||||
msgid "Run in background"
|
||||
msgstr "Запуск в фоновом режиме"
|
||||
|
||||
#: src/window.ui:371
|
||||
#: window.ui:371
|
||||
msgid "Previous"
|
||||
msgstr "Предыдущий"
|
||||
|
||||
#: src/window.ui:386
|
||||
#: window.ui:386
|
||||
msgid "Next"
|
||||
msgstr "Следующий"
|
||||
|
||||
#: src/window.ui:412
|
||||
#: window.ui:412
|
||||
msgid "Welcome to Alpaca"
|
||||
msgstr "Добро пожаловать в Alpaca"
|
||||
|
||||
#: src/window.ui:413
|
||||
#: window.ui:413
|
||||
msgid "Powered by Ollama"
|
||||
msgstr "При поддержке Ollama"
|
||||
|
||||
#: src/window.ui:416
|
||||
#: window.ui:416
|
||||
msgid "Ollama Website"
|
||||
msgstr "Веб-сайт Ollama"
|
||||
|
||||
#: src/window.ui:433
|
||||
#: window.ui:432
|
||||
msgid "Disclaimer"
|
||||
msgstr "Отказ от ответственности"
|
||||
|
||||
#: window.ui:433
|
||||
msgid ""
|
||||
"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. "
|
||||
@@ -771,57 +331,85 @@ msgstr ""
|
||||
"Пожалуйста, будьте осторожны и внимательно ознакомьтесь с кодом перед его "
|
||||
"запуском."
|
||||
|
||||
#: src/window.ui:454
|
||||
#: window.ui:454
|
||||
msgid "Keyboard Shortcuts"
|
||||
msgstr "Комбинации Клавиш"
|
||||
|
||||
#: src/window.ui:458
|
||||
#: window.ui:458
|
||||
msgid "About Alpaca"
|
||||
msgstr "О Программе"
|
||||
|
||||
#: src/window.ui:496
|
||||
#: window.ui:496
|
||||
msgid "Close application"
|
||||
msgstr "Закрыть приложение"
|
||||
|
||||
#: src/window.ui:502
|
||||
#: window.ui:502
|
||||
msgid "Clear chat"
|
||||
msgstr "Очистить чат"
|
||||
|
||||
#: src/window.ui:514
|
||||
#: window.ui:514
|
||||
msgid "Show shortcuts window"
|
||||
msgstr "Показать окно комбинаций клавиш"
|
||||
|
||||
#: src/window.ui:521
|
||||
#: window.ui:521
|
||||
msgid "Editor"
|
||||
msgstr "Редактор"
|
||||
|
||||
#: src/window.ui:525
|
||||
#: window.ui:525
|
||||
msgid "Copy"
|
||||
msgstr "Копировать"
|
||||
|
||||
#: src/window.ui:531
|
||||
#: window.ui:531
|
||||
msgid "Paste"
|
||||
msgstr "Вставить"
|
||||
|
||||
#: src/window.ui:537
|
||||
#: window.ui:537
|
||||
msgid "Insert new line"
|
||||
msgstr ""
|
||||
msgstr "Вставить новую строку"
|
||||
|
||||
#: src/window.ui:543
|
||||
#: window.ui:543
|
||||
msgid "Send Message"
|
||||
msgstr "Отправить Сообщение"
|
||||
|
||||
#~ msgctxt "shortcut window"
|
||||
#~ msgid "General"
|
||||
#~ msgstr "Общие"
|
||||
#, fuzzy
|
||||
#~ msgid "Alpaca"
|
||||
#~ msgstr "О Программе"
|
||||
|
||||
#~ msgctxt "shortcut window"
|
||||
#~ msgid "Show Shortcuts"
|
||||
#~ msgstr "Показывать комбинации клавиш"
|
||||
#, fuzzy
|
||||
#~ msgid "Chat with local AI models"
|
||||
#~ msgstr "Не удалось перечислить локальные модели"
|
||||
|
||||
#~ msgctxt "shortcut window"
|
||||
#~ msgid "Quit"
|
||||
#~ msgstr "Выйти"
|
||||
#, fuzzy
|
||||
#~ msgid "An Ollama client"
|
||||
#~ 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"
|
||||
#~ msgstr "Чаты"
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# connectionhandler.py
|
||||
import json, requests
|
||||
|
||||
url = None
|
||||
|
||||
def simple_get(connection_url:str) -> dict:
|
||||
try:
|
||||
response = requests.get(connection_url)
|
||||
@@ -37,25 +39,3 @@ def stream_post(connection_url:str, data, callback:callable) -> dict:
|
||||
except Exception as e:
|
||||
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',
|
||||
flags=Gio.ApplicationFlags.DEFAULT_FLAGS)
|
||||
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('about', self.on_about_action)
|
||||
|
||||
@@ -48,7 +47,7 @@ class AlpacaApplication(Adw.Application):
|
||||
application_name='Alpaca',
|
||||
application_icon='com.jeffser.Alpaca',
|
||||
developer_name='Jeffry Samuel Eduarte Rojas',
|
||||
version='0.8.1',
|
||||
version='0.8.5',
|
||||
developers=['Jeffser https://jeffser.com'],
|
||||
designers=['Jeffser https://jeffser.com'],
|
||||
translator_credits='Alex K (Russian) https://github.com/alexkdeveloper\nJeffser (Spanish) https://jeffser.com\nDaimar Stein (Brazilian Portuguese) https://github.com/not-a-dev-stein',
|
||||
|
||||
@@ -31,7 +31,9 @@ alpaca_sources = [
|
||||
'main.py',
|
||||
'window.py',
|
||||
'connection_handler.py',
|
||||
'available_models.py'
|
||||
'available_models.py',
|
||||
'dialogs.py',
|
||||
'local_instance.py'
|
||||
]
|
||||
|
||||
install_data(alpaca_sources, install_dir: moduledir)
|
||||
|
||||
695
src/window.py
695
src/window.py
@@ -26,8 +26,8 @@ from time import sleep
|
||||
from io import BytesIO
|
||||
from PIL import Image
|
||||
from datetime import datetime
|
||||
from .connection_handler import simple_get, simple_delete, stream_post, stream_post_fake
|
||||
from .available_models import available_models
|
||||
from . import dialogs, local_instance, connection_handler
|
||||
|
||||
@Gtk.Template(resource_path='/com/jeffser/Alpaca/window.ui')
|
||||
class AlpacaWindow(Adw.ApplicationWindow):
|
||||
@@ -46,17 +46,13 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
||||
|
||||
#Variables
|
||||
run_on_background = False
|
||||
ollama_url = ""
|
||||
remote_url = ""
|
||||
run_remote = False
|
||||
local_ollama_port = 11435
|
||||
local_ollama_instance = None
|
||||
local_models = []
|
||||
pulling_models = {}
|
||||
current_chat_elements = [] #Used for deleting
|
||||
chats = {"chats": {_("New Chat"): {"messages": []}}, "selected_chat": "New Chat"}
|
||||
attached_image = {"path": None, "base64": None}
|
||||
first_time_setup = False
|
||||
|
||||
#Elements
|
||||
preferences_dialog = Gtk.Template.Child()
|
||||
@@ -80,7 +76,6 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
||||
model_drop_down = Gtk.Template.Child()
|
||||
model_string_list = Gtk.Template.Child()
|
||||
|
||||
manage_models_button = Gtk.Template.Child()
|
||||
manage_models_dialog = Gtk.Template.Child()
|
||||
pulling_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"),
|
||||
_("Chat cannot be cleared while receiving a message"),
|
||||
_("That tag is already being pulled"),
|
||||
_("That tag has been pulled already")
|
||||
_("That tag has been pulled already"),
|
||||
_("Code copied to the clipboard")
|
||||
],
|
||||
"good": [
|
||||
_("Model deleted successfully"),
|
||||
@@ -122,6 +118,127 @@ 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)
|
||||
|
||||
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()
|
||||
|
||||
@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("Connect")
|
||||
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)
|
||||
|
||||
def show_toast(self, message_type:str, message_id:int, overlay):
|
||||
if message_type not in self.toast_messages or message_id > len(self.toast_messages[message_type] or message_id < 0):
|
||||
message_type = "error"
|
||||
@@ -174,7 +291,8 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
||||
message_box = Gtk.Box(
|
||||
orientation=1,
|
||||
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)
|
||||
|
||||
@@ -209,22 +327,9 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
||||
self.bot_message_view = message_text
|
||||
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):
|
||||
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):
|
||||
self.model_string_list.remove(i)
|
||||
if response['status'] == 'ok':
|
||||
@@ -244,7 +349,7 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
||||
valign = 3,
|
||||
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)
|
||||
self.local_model_list_box.append(model_row)
|
||||
|
||||
@@ -257,12 +362,11 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
||||
self.connection_error()
|
||||
|
||||
def verify_connection(self):
|
||||
response = simple_get(self.ollama_url)
|
||||
response = connection_handler.simple_get(connection_handler.url)
|
||||
if response['status'] == 'ok':
|
||||
if "Ollama is running" in response['text']:
|
||||
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)
|
||||
#self.message_text_view.grab_focus_without_selecting()
|
||||
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.update_list_local_models()
|
||||
return True
|
||||
return False
|
||||
@@ -301,8 +405,6 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
||||
wrap_mode= Gtk.WrapMode.WORD,
|
||||
margin_top=12,
|
||||
margin_bottom=12,
|
||||
margin_start=12,
|
||||
margin_end=12,
|
||||
hexpand=True,
|
||||
css_classes=["flat"]
|
||||
)
|
||||
@@ -336,18 +438,50 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
||||
self.bot_message_box.append(message_text)
|
||||
else:
|
||||
language = GtkSource.LanguageManager.get_default().get_language(part['language'])
|
||||
if language:
|
||||
buffer = GtkSource.Buffer.new_with_language(language)
|
||||
else:
|
||||
buffer = GtkSource.Buffer()
|
||||
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(
|
||||
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.get_style_context().add_class("card")
|
||||
self.bot_message_box.append(source_view)
|
||||
code_block_box = Gtk.Box(css_classes=["card"], orientation=1, overflow=1)
|
||||
title_box = Gtk.Box(margin_start=12, margin_top=3, margin_bottom=3, margin_end=3)
|
||||
title_box.append(Gtk.Label(label=language.get_name() if language else 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)
|
||||
self.bot_message = 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):
|
||||
if self.bot_message is None:
|
||||
self.save_history()
|
||||
@@ -373,92 +507,24 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
||||
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']
|
||||
|
||||
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):
|
||||
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.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_icon_name, "send-to-symbolic")
|
||||
GLib.idle_add(self.chat_list_box.set_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)
|
||||
GLib.idle_add(self.toggle_ui_sensitive, True)
|
||||
if self.verify_if_image_can_be_used(): GLib.idle_add(self.image_button.set_sensitive, True)
|
||||
GLib.idle_add(self.image_button.set_css_classes, [])
|
||||
GLib.idle_add(self.image_button.get_child().set_icon_name, "image-x-generic-symbolic")
|
||||
self.attached_image = {"path": None, "base64": None}
|
||||
# GLib.idle_add(self.message_text_view.set_sensitive, True)
|
||||
if response['status'] == '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):
|
||||
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 ""))
|
||||
@@ -467,111 +533,49 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
||||
GLib.idle_add(self.pulling_model_list_box.set_visible, False)
|
||||
sys.exit()
|
||||
|
||||
def pull_model(self, model_name, tag):
|
||||
data = {"name":f"{model_name}:{tag}"}
|
||||
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))
|
||||
def pull_model_process(self, model):
|
||||
data = {"name":model}
|
||||
response = connection_handler.stream_post(f"{connection_handler.url}/api/pull", data=json.dumps(data), callback=lambda data, model_name=model: self.pull_model_update(data, model_name))
|
||||
GLib.idle_add(self.update_list_local_models)
|
||||
|
||||
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.pulling_models[f"{model_name}:{tag}"].get_parent().remove, self.pulling_models[f"{model_name}:{tag}"])
|
||||
del 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[model]
|
||||
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.pulling_models[f"{model_name}:{tag}"].get_parent().remove, self.pulling_models[f"{model_name}:{tag}"])
|
||||
del self.pulling_models[f"{model_name}:{tag}"]
|
||||
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[model].get_parent().remove, self.pulling_models[model])
|
||||
del self.pulling_models[model]
|
||||
GLib.idle_add(self.manage_models_dialog.close)
|
||||
GLib.idle_add(self.connection_error)
|
||||
if len(list(self.pulling_models.keys())) == 0:
|
||||
GLib.idle_add(self.pulling_model_list_box.set_visible, False)
|
||||
|
||||
def stop_pull_model(self, dialog, task, model_name):
|
||||
if dialog.choose_finish(task) == "stop":
|
||||
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()):
|
||||
def pull_model(self, model):
|
||||
if model in list(self.pulling_models.keys()):
|
||||
self.show_toast("info", 3, self.manage_models_overlay)
|
||||
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)
|
||||
return
|
||||
#self.pull_model_status_page.set_description(f"{model_name}:{tag}")
|
||||
self.pulling_model_list_box.set_visible(True)
|
||||
model_row = Adw.ActionRow(
|
||||
title = f"{model_name}:{tag}",
|
||||
subtitle = ""
|
||||
title = model
|
||||
)
|
||||
thread = threading.Thread(target=self.pull_model, args=(model_name, tag))
|
||||
self.pulling_models[f"{model_name}:{tag}"] = model_row
|
||||
thread = threading.Thread(target=self.pull_model_process, kwargs={"model": model})
|
||||
self.pulling_models[model] = model_row
|
||||
button = Gtk.Button(
|
||||
icon_name = "media-playback-stop-symbolic",
|
||||
vexpand = False,
|
||||
valign = 3,
|
||||
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)
|
||||
self.pulling_model_list_box.append(model_row)
|
||||
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):
|
||||
self.available_model_list_box.remove_all()
|
||||
for name, model_info in available_models.items():
|
||||
@@ -592,58 +596,11 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
||||
css_classes = ["accent"]
|
||||
)
|
||||
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(pull_button)
|
||||
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):
|
||||
with open(os.path.join(self.config_dir, "chats.json"), "w+") as f:
|
||||
json.dump(self.chats, f, indent=4)
|
||||
@@ -660,14 +617,13 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
||||
|
||||
def load_history(self):
|
||||
if os.path.exists(os.path.join(self.config_dir, "chats.json")):
|
||||
self.clear_chat()
|
||||
try:
|
||||
with open(os.path.join(self.config_dir, "chats.json"), "r") as f:
|
||||
self.chats = json.load(f)
|
||||
if "selected_chat" not in self.chats or self.chats["selected_chat"] not in self.chats["chats"]: self.chats["selected_chat"] = list(self.chats["chats"].keys())[0]
|
||||
if len(list(self.chats["chats"].keys())) == 0: self.chats["chats"]["New Chat"] = {"messages": []}
|
||||
if len(list(self.chats["chats"].keys())) == 0: self.chats["chats"][_("New Chat")] = {"messages": []}
|
||||
except Exception as e:
|
||||
self.chats = {"chats": {"New Chat": {"messages": []}}, "selected_chat": "New Chat"}
|
||||
self.chats = {"chats": {_("New Chat"): {"messages": []}}, "selected_chat": _("New Chat")}
|
||||
self.load_history_into_chat()
|
||||
|
||||
def load_image(self, file_dialog, result):
|
||||
@@ -695,141 +651,70 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
||||
except Exception as e:
|
||||
self.show_toast("error", 5, self.main_overlay)
|
||||
|
||||
def remove_image(self, dialog, task):
|
||||
if dialog.choose_finish(task) == 'remove':
|
||||
def remove_image(self):
|
||||
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}
|
||||
|
||||
def open_image(self, button):
|
||||
if "destructive-action" in button.get_css_classes():
|
||||
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 = self.remove_image
|
||||
)
|
||||
else:
|
||||
file_dialog = Gtk.FileDialog(default_filter=self.file_filter_image)
|
||||
file_dialog.open(self, None, self.load_image)
|
||||
def generate_numbered_chat_name(self, chat_name) -> str:
|
||||
if chat_name in self.chats["chats"]:
|
||||
for i in range(len(list(self.chats["chats"].keys()))):
|
||||
if chat_name + f" {i+1}" not in self.chats["chats"]:
|
||||
chat_name += f" {i+1}"
|
||||
break
|
||||
return chat_name
|
||||
|
||||
def chat_delete(self, dialog, task, chat_name):
|
||||
if dialog.choose_finish(task) == "delete":
|
||||
def clear_chat(self):
|
||||
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]
|
||||
self.save_history()
|
||||
self.update_chat_list()
|
||||
if len(self.chats['chats'])==0:
|
||||
self.chat_new()
|
||||
|
||||
def chat_delete_dialog(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: 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:
|
||||
def rename_chat(self, old_chat_name, new_chat_name, label_element):
|
||||
new_chat_name = self.generate_numbered_chat_name(new_chat_name)
|
||||
self.chats["chats"][new_chat_name] = self.chats["chats"][old_chat_name]
|
||||
del self.chats["chats"][old_chat_name]
|
||||
label_element.set_label(new_chat_name)
|
||||
self.save_history()
|
||||
self.update_chat_list()
|
||||
|
||||
def chat_rename_dialog(self, chat_name:str, body:str, error:bool=False):
|
||||
entry = Gtk.Entry(
|
||||
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:
|
||||
def new_chat(self, chat_name):
|
||||
chat_name = self.generate_numbered_chat_name(chat_name)
|
||||
self.chats["chats"][chat_name] = {"messages": []}
|
||||
self.chats["selected_chat"] = chat_name
|
||||
self.save_history()
|
||||
self.update_chat_list()
|
||||
self.load_history_into_chat()
|
||||
self.new_chat_element(chat_name)
|
||||
|
||||
def chat_new_dialog(self, body:str, error:bool=False):
|
||||
entry = Gtk.Entry(
|
||||
css_classes = ["error"] if error else None
|
||||
)
|
||||
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 stop_pull_model(self, model_name):
|
||||
self.pulling_models[model_name].get_parent().remove(self.pulling_models[model_name])
|
||||
del self.pulling_models[model_name]
|
||||
|
||||
def update_chat_list(self):
|
||||
self.chat_list_box.remove_all()
|
||||
for name, content in self.chats['chats'].items():
|
||||
def delete_model(self, model_name):
|
||||
response = connection_handler.simple_delete(connection_handler.url + "/api/delete", data={"name": model_name})
|
||||
self.update_list_local_models()
|
||||
if response['status'] == 'ok':
|
||||
self.show_toast("good", 0, self.manage_models_overlay)
|
||||
else:
|
||||
self.manage_models_dialog.close()
|
||||
self.connection_error()
|
||||
|
||||
def new_chat_element(self, chat_name):
|
||||
chat_content = Gtk.Box(
|
||||
spacing = 6,
|
||||
spacing=6
|
||||
)
|
||||
chat_row = Gtk.ListBoxRow(
|
||||
css_classes = ["chat_row"],
|
||||
height_request = 45,
|
||||
child = chat_content,
|
||||
name = name
|
||||
name = chat_name
|
||||
)
|
||||
chat_label = Gtk.Label(
|
||||
label=name,
|
||||
label=chat_name,
|
||||
hexpand=True,
|
||||
halign=1
|
||||
)
|
||||
@@ -839,94 +724,47 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
||||
valign = 3,
|
||||
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(
|
||||
icon_name = "document-edit-symbolic",
|
||||
vexpand = False,
|
||||
valign = 3,
|
||||
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(button_delete)
|
||||
chat_content.append(button_rename)
|
||||
self.chat_list_box.append(chat_row)
|
||||
if name==self.chats["selected_chat"]: self.chat_list_box.select_row(chat_row)
|
||||
if chat_name==self.chats["selected_chat"]: self.chat_list_box.select_row(chat_row)
|
||||
|
||||
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
|
||||
def update_chat_list(self):
|
||||
self.chat_list_box.remove_all()
|
||||
for name, content in self.chats['chats'].items():
|
||||
self.new_chat_element(name)
|
||||
|
||||
def show_preferences_dialog(self):
|
||||
self.preferences_dialog.present(self)
|
||||
|
||||
def start_instance(self):
|
||||
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)
|
||||
print("Starting Alpaca's Ollama instance...")
|
||||
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
|
||||
def connect_remote(self, url):
|
||||
connection_handler.url = url
|
||||
self.remote_url = connection_handler.url
|
||||
self.remote_connection_entry.set_text(self.remote_url)
|
||||
if self.verify_connection() == False: self.connection_error()
|
||||
elif response == "local":
|
||||
|
||||
def connect_local(self):
|
||||
self.run_remote = False
|
||||
self.ollama_url = f"http://127.0.0.1:{self.local_ollama_port}"
|
||||
self.start_instance()
|
||||
connection_handler.url = f"http://127.0.0.1:{local_instance.port}"
|
||||
local_instance.start(self.data_dir)
|
||||
if self.verify_connection() == False: self.connection_error()
|
||||
else: self.remote_connection_switch.set_active(False)
|
||||
elif response == "close":
|
||||
self.destroy()
|
||||
|
||||
def connection_error(self):
|
||||
if self.run_remote:
|
||||
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=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)
|
||||
)
|
||||
dialogs.reconnect_remote(self)
|
||||
else:
|
||||
self.restart_instance()
|
||||
local_instance.stop()
|
||||
local_instance.start(self.data_dir)
|
||||
self.show_toast("error", 7, self.main_overlay)
|
||||
|
||||
def connection_switched(self):
|
||||
@@ -934,23 +772,14 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
||||
if new_value != self.run_remote:
|
||||
self.run_remote = new_value
|
||||
if self.run_remote:
|
||||
self.ollama_url = self.remote_url
|
||||
connection_handler.url = self.remote_url
|
||||
if self.verify_connection() == False: self.connection_error()
|
||||
else: self.stop_instance()
|
||||
else: local_instance.stop()
|
||||
else:
|
||||
self.ollama_url = f"http://127.0.0.1:{self.local_ollama_port}"
|
||||
self.start_instance()
|
||||
connection_handler.url = f"http://127.0.0.1:{local_instance.port}"
|
||||
local_instance.start(self.data_dir)
|
||||
if self.verify_connection() == False: self.connection_error()
|
||||
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):
|
||||
file.replace_contents_finish(result)
|
||||
@@ -999,46 +828,36 @@ class AlpacaWindow(Adw.ApplicationWindow):
|
||||
GtkSource.init()
|
||||
self.set_help_overlay(self.shortcut_window)
|
||||
self.get_application().set_accels_for_action("win.show-help-overlay", ['<primary>slash'])
|
||||
self.get_application().create_action('clear', lambda *_: dialogs.clear_chat(self), ['<primary>e'])
|
||||
self.get_application().create_action('send', lambda *_: self.send_message(self), ['Return'])
|
||||
self.manage_models_button.connect("clicked", self.manage_models_button_activate)
|
||||
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.add_chat_button.connect("clicked", lambda button : dialogs.new_chat(self))
|
||||
|
||||
self.export_chat_button.connect("clicked", lambda button : self.export_current_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("apply", self.change_remote_url)
|
||||
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())
|
||||
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:
|
||||
data = json.load(f)
|
||||
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.run_on_background = data['run_on_background']
|
||||
self.background_switch.set_active(self.run_on_background)
|
||||
self.set_hide_on_close(self.run_on_background)
|
||||
self.remote_connection_entry.set_text(self.remote_url)
|
||||
if self.run_remote:
|
||||
self.ollama_url = data['remote_url']
|
||||
connection_handler.url = data['remote_url']
|
||||
self.remote_connection_switch.set_active(True)
|
||||
else:
|
||||
self.remote_connection_switch.set_active(False)
|
||||
self.ollama_url = f"http://127.0.0.1:{self.local_ollama_port}"
|
||||
self.start_instance()
|
||||
connection_handler.url = f"http://127.0.0.1:{local_instance.port}"
|
||||
local_instance.start(self.data_dir)
|
||||
else:
|
||||
self.start_instance()
|
||||
self.ollama_url = f"http://127.0.0.1:{self.local_ollama_port}"
|
||||
self.first_time_setup = True
|
||||
local_instance.start(self.data_dir)
|
||||
connection_handler.url = f"http://127.0.0.1:{local_instance.port}"
|
||||
self.welcome_dialog.present(self)
|
||||
if self.verify_connection() is False and self.run_remote == False: self.connection_error()
|
||||
self.update_list_available_models()
|
||||
|
||||
@@ -27,8 +27,6 @@
|
||||
</object>
|
||||
</child>
|
||||
<property name="content">
|
||||
<object class="AdwToastOverlay" id="main_overlay">
|
||||
<child>
|
||||
<object class="AdwOverlaySplitView" id="split_view_overlay">
|
||||
<property name="show-sidebar" bind-source="show_sidebar_button" bind-property="active" bind-flags="sync-create"/>
|
||||
<property name="sidebar">
|
||||
@@ -38,7 +36,7 @@
|
||||
<child type="start">
|
||||
<object class="GtkButton" id="add_chat_button">
|
||||
<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>
|
||||
<class name="flat"/>
|
||||
</style>
|
||||
@@ -70,6 +68,7 @@
|
||||
<property name="hexpand">true</property>
|
||||
<child>
|
||||
<object class="GtkListBox" id="chat_list_box">
|
||||
<signal name="row-selected" handler="chat_changed"/>
|
||||
<property name="selection-mode">single</property>
|
||||
<style>
|
||||
<class name="navigation-sidebar"></class>
|
||||
@@ -97,6 +96,7 @@
|
||||
<property name="spacing">12</property>
|
||||
<child>
|
||||
<object class="GtkDropDown" id="model_drop_down">
|
||||
<signal name="notify" handler="verify_if_image_can_be_used"/>
|
||||
<property name="enable-search">true</property>
|
||||
<property name="model">
|
||||
<object class="GtkStringList" id="model_string_list">
|
||||
@@ -108,6 +108,7 @@
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="manage_models_button">
|
||||
<signal name="clicked" handler="manage_models_button_activate"/>
|
||||
<property name="tooltip-text" translatable="yes">Manage models</property>
|
||||
<child>
|
||||
<object class="AdwButtonContent">
|
||||
@@ -133,6 +134,8 @@
|
||||
<property name="orientation">1</property>
|
||||
<property name="vexpand">true</property>
|
||||
<property name="hexpand">true</property>
|
||||
<child>
|
||||
<object class="AdwToastOverlay" id="main_overlay">
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="chat_window">
|
||||
<property name="propagate-natural-height">true</property>
|
||||
@@ -162,7 +165,8 @@
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwClamp">
|
||||
<property name="maximum-size">1000</property>
|
||||
@@ -208,6 +212,7 @@
|
||||
<property name="spacing">12</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="send_button">
|
||||
<signal name="clicked" handler="send_message"/>
|
||||
<style>
|
||||
<class name="suggested-action"/>
|
||||
</style>
|
||||
@@ -221,6 +226,7 @@
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="image_button">
|
||||
<signal name="clicked" handler="open_image"/>
|
||||
<property name="sensitive">false</property>
|
||||
<property name="tooltip-text" translatable="yes">Only available on selected models</property>
|
||||
<child>
|
||||
@@ -244,8 +250,6 @@
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
<object class="AdwPreferencesDialog" id="preferences_dialog">
|
||||
<property name="can-close">true</property>
|
||||
@@ -267,6 +271,7 @@
|
||||
</child>
|
||||
<child>
|
||||
<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="show-apply-button">true</property>
|
||||
</object>
|
||||
@@ -368,6 +373,7 @@
|
||||
<property name="margin-bottom">5</property>
|
||||
<child type="start">
|
||||
<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="label">Previous</property>
|
||||
<property name="sensitive">false</property>
|
||||
@@ -383,6 +389,7 @@
|
||||
</child>
|
||||
<child type="end">
|
||||
<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="label">Next</property>
|
||||
<style>
|
||||
@@ -395,6 +402,7 @@
|
||||
|
||||
<child>
|
||||
<object class="AdwCarousel" id="welcome_carousel">
|
||||
<signal name="page-changed" handler="welcome_carousel_page_changed"/>
|
||||
<property name="hexpand">true</property>
|
||||
<property name="vexpand">true</property>
|
||||
<property name="allow-long-swipes">true</property>
|
||||
|
||||
@@ -4,7 +4,7 @@ echo "Preparing template..."
|
||||
xgettext --output=po/alpaca.pot --files-from=po/POTFILES
|
||||
echo "Updating Spanish..."
|
||||
msgmerge -U po/es.po po/alpaca.pot
|
||||
echo "Updating Russian..."
|
||||
msgmerge -U po/ru.po po/alpaca.pot
|
||||
#echo "Updating Russian..."
|
||||
#msgmerge -U po/ru.po po/alpaca.pot
|
||||
echo "Updating Brazilian Portuguese"
|
||||
msgmerge -U po/pt_BR.po po/alpaca.pot
|
||||
|
||||
Reference in New Issue
Block a user